diff --git a/.htaccess b/.htaccess index 546f18e6..60e17952 100644 --- a/.htaccess +++ b/.htaccess @@ -32,7 +32,7 @@ ## adjust memory limit # php_value memory_limit 64M - php_value memory_limit 128M + php_value memory_limit 256M php_value max_execution_time 18000 ############################################ @@ -122,12 +122,35 @@ #RewriteBase /magento/ +############################################ +## uncomment next line to enable light API calls processing + +# RewriteRule ^api/([a-z][0-9a-z_]+)/?$ api.php?type=$1 [QSA,L] + +############################################ +## rewrite API2 calls to api.php (by now it is REST only) + + RewriteRule ^api/rest api.php?type=rest [QSA,L] + ############################################ ## workaround for HTTP authorization ## in CGI environment RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +############################################ +## TRACE and TRACK HTTP methods disabled to prevent XSS attacks + + RewriteCond %{REQUEST_METHOD} ^TRAC[EK] + RewriteRule .* - [L,R=405] + +############################################ +## redirect for mobile user agents + + #RewriteCond %{REQUEST_URI} !^/mobiledirectoryhere/.*$ + #RewriteCond %{HTTP_USER_AGENT} "android|blackberry|ipad|iphone|ipod|iemobile|opera mobile|palmos|webos|googlebot-mobile" [NC] + #RewriteRule ^(.*)$ /mobiledirectoryhere/ [L,R=302] + ############################################ ## always send 404 on missing files in these folders @@ -171,9 +194,16 @@ Order allow,deny Allow from all +########################################### +## Deny access to release notes to prevent disclosure of the installed Magento version + + + order allow,deny + deny from all + + ############################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags #FileETag none - diff --git a/api.php b/api.php new file mode 100644 index 00000000..6430d5cc --- /dev/null +++ b/api.php @@ -0,0 +1,88 @@ +loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS); +Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_ADMINHTML, Mage_Core_Model_App_Area::PART_EVENTS); + +// query parameter "type" is set by .htaccess rewrite rule +$apiAlias = Mage::app()->getRequest()->getParam('type'); + +// check request could be processed by API2 +if (in_array($apiAlias, Mage_Api2_Model_Server::getApiTypes())) { + /** @var $server Mage_Api2_Model_Server */ + $server = Mage::getSingleton('api2/server'); + + $server->run(); +} else { + /* @var $server Mage_Api_Model_Server */ + $server = Mage::getSingleton('api/server'); + $adapterCode = $server->getAdapterCodeByAlias($apiAlias); + + // if no adapters found in aliases - find it by default, by code + if (null === $adapterCode) { + $adapterCode = $apiAlias; + } + try { + $server->initialize($adapterCode); + $server->run(); + + Mage::app()->getResponse()->sendResponse(); + } catch (Exception $e) { + Mage::logException($e); + + echo $e->getMessage(); + exit; + } +} diff --git a/app/Mage.php b/app/Mage.php index cec11538..3dafad03 100644 --- a/app/Mage.php +++ b/app/Mage.php @@ -33,8 +33,8 @@ if (defined('COMPILER_INCLUDE_PATH')) { $appPath = COMPILER_INCLUDE_PATH; set_include_path($appPath . PS . Mage::registry('original_include_path')); - include_once "Mage_Core_functions.php"; - include_once "Varien_Autoload.php"; + include_once COMPILER_INCLUDE_PATH . DS . "Mage_Core_functions.php"; + include_once COMPILER_INCLUDE_PATH . DS . "Varien_Autoload.php"; } else { /** * Set include path @@ -129,6 +129,22 @@ final class Mage */ static private $_isInstalled; + /** + * Magento edition constants + */ + const EDITION_COMMUNITY = 'Community'; + const EDITION_ENTERPRISE = 'Enterprise'; + const EDITION_PROFESSIONAL = 'Professional'; + const EDITION_GO = 'Go'; + + /** + * Current Magento edition. + * + * @var string + * @static + */ + static private $_currentEdition = self::EDITION_COMMUNITY; + /** * Gets the current Magento version string * @link http://www.magentocommerce.com/blog/new-community-edition-release-process/ @@ -138,7 +154,8 @@ final class Mage public static function getVersion() { $i = self::getVersionInfo(); - return trim("{$i['major']}.{$i['minor']}.{$i['revision']}" . ($i['patch'] != '' ? ".{$i['patch']}" : "") . "-{$i['stability']}{$i['number']}", '.-'); + return trim("{$i['major']}.{$i['minor']}.{$i['revision']}" . ($i['patch'] != '' ? ".{$i['patch']}" : "") + . "-{$i['stability']}{$i['number']}", '.-'); } /** @@ -151,14 +168,25 @@ public static function getVersionInfo() { return array( 'major' => '1', - 'minor' => '4', - 'revision' => '1', - 'patch' => '1', + 'minor' => '7', + 'revision' => '0', + 'patch' => '0', 'stability' => '', 'number' => '', ); } + /** + * Get current Magento edition + * + * @static + * @return string + */ + public static function getEdition() + { + return self::$_currentEdition; + } + /** * Set all my static data to defaults * @@ -166,12 +194,14 @@ public static function getVersionInfo() public static function reset() { self::$_registry = array(); + self::$_appRoot = null; self::$_app = null; self::$_config = null; self::$_events = null; self::$_objects = null; self::$_isDownloader = false; self::$_isDeveloperMode = false; + self::$_isInstalled = null; // do not reset $headersSentThrowsException } @@ -343,6 +373,7 @@ public static function getStoreConfigFlag($path, $store = null) * Get base URL path by type * * @param string $type + * @param null|bool $secure * @return string */ public static function getBaseUrl($type = Mage_Core_Model_Store::URL_TYPE_LINK, $secure = null) @@ -404,17 +435,16 @@ public static function addObserver($eventName, $callback, $data = array(), $obse * Dispatch event * * Calls all observer callbacks registered for this event - * and multiobservers matching event name pattern + * and multiple observers matching event name pattern * * @param string $name - * @param array $args + * @param array $data * @return Mage_Core_Model_App */ public static function dispatchEvent($name, array $data = array()) { Varien_Profiler::start('DISPATCH EVENT:'.$name); $result = self::app()->dispatchEvent($name, $data); - #$result = self::registry('events')->dispatch($name, $data); Varien_Profiler::stop('DISPATCH EVENT:'.$name); return $result; } @@ -424,8 +454,8 @@ public static function dispatchEvent($name, array $data = array()) * * @link Mage_Core_Model_Config::getModelInstance * @param string $modelClass - * @param array $arguments - * @return Mage_Core_Model_Abstract + * @param array|object $arguments + * @return Mage_Core_Model_Abstract|false */ public static function getModel($modelClass = '', $arguments = array()) { @@ -510,10 +540,6 @@ public static function getBlockSingleton($type) */ public static function helper($name) { - if (strpos($name, '/') === false) { - $name .= '/data'; - } - $registryKey = '_helper/' . $name; if (!self::registry($registryKey)) { $helperClass = self::getConfig()->getHelperClassName($name); @@ -522,6 +548,23 @@ public static function helper($name) return self::registry($registryKey); } + /** + * Retrieve resource helper object + * + * @param string $moduleName + * @return Mage_Core_Model_Resource_Helper_Abstract + */ + public static function getResourceHelper($moduleName) + { + $registryKey = '_resource_helper/' . $moduleName; + if (!self::registry($registryKey)) { + $helperClass = self::getConfig()->getResourceHelper($moduleName); + self::register($registryKey, $helperClass); + } + + return self::registry($registryKey); + } + /** * Return new exception by module to be thrown * @@ -532,7 +575,7 @@ public static function helper($name) */ public static function exception($module = 'Mage_Core', $message = '', $code = 0) { - $className = $module.'_Exception'; + $className = $module . '_Exception'; return new $className($message, $code); } @@ -541,6 +584,7 @@ public static function exception($module = 'Mage_Core', $message = '', $code = 0 * * @param string $message * @param string $messageStorage + * @throws Mage_Core_Exception */ public static function throwException($message, $messageStorage = null) { @@ -564,7 +608,8 @@ public static function app($code = '', $type = 'store', $options = array()) self::$_app = new Mage_Core_Model_App(); self::setRoot(); self::$_events = new Varien_Event_Collection(); - self::$_config = new Mage_Core_Model_Config(); + self::_setIsInstalled($options); + self::_setConfigModel($options); Varien_Profiler::start('self::app::init'); self::$_app->init($code, $type, $options); @@ -574,6 +619,38 @@ public static function app($code = '', $type = 'store', $options = array()) return self::$_app; } + /** + * @static + * @param string $code + * @param string $type + * @param array $options + * @param string|array $modules + */ + public static function init($code = '', $type = 'store', $options = array(), $modules = array()) + { + try { + self::setRoot(); + self::$_app = new Mage_Core_Model_App(); + self::_setIsInstalled($options); + self::_setConfigModel($options); + + if (!empty($modules)) { + self::$_app->initSpecified($code, $type, $options, $modules); + } else { + self::$_app->init($code, $type, $options); + } + } catch (Mage_Core_Model_Session_Exception $e) { + header('Location: ' . self::getBaseUrl()); + die; + } catch (Mage_Core_Model_Store_Exception $e) { + require_once(self::getBaseDir() . DS . 'errors' . DS . '404.php'); + die; + } catch (Exception $e) { + self::printException($e); + die; + } + } + /** * Front end main entry point * @@ -581,14 +658,24 @@ public static function app($code = '', $type = 'store', $options = array()) * @param string $type * @param string|array $options */ - public static function run($code = '', $type = 'store', $options=array()) + public static function run($code = '', $type = 'store', $options = array()) { try { Varien_Profiler::start('mage'); self::setRoot(); - self::$_app = new Mage_Core_Model_App(); + if (isset($options['edition'])) { + self::$_currentEdition = $options['edition']; + } + self::$_app = new Mage_Core_Model_App(); + if (isset($options['request'])) { + self::$_app->setRequest($options['request']); + } + if (isset($options['response'])) { + self::$_app->setResponse($options['response']); + } self::$_events = new Varien_Event_Collection(); - self::$_config = new Mage_Core_Model_Config(); + self::_setIsInstalled($options); + self::_setConfigModel($options); self::$_app->run(array( 'scope_code' => $code, 'scope_type' => $type, @@ -619,6 +706,40 @@ public static function run($code = '', $type = 'store', $options=array()) } } + /** + * Set application isInstalled flag based on given options + * + * @param array $options + */ + protected static function _setIsInstalled($options = array()) + { + if (isset($options['is_installed']) && $options['is_installed']) { + self::$_isInstalled = true; + } + } + + /** + * Set application Config model + * + * @param array $options + */ + protected static function _setConfigModel($options = array()) + { + if (isset($options['config_model']) && class_exists($options['config_model'])) { + $alternativeConfigModelName = $options['config_model']; + unset($options['config_model']); + $alternativeConfigModel = new $alternativeConfigModelName($options); + } else { + $alternativeConfigModel = null; + } + + if (!is_null($alternativeConfigModel) && ($alternativeConfigModel instanceof Mage_Core_Model_Config)) { + self::$_config = $alternativeConfigModel; + } else { + self::$_config = new Mage_Core_Model_Config($options); + } + } + /** * Retrieve application installation flag * @@ -633,11 +754,11 @@ public static function isInstalled($options = array()) if (is_string($options)) { $options = array('etc_dir' => $options); } - $etcDir = 'etc'; + $etcDir = self::getRoot() . DS . 'etc'; if (!empty($options['etc_dir'])) { $etcDir = $options['etc_dir']; } - $localConfigFile = self::getRoot() . DS . $etcDir . DS . 'local.xml'; + $localConfigFile = $etcDir . DS . 'local.xml'; self::$_isInstalled = false; @@ -687,10 +808,12 @@ public static function log($message, $level = null, $file = '', $forceLog = fals try { if (!isset($loggers[$file])) { - $logFile = self::getBaseDir('var') . DS . 'log' . DS . $file; + $logDir = self::getBaseDir('var') . DS . 'log'; + $logFile = $logDir . DS . $file; - if (!is_dir(self::getBaseDir('var').DS.'log')) { - mkdir(self::getBaseDir('var').DS.'log', 0777); + if (!is_dir($logDir)) { + mkdir($logDir); + chmod($logDir, 0777); } if (!file_exists($logFile)) { diff --git a/app/code/core/Mage/Admin/Helper/Data.php b/app/code/core/Mage/Admin/Helper/Data.php new file mode 100644 index 00000000..b7d76733 --- /dev/null +++ b/app/code/core/Mage/Admin/Helper/Data.php @@ -0,0 +1,61 @@ + + */ +class Mage_Admin_Helper_Data extends Mage_Core_Helper_Abstract +{ + /** + * Configuration path to expiration period of reset password link + */ + const XML_PATH_ADMIN_RESET_PASSWORD_LINK_EXPIRATION_PERIOD + = 'default/admin/emails/password_reset_link_expiration_period'; + + /** + * Generate unique token for reset password confirmation link + * + * @return string + */ + public function generateResetPasswordLinkToken() + { + return Mage::helper('core')->uniqHash(); + } + + /** + * Retrieve customer reset password link expiration period in days + * + * @return int + */ + public function getResetPasswordLinkExpirationPeriod() + { + return (int) Mage::getConfig()->getNode(self::XML_PATH_ADMIN_RESET_PASSWORD_LINK_EXPIRATION_PERIOD); + } +} diff --git a/app/code/core/Mage/Admin/Model/Acl.php b/app/code/core/Mage/Admin/Model/Acl.php index 4754cfc5..50255c5a 100644 --- a/app/code/core/Mage/Admin/Model/Acl.php +++ b/app/code/core/Mage/Admin/Model/Acl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Assert/Ip.php b/app/code/core/Mage/Admin/Model/Acl/Assert/Ip.php index 629b3e62..da793cb4 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Assert/Ip.php +++ b/app/code/core/Mage/Admin/Model/Acl/Assert/Ip.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Assert/Time.php b/app/code/core/Mage/Admin/Model/Acl/Assert/Time.php index 7b5d8d5d..83494d11 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Assert/Time.php +++ b/app/code/core/Mage/Admin/Model/Acl/Assert/Time.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Resource.php b/app/code/core/Mage/Admin/Model/Acl/Resource.php index ab1a66ef..54ce9761 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Resource.php +++ b/app/code/core/Mage/Admin/Model/Acl/Resource.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Role.php b/app/code/core/Mage/Admin/Model/Acl/Role.php index a50216e6..4297107e 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Role.php +++ b/app/code/core/Mage/Admin/Model/Acl/Role.php @@ -20,19 +20,41 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * User acl role - * - * @category Mage - * @package Mage_Admin + * + * @method Mage_Admin_Model_Resource_Acl_Role _getResource() + * @method Mage_Admin_Model_Resource_Acl_Role getResource() + * @method int getParentId() + * @method Mage_Admin_Model_Acl_Role setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Admin_Model_Acl_Role setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Admin_Model_Acl_Role setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Admin_Model_Acl_Role setRoleType(string $value) + * @method int getUserId() + * @method Mage_Admin_Model_Acl_Role setUserId(int $value) + * @method string getRoleName() + * @method Mage_Admin_Model_Acl_Role setRoleName(string $value) + * + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ -class Mage_Admin_Model_Acl_Role extends Varien_Object +class Mage_Admin_Model_Acl_Role extends Mage_Core_Model_Abstract { - + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('admin/acl_role'); + } } diff --git a/app/code/core/Mage/Admin/Model/Acl/Role/Generic.php b/app/code/core/Mage/Admin/Model/Acl/Role/Generic.php index cb91256d..232e528f 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Role/Generic.php +++ b/app/code/core/Mage/Admin/Model/Acl/Role/Generic.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Role/Group.php b/app/code/core/Mage/Admin/Model/Acl/Role/Group.php index 5250a9ba..4afcea59 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Role/Group.php +++ b/app/code/core/Mage/Admin/Model/Acl/Role/Group.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Role/Registry.php b/app/code/core/Mage/Admin/Model/Acl/Role/Registry.php index cd77a724..41c56f9d 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Role/Registry.php +++ b/app/code/core/Mage/Admin/Model/Acl/Role/Registry.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Acl/Role/User.php b/app/code/core/Mage/Admin/Model/Acl/Role/User.php index 319f420d..728b20b1 100644 --- a/app/code/core/Mage/Admin/Model/Acl/Role/User.php +++ b/app/code/core/Mage/Admin/Model/Acl/Role/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/Model/Config.php b/app/code/core/Mage/Admin/Model/Config.php index 52d55468..a5de8809 100644 --- a/app/code/core/Mage/Admin/Model/Config.php +++ b/app/code/core/Mage/Admin/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -48,6 +48,7 @@ public function __construct() { parent::__construct(); $this->setCacheId('adminhtml_acl_menu_config'); + /* @var $adminhtmlConfig Varien_Simplexml_Config */ $adminhtmlConfig = Mage::app()->loadCache($this->getCacheId()); if ($adminhtmlConfig) { @@ -86,13 +87,13 @@ public function __construct() * @param string $parentName * @return Mage_Admin_Model_Config */ - public function loadAclResources(Mage_Admin_Model_Acl $acl, $resource=null, $parentName=null) + public function loadAclResources(Mage_Admin_Model_Acl $acl, $resource = null, $parentName = null) { if (is_null($resource)) { $resource = $this->getAdminhtmlConfig()->getNode("acl/resources"); $resourceName = null; } else { - $resourceName = (is_null($parentName) ? '' : $parentName.'/').$resource->getName(); + $resourceName = (is_null($parentName) ? '' : $parentName . '/') . $resource->getName(); $acl->add(Mage::getModel('admin/acl_resource', $resourceName), $parentName); } @@ -113,6 +114,9 @@ public function loadAclResources(Mage_Admin_Model_Acl $acl, $resource=null, $par } foreach ($children as $res) { + if (1 == $res->disabled) { + continue; + } $this->loadAclResources($acl, $res, $resourceName); } return $this; @@ -124,10 +128,10 @@ public function loadAclResources(Mage_Admin_Model_Acl $acl, $resource=null, $par * @param string $name * @return Mage_Core_Model_Config_Element|boolean */ - public function getAclAssert($name='') + public function getAclAssert($name = '') { $asserts = $this->getNode("admin/acl/asserts"); - if (''===$name) { + if ('' === $name) { return $asserts; } @@ -144,10 +148,10 @@ public function getAclAssert($name='') * @param string $name * @return Mage_Core_Model_Config_Element|boolean */ - public function getAclPrivilegeSet($name='') + public function getAclPrivilegeSet($name = '') { $sets = $this->getNode("admin/acl/privilegeSets"); - if (''===$name) { + if ('' === $name) { return $sets; } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Acl.php b/app/code/core/Mage/Admin/Model/Mysql4/Acl.php index 82e71c10..6a52e1b8 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Acl.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Acl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,145 +28,10 @@ /** * Resource model for admin ACL * - * @category Mage - * @package Mage_Admin + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ -class Mage_Admin_Model_Mysql4_Acl +class Mage_Admin_Model_Mysql4_Acl extends Mage_Admin_Model_Resource_Acl { - const ACL_ALL_RULES = 'all'; - - /** - * Read resource connection - * - * @var mixed - */ - protected $_read; - - /** - * Write resource connection - * - * @var mixed - */ - protected $_write; - - /** - * Initialize resource connections - * - */ - function __construct() - { - $this->_read = Mage::getSingleton('core/resource')->getConnection('admin_read'); - $this->_write = Mage::getSingleton('core/resource')->getConnection('admin_write'); - } - - /** - * Load ACL for the user - * - * @param integer $userId - * @return Mage_Admin_Model_Acl - */ - function loadAcl() - { - $acl = Mage::getModel('admin/acl'); - - Mage::getSingleton('admin/config')->loadAclResources($acl); - - $roleTable = Mage::getSingleton('core/resource')->getTableName('admin/role'); - $rolesArr = $this->_read->fetchAll("select * from $roleTable order by tree_level"); - $this->loadRoles($acl, $rolesArr); - - $ruleTable = Mage::getSingleton('core/resource')->getTableName('admin/rule'); - $assertTable = Mage::getSingleton('core/resource')->getTableName('admin/assert'); - $rulesArr = $this->_read->fetchAll("select r.*, a.assert_type, a.assert_data - from $ruleTable r left join $assertTable a on a.assert_id=r.assert_id"); - $this->loadRules($acl, $rulesArr); - - return $acl; - } - - /** - * Load roles - * - * @param Mage_Admin_Model_Acl $acl - * @param array $rolesArr - * @return Mage_Admin_Model_Mysql4_Acl - */ - function loadRoles(Mage_Admin_Model_Acl $acl, array $rolesArr) - { - foreach ($rolesArr as $role) { - $parent = $role['parent_id']>0 ? Mage_Admin_Model_Acl::ROLE_TYPE_GROUP.$role['parent_id'] : null; - switch ($role['role_type']) { - case Mage_Admin_Model_Acl::ROLE_TYPE_GROUP: - $roleId = $role['role_type'].$role['role_id']; - $acl->addRole(Mage::getModel('admin/acl_role_group', $roleId), $parent); - break; - - case Mage_Admin_Model_Acl::ROLE_TYPE_USER: - $roleId = $role['role_type'].$role['user_id']; - if (!$acl->hasRole($roleId)) { - $acl->addRole(Mage::getModel('admin/acl_role_user', $roleId), $parent); - } else { - $acl->addRoleParent($roleId, $parent); - } - break; - } - } - - return $this; - } - - /** - * Load rules - * - * @param Mage_Admin_Model_Acl $acl - * @param array $rulesArr - * @return Mage_Admin_Model_Mysql4_Acl - */ - function loadRules(Mage_Admin_Model_Acl $acl, array $rulesArr) - { - foreach ($rulesArr as $rule) { - $role = $rule['role_type'].$rule['role_id']; - $resource = $rule['resource_id']; - $privileges = !empty($rule['privileges']) ? explode(',', $rule['privileges']) : null; - - $assert = null; - if (0!=$rule['assert_id']) { - $assertClass = Mage::getSingleton('admin/config')->getAclAssert($rule['assert_type'])->getClassName(); - $assert = new $assertClass(unserialize($rule['assert_data'])); - } - try { - if ( $rule['permission'] == 'allow' ) { - if ($resource === self::ACL_ALL_RULES) { - $acl->allow($role, null, $privileges, $assert); - } - $acl->allow($role, $resource, $privileges, $assert); - } else if ( $rule['permission'] == 'deny' ) { - $acl->deny($role, $resource, $privileges, $assert); - } - } catch (Exception $e) { - //$m = $e->getMessage(); - //if ( eregi("^Resource '(.*)' not found", $m) ) { - // Deleting non existent resource rule from rules table - //$cond = $this->_write->quoteInto('resource_id = ?', $resource); - //$this->_write->delete(Mage::getSingleton('core/resource')->getTableName('admin/rule'), $cond); - //} else { - //TODO: We need to log such exceptions to somewhere like a system/errors.log - //} - } - /* - switch ($rule['permission']) { - case Mage_Admin_Model_Acl::RULE_PERM_ALLOW: - $acl->allow($role, $resource, $privileges, $assert); - break; - - case Mage_Admin_Model_Acl::RULE_PERM_DENY: - $acl->deny($role, $resource, $privileges, $assert); - break; - } - */ - } - return $this; - } - } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role.php b/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role.php index 68c7cbc6..316d374d 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role.php @@ -20,66 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * ACL role resource * - * @category Mage - * @package Mage_Admin + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ -class Mage_Admin_Model_Mysql4_Acl_Role +class Mage_Admin_Model_Mysql4_Acl_Role extends Mage_Admin_Model_Resource_Acl_Role { - protected $_roleTable; - protected $_read; - protected $_write; - - public function __construct() - { - $this->_roleTable = Mage::getSingleton('core/resource')->getTableName('admin/role'); - $this->_read = Mage::getSingleton('core/resource')->getConnection('admin_read'); - $this->_write = Mage::getSingleton('core/resource')->getConnection('admin_write'); - } - - public function load($roleId) - { - $select = $this->_read->select()->from($this->_roleTable) - ->where("role_id=?", $roleId); - return $this->_read->fetchRow($select); - } - - public function save(Mage_Admin_Model_Acl_Role $role) - { - $data = $role->getData(); - - $this->_write->beginTransaction(); - - try { - if ($role->getId()) { - $condition = $this->_write->quoteInto('role_id=?', $role->getRoleId()); - $this->_write->update($this->_roleTable, $data, $condition); - } else { - $data['created'] = now(); - $this->_write->insert($this->_roleTable, $data); - $role->setRoleId($this->_write->lastInsertId()); - } - - $this->_write->commit(); - } - catch (Mage_Core_Exception $e) - { - $this->_write->rollback(); - throw $e; - } - - return $role; - } - - public function delete() - { - - } } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role/Collection.php index 3c3ace06..52187c07 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Acl/Role/Collection.php @@ -20,27 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Roles collection * - * @category Mage - * @package Mage_Admin + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ -class Mage_Admin_Model_Mysql4_Acl_Role_Collection extends Varien_Data_Collection_Db +class Mage_Admin_Model_Mysql4_Acl_Role_Collection extends Mage_Admin_Model_Resource_Acl_Role_Collection { - protected $_roleTable; - - public function __construct() - { - parent::__construct(Mage::getSingleton('core/resource')->getConnection('admin_read')); - $this->_roleTable = Mage::getSingleton('core/resource')->getTableName('admin/role'); - $this->_select->from($this->_roleTable); - - $this->setItemObjectClass(Mage::getConfig()->getModelClassName('admin/acl_role')); - } } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Permissions/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Permissions/Collection.php index 627824d8..8bbd4a5e 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Permissions/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Permissions/Collection.php @@ -20,14 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Permissions_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract + +/** + * Permissions Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Permissions_Collection extends Mage_Admin_Model_Resource_Permissions_Collection { - protected function _construct() - { - $this->_init('admin/rules'); - } } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Role.php b/app/code/core/Mage/Admin/Model/Mysql4/Role.php index a0083331..6a7228ba 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Role.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Role.php @@ -20,36 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Role extends Mage_Core_Model_Mysql4_Abstract -{ - protected function _construct() - { - $this->_init('admin/role', 'role_id'); - } - - /** - * - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if ( !$object->getId() ) { - $object->setCreated(now()); - } - $object->setModified(now()); - return $this; - } - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - if (!intval($value) && is_string($value)) { - $field = 'role_id'; - } - return parent::load($object, $value, $field); - } +/** + * Mysql4 Role resource model + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Role extends Mage_Admin_Model_Resource_Role +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Role/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Role/Collection.php index 386d4cb7..a2dc6fe7 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Role/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Role/Collection.php @@ -20,33 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Role_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('admin/role'); - } - - /** - * Enter description here... - * - * @param int $userId - * @return Mage_Admin_Model_Mysql4_Role_Collection - */ - public function setUserFilter($userId) - { - $this->addFieldToFilter('user_id', $userId); - $this->addFieldToFilter('role_type', 'G'); - return $this; - } - public function setRolesFilter() - { - $this->addFieldToFilter('role_type', 'G'); - return $this; - } +/** + * Role Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Role_Collection extends Mage_Admin_Model_Resource_Role_Collection +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Roles.php b/app/code/core/Mage/Admin/Model/Mysql4/Roles.php index 5c042fbb..ebafc56c 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Roles.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Roles.php @@ -20,77 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Roles extends Mage_Core_Model_Mysql4_Abstract -{ - protected $_usersTable; - protected $_ruleTable; - - protected function _construct() { - $this->_init('admin/role', 'role_id'); - - $this->_usersTable = $this->getTable('admin/user'); - $this->_ruleTable = $this->getTable('admin/rule'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $role) - { - if ($role->getId() == '') { - if ($role->getIdFieldName()) { - $role->unsetData($role->getIdFieldName()); - } else { - $role->unsetData('id'); - } - } - - if ($role->getPid() > 0) { - $row = $this->load($role->getPid()); - } else { - $row = array('tree_level' => 0); - } - $role->setTreeLevel($row['tree_level'] + 1); - $role->setRoleName($role->getName()); - return $this; - } - - protected function _afterSave(Mage_Core_Model_Abstract $role) - { - $this->_updateRoleUsersAcl($role); - Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, - array(Mage_Adminhtml_Block_Page_Menu::CACHE_TAGS)); - return $this; - } - - protected function _afterDelete(Mage_Core_Model_Abstract $role) - { - $this->_getWriteAdapter()->delete($this->getMainTable(), "parent_id={$role->getId()}"); - $this->_getWriteAdapter()->delete($this->_ruleTable, "role_id={$role->getId()}"); - return $this; - } - public function getRoleUsers(Mage_Admin_Model_Roles $role) - { - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getMainTable(), array('user_id'))->where("(parent_id = '{$role->getId()}' AND role_type = 'U') AND user_id > 0"); - return $read->fetchCol($select); - } - - private function _updateRoleUsersAcl(Mage_Admin_Model_Roles $role) - { - $write = $this->_getWriteAdapter(); - $users = $this->getRoleUsers($role); - $rowsCount = 0; - if ( sizeof($users) > 0 ) { - $inStatement = implode(", ", $users); - $rowsCount = $write->update($this->_usersTable, array('reload_acl_flag' => 1), "user_id IN({$inStatement})"); - } - if ($rowsCount > 0) { - return true; - } else { - return false; - } - } +/** + * Mysql4 Roles resource model + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Roles extends Mage_Admin_Model_Resource_Roles +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Roles/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Roles/Collection.php index 0a949ba5..0028b4b8 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Roles/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Roles/Collection.php @@ -20,24 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Roles_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('admin/role'); - } - - protected function _initSelect(){ - parent::_initSelect(); - $this->getSelect()->where("main_table.role_type='G'"); - } - public function toOptionArray() - { - return $this->_toOptionArray('role_id', 'role_name'); - } +/** + * Roles Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Roles_Collection extends Mage_Admin_Model_Resource_Roles_Collection +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Roles/User/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Roles/User/Collection.php index 63cb13dc..652680a9 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Roles/User/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Roles/User/Collection.php @@ -20,21 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Roles_User_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('admin/user'); - } - - protected function _initSelect() - { - parent::_initSelect(); - $this->getSelect()->where("user_id > 0"); - } +/** + * Roles User Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Roles_User_Collection extends Mage_Admin_Model_Resource_Roles_User_Collection +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Rules.php b/app/code/core/Mage/Admin/Model/Mysql4/Rules.php index a0e94e0b..7df3b808 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Rules.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Rules.php @@ -20,57 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Rules extends Mage_Core_Model_Mysql4_Abstract -{ - protected function _construct() { - $this->_init('admin/rule', 'rule_id'); - } - - /** - * Save ACL resources - * - * @param Mage_Admin_Model_Rules $rule - */ - public function saveRel(Mage_Admin_Model_Rules $rule) - { - try { - $this->_getWriteAdapter()->beginTransaction(); - $roleId = $rule->getRoleId(); - $this->_getWriteAdapter()->delete($this->getMainTable(), "role_id = {$roleId}"); - $postedResources = $rule->getResources(); - if ($postedResources) { - $row = array( - 'role_type' => 'G', - 'resource_id' => 'all', - 'privileges' => '', // not used yet - 'assert_id' => 0, - 'role_id' => $roleId, - 'permission' => 'allow' - ); - - // If all was selected save it only and nothing else. - if ($postedResources === array('all')) { - $this->_getWriteAdapter()->insert($this->getMainTable(), $row); - } else { - foreach (Mage::getModel('admin/roles')->getResourcesList2D() as $index => $resName) { - $row['permission'] = (in_array($resName, $postedResources) ? 'allow' : 'deny'); - $row['resource_id'] = trim($resName, '/'); - $this->_getWriteAdapter()->insert($this->getMainTable(), $row); - } - } - } - $this->_getWriteAdapter()->commit(); - } catch (Mage_Core_Exception $e) { - $this->_getWriteAdapter()->rollBack(); - throw $e; - } catch (Exception $e){ - $this->_getWriteAdapter()->rollBack(); - Mage::logException($e); - } - } +/** + * Mysql4 Rules Resource Model + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Rules extends Mage_Admin_Model_Resource_Rules +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/Rules/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/Rules/Collection.php index 1b76a1f7..c6c2e1d7 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/Rules/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/Rules/Collection.php @@ -20,27 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_Rules_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('admin/rules'); - } - - public function getByRoles($id) - { - $this->getSelect()->where("role_id = ?", (int)$id); - return $this; - } - public function addSortByLength() - { - $this->getSelect()->columns(array('length' => 'LENGTH(resource_id)')) - ->order('length desc'); - return $this; - } +/** + * Mysql4 Rules Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_Rules_Collection extends Mage_Admin_Model_Resource_Rules_Collection +{ } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/User.php b/app/code/core/Mage/Admin/Model/Mysql4/User.php index 386d7eb8..867e5ab2 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/User.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/User.php @@ -20,271 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * ACL user resource * - * @category Mage - * @package Mage_Admin + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ -class Mage_Admin_Model_Mysql4_User extends Mage_Core_Model_Mysql4_Abstract +class Mage_Admin_Model_Mysql4_User extends Mage_Admin_Model_Resource_User { - - protected function _construct() - { - $this->_init('admin/user', 'user_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array( - array( - 'field' => 'email', - 'title' => Mage::helper('adminhtml')->__('Email') - ), - array( - 'field' => 'username', - 'title' => Mage::helper('adminhtml')->__('User Name') - ), - ); - return $this; - } - - /** - * Authenticate user by $username and $password - * - * @param string $username - * @param string $password - * @return boolean|Object - */ - public function recordLogin(Mage_Admin_Model_User $user) - { - $data = array( - 'logdate' => now(), - 'lognum' => $user->getLognum()+1 - ); - $condition = $this->_getWriteAdapter()->quoteInto('user_id=?', $user->getUserId()); - $this->_getWriteAdapter()->update($this->getTable('admin/user'), $data, $condition); - return $this; - } - - public function loadByUsername($username) - { - $select = $this->_getReadAdapter()->select()->from($this->getTable('admin/user')) - ->where('username=:username'); - return $this->_getReadAdapter()->fetchRow($select, array('username'=>$username)); - } - - public function hasAssigned2Role($user) - { - if (is_numeric($user)) { - $userId = $user; - } else if ($user instanceof Mage_Core_Model_Abstract) { - $userId = $user->getUserId(); - } else { - return null; - } - - if ( $userId > 0 ) { - $dbh = $this->_getReadAdapter(); - $select = $dbh->select(); - $select->from($this->getTable('admin/role')) - ->where("parent_id > 0 AND user_id = {$userId}"); - return $dbh->fetchAll($select); - } else { - return null; - } - } - - private function _encryptPassword($pwStr) - { - return Mage::helper('core')->getHash($pwStr, 2); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $user) - { - if (!$user->getId()) { - $user->setCreated(now()); - } - $user->setModified(now()); - return $this; - } - - protected function _afterSave(Mage_Core_Model_Abstract $user) - { - $user->setExtra(unserialize($user->getExtra())); - return $this; - } - - protected function _afterLoad(Mage_Core_Model_Abstract $user) - { - if (is_string($user->getExtra())) { - $user->setExtra(unserialize($user->getExtra())); - } - return parent::_afterLoad($user); - } - - public function load(Mage_Core_Model_Abstract $user, $value, $field=null) - { -// if (!intval($value) && is_string($value)) { -// $field = 'user_id'; -// } - return parent::load($user, $value, $field); - } - - public function delete(Mage_Core_Model_Abstract $user) - { - $dbh = $this->_getWriteAdapter(); - $uid = $user->getId(); - $dbh->beginTransaction(); - try { - $dbh->delete($this->getTable('admin/user'), "user_id=$uid"); - $dbh->delete($this->getTable('admin/role'), "user_id=$uid"); - } catch (Mage_Core_Exception $e) { - throw $e; - return false; - } catch (Exception $e){ - $dbh->rollBack(); - return false; - } - $dbh->commit(); - return true; - } - - /** - * TODO: unify _saveRelations() and add() methods, they make same things - */ - public function _saveRelations(Mage_Core_Model_Abstract $user) - { - $rolesIds = $user->getRoleIds(); - - if( !is_array($rolesIds) || count($rolesIds) == 0 ) { - return $user; - } - - $this->_getWriteAdapter()->beginTransaction(); - - try { - $this->_getWriteAdapter()->delete($this->getTable('admin/role'), "user_id = {$user->getId()}"); - foreach ($rolesIds as $rid) { - $rid = intval($rid); - if ($rid > 0) { - $row = Mage::getModel('admin/role')->load($rid)->getData(); - } else { - $row = array('tree_level' => 0); - } - - $data = array( - 'parent_id' => $rid, - 'tree_level' => $row['tree_level'] + 1, - 'sort_order' => 0, - 'role_type' => 'U', - 'user_id' => $user->getId(), - 'role_name' => $user->getFirstname() - ); - $this->_getWriteAdapter()->insert($this->getTable('admin/role'), $data); - } - $this->_getWriteAdapter()->commit(); - } catch (Mage_Core_Exception $e) { - throw $e; - } catch (Exception $e){ - $this->_getWriteAdapter()->rollBack(); - } - } - - public function getRoles(Mage_Core_Model_Abstract $user) - { - if ( !$user->getId() ) { - return array(); - } - $table = $this->getTable('admin/role'); - $read = $this->_getReadAdapter(); - $select = $read->select()->from($table, array()) - ->joinLeft(array('ar' => $table), "(ar.role_id = `{$table}`.parent_id and ar.role_type = 'G')", array('role_id')) - ->where("`{$table}`.user_id = {$user->getId()}"); - - return (($roles = $read->fetchCol($select)) ? $roles : array()); - } - - public function add(Mage_Core_Model_Abstract $user) - { - $dbh = $this->_getWriteAdapter(); - - $aRoles = $this->hasAssigned2Role($user); - if ( sizeof($aRoles) > 0 ) { - foreach($aRoles as $idx => $data){ - $dbh->delete($this->getTable('admin/role'), "role_id = {$data['role_id']}"); - } - } - - if ($user->getId() > 0) { - $role = Mage::getModel('admin/role')->load($user->getRoleId()); - } else { - $role = new Varien_Object(); - $role->setTreeLevel(0); - } - $dbh->insert($this->getTable('admin/role'), array( - 'parent_id' => $user->getRoleId(), - 'tree_level'=> ($role->getTreeLevel() + 1), - 'sort_order'=> 0, - 'role_type' => 'U', - 'user_id' => $user->getUserId(), - 'role_name' => $user->getFirstname() - )); - - return $this; - } - - public function deleteFromRole(Mage_Core_Model_Abstract $user) - { - if ( $user->getUserId() <= 0 ) { - return $this; - } - if ( $user->getRoleId() <= 0 ) { - return $this; - } - $dbh = $this->_getWriteAdapter(); - $condition = "`{$this->getTable('admin/role')}`.user_id = ".$dbh->quote($user->getUserId())." AND `{$this->getTable('admin/role')}`.parent_id = ".$dbh->quote($user->getRoleId()); - $dbh->delete($this->getTable('admin/role'), $condition); - return $this; - } - - public function roleUserExists(Mage_Core_Model_Abstract $user) - { - if ( $user->getUserId() > 0 ) { - $roleTable = $this->getTable('admin/role'); - $dbh = $this->_getReadAdapter(); - $select = $dbh->select()->from($roleTable) - ->where("parent_id = {$user->getRoleId()} AND user_id = {$user->getUserId()}"); - return $dbh->fetchCol($select); - } else { - return array(); - } - } - - public function userExists(Mage_Core_Model_Abstract $user) - { - $usersTable = $this->getTable('admin/user'); - $select = $this->_getReadAdapter()->select(); - $select->from($usersTable); - $select->where("({$usersTable}.username = '{$user->getUsername()}' OR {$usersTable}.email = '{$user->getEmail()}') AND {$usersTable}.user_id != '{$user->getId()}'"); - return $this->_getReadAdapter()->fetchRow($select); - } - - public function saveExtra($object, $data) - { - if ($object->getId()) { - $this->_getWriteAdapter()->update($this->getMainTable(), array('extra'=>$data)); - } - return $this; - } } diff --git a/app/code/core/Mage/Admin/Model/Mysql4/User/Collection.php b/app/code/core/Mage/Admin/Model/Mysql4/User/Collection.php index b39f1a91..18c34eb5 100644 --- a/app/code/core/Mage/Admin/Model/Mysql4/User/Collection.php +++ b/app/code/core/Mage/Admin/Model/Mysql4/User/Collection.php @@ -20,14 +20,18 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Admin_Model_Mysql4_User_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract + +/** + * Admin Mysql4 User Collection + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ +class Mage_Admin_Model_Mysql4_User_Collection extends Mage_Admin_Model_Resource_User_Collection { - protected function _construct() - { - $this->_init('admin/user'); - } } diff --git a/app/code/core/Mage/Admin/Model/Observer.php b/app/code/core/Mage/Admin/Model/Observer.php index 1644cb35..b4913820 100644 --- a/app/code/core/Mage/Admin/Model/Observer.php +++ b/app/code/core/Mage/Admin/Model/Observer.php @@ -20,30 +20,44 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Admin observer model * - * @category Mage - * @package Mage_Admin + * @category Mage + * @package Mage_Admin * @author Magento Core Team */ class Mage_Admin_Model_Observer { - public function actionPreDispatchAdmin($event) + const FLAG_NO_LOGIN = 'no-login'; + /** + * Handler for controller_action_predispatch event + * + * @param Varien_Event_Observer $observer + * @return boolean + */ + public function actionPreDispatchAdmin($observer) { - $session = Mage::getSingleton('admin/session'); - /* @var $session Mage_Admin_Model_Session */ + $session = Mage::getSingleton('admin/session'); + /** @var $session Mage_Admin_Model_Session */ $request = Mage::app()->getRequest(); $user = $session->getUser(); - if ($request->getActionName() == 'forgotpassword' || $request->getActionName() == 'logout') { + $requestedActionName = $request->getActionName(); + $openActions = array( + 'forgotpassword', + 'resetpassword', + 'resetpasswordpost', + 'logout', + 'refresh' // captcha refresh + ); + if (in_array($requestedActionName, $openActions)) { $request->setDispatched(true); - } - else { + } else { if($user) { $user->reload(); } @@ -52,7 +66,7 @@ public function actionPreDispatchAdmin($event) $postLogin = $request->getPost('login'); $username = isset($postLogin['username']) ? $postLogin['username'] : ''; $password = isset($postLogin['password']) ? $postLogin['password'] : ''; - $user = $session->login($username, $password, $request); + $session->login($username, $password, $request); $request->setPost('login', null); } if (!$request->getParam('forwarded')) { @@ -61,8 +75,7 @@ public function actionPreDispatchAdmin($event) ->setControllerName('index') ->setActionName('deniedIframe') ->setDispatched(false); - } - elseif($request->getParam('isAjax')) { + } elseif($request->getParam('isAjax')) { $request->setParam('forwarded', true) ->setControllerName('index') ->setActionName('deniedJson') diff --git a/app/code/core/Mage/Admin/Model/Resource/Acl.php b/app/code/core/Mage/Admin/Model/Resource/Acl.php new file mode 100755 index 00000000..764f2253 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Acl.php @@ -0,0 +1,171 @@ + + */ +class Mage_Admin_Model_Resource_Acl extends Mage_Core_Model_Resource_Db_Abstract +{ + const ACL_ALL_RULES = 'all'; + + /** + * Initialize resource + * + */ + protected function _construct() + { + $this->_init('admin/role', 'role_id'); + } + + /** + * Load ACL for the user + * + * @return Mage_Admin_Model_Acl + */ + public function loadAcl() + { + $acl = Mage::getModel('admin/acl'); + + Mage::getSingleton('admin/config')->loadAclResources($acl); + + $roleTable = $this->getTable('admin/role'); + $ruleTable = $this->getTable('admin/rule'); + $assertTable = $this->getTable('admin/assert'); + + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($roleTable) + ->order('tree_level'); + + $rolesArr = $adapter->fetchAll($select); + + $this->loadRoles($acl, $rolesArr); + + $select = $adapter->select() + ->from(array('r' => $ruleTable)) + ->joinLeft( + array('a' => $assertTable), + 'a.assert_id = r.assert_id', + array('assert_type', 'assert_data') + ); + + $rulesArr = $adapter->fetchAll($select); + + $this->loadRules($acl, $rulesArr); + + return $acl; + } + + /** + * Load roles + * + * @param Mage_Admin_Model_Acl $acl + * @param array $rolesArr + * @return Mage_Admin_Model_Resource_Acl + */ + public function loadRoles(Mage_Admin_Model_Acl $acl, array $rolesArr) + { + foreach ($rolesArr as $role) { + $parent = ($role['parent_id'] > 0) ? Mage_Admin_Model_Acl::ROLE_TYPE_GROUP . $role['parent_id'] : null; + switch ($role['role_type']) { + case Mage_Admin_Model_Acl::ROLE_TYPE_GROUP: + $roleId = $role['role_type'] . $role['role_id']; + $acl->addRole(Mage::getModel('admin/acl_role_group', $roleId), $parent); + break; + + case Mage_Admin_Model_Acl::ROLE_TYPE_USER: + $roleId = $role['role_type'] . $role['user_id']; + if (!$acl->hasRole($roleId)) { + $acl->addRole(Mage::getModel('admin/acl_role_user', $roleId), $parent); + } else { + $acl->addRoleParent($roleId, $parent); + } + break; + } + } + + return $this; + } + + /** + * Load rules + * + * @param Mage_Admin_Model_Acl $acl + * @param array $rulesArr + * @return Mage_Admin_Model_Resource_Acl + */ + public function loadRules(Mage_Admin_Model_Acl $acl, array $rulesArr) + { + foreach ($rulesArr as $rule) { + $role = $rule['role_type'] . $rule['role_id']; + $resource = $rule['resource_id']; + $privileges = !empty($rule['privileges']) ? explode(',', $rule['privileges']) : null; + + $assert = null; + if (0 != $rule['assert_id']) { + $assertClass = Mage::getSingleton('admin/config')->getAclAssert($rule['assert_type'])->getClassName(); + $assert = new $assertClass(unserialize($rule['assert_data'])); + } + try { + if ( $rule['permission'] == 'allow' ) { + if ($resource === self::ACL_ALL_RULES) { + $acl->allow($role, null, $privileges, $assert); + } + $acl->allow($role, $resource, $privileges, $assert); + } else if ( $rule['permission'] == 'deny' ) { + $acl->deny($role, $resource, $privileges, $assert); + } + } catch (Exception $e) { + //$m = $e->getMessage(); + //if ( eregi("^Resource '(.*)' not found", $m) ) { + // Deleting non existent resource rule from rules table + //$cond = $this->_write->quoteInto('resource_id = ?', $resource); + //$this->_write->delete(Mage::getSingleton('core/resource')->getTableName('admin/rule'), $cond); + //} else { + //TODO: We need to log such exceptions to somewhere like a system/errors.log + //} + } + /* + switch ($rule['permission']) { + case Mage_Admin_Model_Acl::RULE_PERM_ALLOW: + $acl->allow($role, $resource, $privileges, $assert); + break; + + case Mage_Admin_Model_Acl::RULE_PERM_DENY: + $acl->deny($role, $resource, $privileges, $assert); + break; + } + */ + } + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Acl/Role.php b/app/code/core/Mage/Admin/Model/Resource/Acl/Role.php new file mode 100755 index 00000000..1f347b14 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Acl/Role.php @@ -0,0 +1,45 @@ + + */ +class Mage_Admin_Model_Resource_Acl_Role extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('admin/role', 'role_id'); + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Acl/Role/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Acl/Role/Collection.php new file mode 100755 index 00000000..6900a561 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Acl/Role/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Admin_Model_Resource_Acl_Role_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource + * + */ + protected function _construct() + { + $this->_init('admin/acl_role'); + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Permissions/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Permissions/Collection.php new file mode 100755 index 00000000..317eecd4 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Permissions/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Admin_Model_Resource_Permissions_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource + * + */ + protected function _construct() + { + $this->_init('admin/rules'); + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Role.php b/app/code/core/Mage/Admin/Model/Resource/Role.php new file mode 100755 index 00000000..af860450 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Role.php @@ -0,0 +1,60 @@ + + */ +class Mage_Admin_Model_Resource_Role extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('admin/role', 'role_id'); + } + + /** + * Process role before saving + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Admin_Model_Resource_Role + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if ( !$object->getId() ) { + $object->setCreated($this->formatDate(true)); + } + $object->setModified($this->formatDate(true)); + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Role/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Role/Collection.php new file mode 100755 index 00000000..4b7af19e --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Role/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Admin_Model_Resource_Role_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('admin/role'); + } + + /** + * Add user filter + * + * @param int $userId + * @return Mage_Admin_Model_Resource_Role_Collection + */ + public function setUserFilter($userId) + { + $this->addFieldToFilter('user_id', $userId); + $this->addFieldToFilter('role_type', 'G'); + return $this; + } + + /** + * Set roles filter + * + * @return Mage_Admin_Model_Resource_Role_Collection + */ + public function setRolesFilter() + { + $this->addFieldToFilter('role_type', 'G'); + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Roles.php b/app/code/core/Mage/Admin/Model/Resource/Roles.php new file mode 100755 index 00000000..9347265b --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Roles.php @@ -0,0 +1,178 @@ + + */ +class Mage_Admin_Model_Resource_Roles extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Users table + * + * @var string + */ + protected $_usersTable; + + /** + * Rule table + * + * @var string + */ + protected $_ruleTable; + + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('admin/role', 'role_id'); + + $this->_usersTable = $this->getTable('admin/user'); + $this->_ruleTable = $this->getTable('admin/rule'); + } + + /** + * Process role before saving + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Admin_Model_Resource_Roles + */ + protected function _beforeSave(Mage_Core_Model_Abstract $role) + { + if ($role->getId() == '') { + if ($role->getIdFieldName()) { + $role->unsetData($role->getIdFieldName()); + } else { + $role->unsetData('id'); + } + } + + if ($role->getPid() > 0) { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), array('tree_level')) + ->where("{$this->getIdFieldName()} = :pid"); + + $binds = array( + 'pid' => (int) $role->getPid(), + ); + + $treeLevel = $this->_getReadAdapter()->fetchOne($select, $binds); + } else { + $treeLevel = 0; + } + $role->setTreeLevel($treeLevel + 1); + $role->setRoleName($role->getName()); + return $this; + } + + /** + * Process role after saving + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Admin_Model_Resource_Roles + */ + protected function _afterSave(Mage_Core_Model_Abstract $role) + { + $this->_updateRoleUsersAcl($role); + Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, + array(Mage_Adminhtml_Block_Page_Menu::CACHE_TAGS)); + return $this; + } + + /** + * Process role after deleting + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Admin_Model_Resource_Roles + */ + protected function _afterDelete(Mage_Core_Model_Abstract $role) + { + $adapter = $this->_getWriteAdapter(); + + $adapter->delete( + $this->getMainTable(), + array('parent_id = ?' => (int) $role->getId()) + ); + + $adapter->delete( + $this->_ruleTable, + array('role_id = ?' => (int) $role->getId()) + ); + + return $this; + } + + /** + * Get role users + * + * @param Mage_Admin_Model_Roles $role + * @return array|false + */ + public function getRoleUsers(Mage_Admin_Model_Roles $role) + { + $read = $this->_getReadAdapter(); + + $binds = array( + 'role_id' => $role->getId(), + 'role_type' => 'U' + ); + + $select = $read->select() + ->from($this->getMainTable(), array('user_id')) + ->where('parent_id = :role_id') + ->where('role_type = :role_type') + ->where('user_id > 0'); + + return $read->fetchCol($select, $binds); + } + + /** + * Update role users ACL + * + * @param Mage_Admin_Model_Roles $role + * @return bool + */ + private function _updateRoleUsersAcl(Mage_Admin_Model_Roles $role) + { + $write = $this->_getWriteAdapter(); + $users = $this->getRoleUsers($role); + $rowsCount = 0; + + if (sizeof($users) > 0) { + $bind = array('reload_acl_flag' => 1); + $where = array('user_id IN(?)' => $users); + $rowsCount = $write->update($this->_usersTable, $bind, $where); + } + + return $rowsCount > 0; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Roles/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Roles/Collection.php new file mode 100755 index 00000000..18ad7710 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Roles/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Admin_Model_Resource_Roles_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('admin/role'); + } + + /** + * Init select + * + * @return Mage_Admin_Model_Resource_Roles_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + + $this->getSelect()->where("main_table.role_type = ?", 'G'); + + return $this; + } + + /** + * Convert to option array + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('role_id', 'role_name'); + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Roles/User/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Roles/User/Collection.php new file mode 100755 index 00000000..c912810c --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Roles/User/Collection.php @@ -0,0 +1,59 @@ + + */ +class Mage_Admin_Model_Resource_Roles_User_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('admin/user'); + } + + /** + * Initialize select + * + * @return Mage_Admin_Model_Resource_Roles_User_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + + $this->getSelect()->where("user_id > 0"); + + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Rules.php b/app/code/core/Mage/Admin/Model/Resource/Rules.php new file mode 100755 index 00000000..a0bfeb80 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Rules.php @@ -0,0 +1,100 @@ + + */ +class Mage_Admin_Model_Resource_Rules extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('admin/rule', 'rule_id'); + } + + /** + * Save ACL resources + * + * @param Mage_Admin_Model_Rules $rule + */ + public function saveRel(Mage_Admin_Model_Rules $rule) + { + try { + $adapter = $this->_getWriteAdapter(); + $adapter->beginTransaction(); + $roleId = $rule->getRoleId(); + + $condition = array( + 'role_id = ?' => (int) $roleId, + ); + + $adapter->delete($this->getMainTable(), $condition); + + $postedResources = $rule->getResources(); + if ($postedResources) { + $row = array( + 'role_type' => 'G', + 'resource_id' => 'all', + 'privileges' => '', // not used yet + 'assert_id' => 0, + 'role_id' => $roleId, + 'permission' => 'allow' + ); + + // If all was selected save it only and nothing else. + if ($postedResources === array('all')) { + $insertData = $this->_prepareDataForTable(new Varien_Object($row), $this->getMainTable()); + + $adapter->insert($this->getMainTable(), $insertData); + } else { + foreach (Mage::getModel('admin/roles')->getResourcesList2D() as $index => $resName) { + $row['permission'] = (in_array($resName, $postedResources) ? 'allow' : 'deny'); + $row['resource_id'] = trim($resName, '/'); + + $insertData = $this->_prepareDataForTable(new Varien_Object($row), $this->getMainTable()); + $adapter->insert($this->getMainTable(), $insertData); + } + } + } + + $adapter->commit(); + } catch (Mage_Core_Exception $e) { + $adapter->rollBack(); + throw $e; + } catch (Exception $e){ + $adapter->rollBack(); + Mage::logException($e); + } + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/Rules/Collection.php b/app/code/core/Mage/Admin/Model/Resource/Rules/Collection.php new file mode 100755 index 00000000..5fa82c45 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/Rules/Collection.php @@ -0,0 +1,71 @@ + + */ +class Mage_Admin_Model_Resource_Rules_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('admin/rules'); + } + + /** + * Get rules by role id + * + * @param int $id + * @return Mage_Admin_Model_Resource_Rules_Collection + */ + public function getByRoles($id) + { + $this->addFieldToFilter('role_id', (int) $id); + return $this; + } + + /** + * Sort by length + * + * @return Mage_Admin_Model_Resource_Rules_Collection + */ + public function addSortByLength() + { + $length = $this->getConnection()->getLengthSql('{{resource_id}}'); + $this->addExpressionFieldToSelect('length', $length, 'resource_id'); + $this->getSelect()->order('length ' . Zend_Db_Select::SQL_DESC); + + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/User.php b/app/code/core/Mage/Admin/Model/Resource/User.php new file mode 100755 index 00000000..70b5f37e --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/User.php @@ -0,0 +1,463 @@ + + */ +class Mage_Admin_Model_Resource_User extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('admin/user', 'user_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Admin_Model_Resource_User + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array( + array( + 'field' => 'email', + 'title' => Mage::helper('adminhtml')->__('Email') + ), + array( + 'field' => 'username', + 'title' => Mage::helper('adminhtml')->__('User Name') + ), + ); + return $this; + } + + /** + * Authenticate user by $username and $password + * + * @param Mage_Admin_Model_User $user + * @return Mage_Admin_Model_Resource_User + */ + public function recordLogin(Mage_Admin_Model_User $user) + { + $adapter = $this->_getWriteAdapter(); + + $data = array( + 'logdate' => now(), + 'lognum' => $user->getLognum() + 1 + ); + + $condition = array( + 'user_id = ?' => (int) $user->getUserId(), + ); + + $adapter->update($this->getMainTable(), $data, $condition); + + return $this; + } + + /** + * Load data by specified username + * + * @param string $username + * @return false|array + */ + public function loadByUsername($username) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('username=:username'); + + $binds = array( + 'username' => $username + ); + + return $adapter->fetchRow($select, $binds); + } + + /** + * Check if user is assigned to any role + * + * @param int|Mage_Core_Admin_Model_User $user + * @return null|false|array + */ + public function hasAssigned2Role($user) + { + if (is_numeric($user)) { + $userId = $user; + } else if ($user instanceof Mage_Core_Model_Abstract) { + $userId = $user->getUserId(); + } else { + return null; + } + + if ( $userId > 0 ) { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select(); + $select->from($this->getTable('admin/role')) + ->where('parent_id > :parent_id') + ->where('user_id = :user_id'); + + $binds = array( + 'parent_id' => 0, + 'user_id' => $userId, + ); + + return $adapter->fetchAll($select, $binds); + } else { + return null; + } + } + + /** + * Encrypt password + * + * @param string $pwStr + * @return string + */ + private function _encryptPassword($pwStr) + { + return Mage::helper('core')->getHash($pwStr, 2); + } + + /** + * Set created/modified values before user save + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + protected function _beforeSave(Mage_Core_Model_Abstract $user) + { + if ($user->isObjectNew()) { + $user->setCreated($this->formatDate(true)); + } + $user->setModified($this->formatDate(true)); + + return parent::_beforeSave($user); + } + + /** + * Unserialize user extra data after user save + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + protected function _afterSave(Mage_Core_Model_Abstract $user) + { + $user->setExtra(unserialize($user->getExtra())); + return $this; + } + + /** + * Unserialize user extra data after user load + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + protected function _afterLoad(Mage_Core_Model_Abstract $user) + { + if (is_string($user->getExtra())) { + $user->setExtra(unserialize($user->getExtra())); + } + return parent::_afterLoad($user); + } + + /** + * Delete user role record with user + * + * @param Mage_Core_Model_Abstract $user + * @return bool + */ + public function delete(Mage_Core_Model_Abstract $user) + { + $this->_beforeDelete($user); + $adapter = $this->_getWriteAdapter(); + + $uid = $user->getId(); + $adapter->beginTransaction(); + try { + $conditions = array( + 'user_id = ?' => $uid + ); + + $adapter->delete($this->getMainTable(), $conditions); + $adapter->delete($this->getTable('admin/role'), $conditions); + } catch (Mage_Core_Exception $e) { + throw $e; + return false; + } catch (Exception $e){ + $adapter->rollBack(); + return false; + } + $adapter->commit(); + $this->_afterDelete($user); + return true; + } + + /** + * TODO: unify _saveRelations() and add() methods, they make same things + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + public function _saveRelations(Mage_Core_Model_Abstract $user) + { + $rolesIds = $user->getRoleIds(); + + if( !is_array($rolesIds) || count($rolesIds) == 0 ) { + return $user; + } + + $adapter = $this->_getWriteAdapter(); + + $adapter->beginTransaction(); + + try { + $conditions = array( + 'user_id = ?' => (int) $user->getId(), + ); + + $adapter->delete($this->getTable('admin/role'), $conditions); + foreach ($rolesIds as $rid) { + $rid = intval($rid); + if ($rid > 0) { + $row = Mage::getModel('admin/role')->load($rid)->getData(); + } else { + $row = array('tree_level' => 0); + } + + $data = new Varien_Object(array( + 'parent_id' => $rid, + 'tree_level' => $row['tree_level'] + 1, + 'sort_order' => 0, + 'role_type' => 'U', + 'user_id' => $user->getId(), + 'role_name' => $user->getFirstname() + )); + + $insertData = $this->_prepareDataForTable($data, $this->getTable('admin/role')); + $adapter->insert($this->getTable('admin/role'), $insertData); + } + $adapter->commit(); + } catch (Mage_Core_Exception $e) { + throw $e; + } catch (Exception $e){ + $adapter->rollBack(); + throw $e; + } + + return $this; + } + + /** + * Get user roles + * + * @param Mage_Core_Model_Abstract $user + * @return array + */ + public function getRoles(Mage_Core_Model_Abstract $user) + { + if ( !$user->getId() ) { + return array(); + } + + $table = $this->getTable('admin/role'); + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($table, array()) + ->joinLeft( + array('ar' => $table), + "(ar.role_id = {$table}.parent_id and ar.role_type = 'G')", + array('role_id')) + ->where("{$table}.user_id = :user_id"); + + $binds = array( + 'user_id' => (int) $user->getId(), + ); + + $roles = $adapter->fetchCol($select, $binds); + + if ($roles) { + return $roles; + } + + return array(); + } + + /** + * Save user roles + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + public function add(Mage_Core_Model_Abstract $user) + { + $dbh = $this->_getWriteAdapter(); + + $aRoles = $this->hasAssigned2Role($user); + if ( sizeof($aRoles) > 0 ) { + foreach($aRoles as $idx => $data){ + $conditions = array( + 'role_id = ?' => $data['role_id'], + ); + + $dbh->delete($this->getTable('admin/role'), $conditions); + } + } + + if ($user->getId() > 0) { + $role = Mage::getModel('admin/role')->load($user->getRoleId()); + } else { + $role = new Varien_Object(); + $role->setTreeLevel(0); + } + + $data = new Varien_Object(array( + 'parent_id' => $user->getRoleId(), + 'tree_level' => ($role->getTreeLevel() + 1), + 'sort_order' => 0, + 'role_type' => 'U', + 'user_id' => $user->getUserId(), + 'role_name' => $user->getFirstname() + )); + + $insertData = $this->_prepareDataForTable($data, $this->getTable('admin/role')); + + $dbh->insert($this->getTable('admin/role'), $insertData); + + return $this; + } + + /** + * Delete user role + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Admin_Model_Resource_User + */ + public function deleteFromRole(Mage_Core_Model_Abstract $user) + { + if ( $user->getUserId() <= 0 ) { + return $this; + } + if ( $user->getRoleId() <= 0 ) { + return $this; + } + + $dbh = $this->_getWriteAdapter(); + + $condition = array( + 'user_id = ?' => (int) $user->getId(), + 'parent_id = ?' => (int) $user->getRoleId(), + ); + + $dbh->delete($this->getTable('admin/role'), $condition); + return $this; + } + + /** + * Check if role user exists + * + * @param Mage_Core_Model_Abstract $user + * @return array|false + */ + public function roleUserExists(Mage_Core_Model_Abstract $user) + { + if ( $user->getUserId() > 0 ) { + $roleTable = $this->getTable('admin/role'); + + $dbh = $this->_getReadAdapter(); + + $binds = array( + 'parent_id' => $user->getRoleId(), + 'user_id' => $user->getUserId(), + ); + + $select = $dbh->select()->from($roleTable) + ->where('parent_id = :parent_id') + ->where('user_id = :user_id'); + + return $dbh->fetchCol($select, $binds); + } else { + return array(); + } + } + + /** + * Check if user exists + * + * @param Mage_Core_Model_Abstract $user + * @return array|false + */ + public function userExists(Mage_Core_Model_Abstract $user) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select(); + + $binds = array( + 'username' => $user->getUsername(), + 'email' => $user->getEmail(), + 'user_id' => (int) $user->getId(), + ); + + $select->from($this->getMainTable()) + ->where('(username = :username OR email = :email)') + ->where('user_id <> :user_id'); + + return $adapter->fetchRow($select, $binds); + } + + /** + * Save user extra data + * + * @param Mage_Core_Model_Abstract $object + * @param string $data + * @return Mage_Admin_Model_Resource_User + */ + public function saveExtra($object, $data) + { + if ($object->getId()) { + $this->_getWriteAdapter()->update( + $this->getMainTable(), + array('extra' => $data), + array('user_id = ?' => (int) $object->getId()) + ); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Admin/Model/Resource/User/Collection.php b/app/code/core/Mage/Admin/Model/Resource/User/Collection.php new file mode 100755 index 00000000..232a5635 --- /dev/null +++ b/app/code/core/Mage/Admin/Model/Resource/User/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Admin_Model_Resource_User_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('admin/user'); + } +} diff --git a/app/code/core/Mage/Admin/Model/Role.php b/app/code/core/Mage/Admin/Model/Role.php index 81351b01..71e2fb70 100644 --- a/app/code/core/Mage/Admin/Model/Role.php +++ b/app/code/core/Mage/Admin/Model/Role.php @@ -20,10 +20,32 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Admin Role Model + * + * @method Mage_Admin_Model_Resource_Role _getResource() + * @method Mage_Admin_Model_Resource_Role getResource() + * @method int getParentId() + * @method Mage_Admin_Model_Role setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Admin_Model_Role setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Admin_Model_Role setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Admin_Model_Role setRoleType(string $value) + * @method int getUserId() + * @method Mage_Admin_Model_Role setUserId(int $value) + * @method string getRoleName() + * @method Mage_Admin_Model_Role setRoleName(string $value) + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ class Mage_Admin_Model_Role extends Mage_Core_Model_Abstract { protected function _construct() diff --git a/app/code/core/Mage/Admin/Model/Roles.php b/app/code/core/Mage/Admin/Model/Roles.php index b6d2c29d..dfdff48f 100644 --- a/app/code/core/Mage/Admin/Model/Roles.php +++ b/app/code/core/Mage/Admin/Model/Roles.php @@ -20,10 +20,32 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Admin Roles Model + * + * @method Mage_Admin_Model_Resource_Roles _getResource() + * @method Mage_Admin_Model_Resource_Roles getResource() + * @method int getParentId() + * @method Mage_Admin_Model_Roles setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Admin_Model_Roles setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Admin_Model_Roles setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Admin_Model_Roles setRoleType(string $value) + * @method int getUserId() + * @method Mage_Admin_Model_Roles setUserId(int $value) + * @method string getRoleName() + * @method Mage_Admin_Model_Roles setRoleName(string $value) + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ class Mage_Admin_Model_Roles extends Mage_Core_Model_Abstract { /** @@ -31,43 +53,88 @@ class Mage_Admin_Model_Roles extends Mage_Core_Model_Abstract */ protected $_eventPrefix = 'admin_roles'; + /** + * Constructor + */ protected function _construct() { $this->_init('admin/roles'); } + /** + * Update object into database + * + * @return Mage_Admin_Model_Roles + */ public function update() { $this->getResource()->update($this); return $this; } + /** + * Retrieve users collection + * + * @return Mage_Admin_Model_Resource_Roles_User_Collection + */ public function getUsersCollection() { return Mage::getResourceModel('admin/roles_user_collection'); } + /** + * Return tree of acl resources + * + * @return array|null|Varien_Simplexml_Element + */ public function getResourcesTree() { return $this->_buildResourcesArray(null, null, null, null, true); } + /** + * Return list of acl resources + * + * @return array|null|Varien_Simplexml_Element + */ public function getResourcesList() { return $this->_buildResourcesArray(); } + /** + * Return list of acl resources in 2D format + * + * @return array|null|Varien_Simplexml_Element + */ public function getResourcesList2D() { return $this->_buildResourcesArray(null, null, null, true); } + /** + * Return users for role + * + * @return array|false + */ public function getRoleUsers() { return $this->getResource()->getRoleUsers($this); } - protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $parentName=null, $level=0, $represent2Darray=null, $rawNodes = false, $module = 'adminhtml') + /** + * Build resources array process + * + * @param null|Varien_Simplexml_Element $resource + * @param null $parentName + * @param int $level + * @param null $represent2Darray + * @param bool $rawNodes + * @param string $module + * @return array|null|Varien_Simplexml_Element + */ + protected function _buildResourcesArray(Varien_Simplexml_Element $resource = null, + $parentName = null, $level = 0, $represent2Darray = null, $rawNodes = false, $module = 'adminhtml') { static $result; if (is_null($resource)) { @@ -76,8 +143,8 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $level = -1; } else { $resourceName = $parentName; - if ($resource->getName()!='title' && $resource->getName()!='sort_order' && $resource->getName() != 'children') { - $resourceName = (is_null($parentName) ? '' : $parentName.'/').$resource->getName(); + if (!in_array($resource->getName(), array('title', 'sort_order', 'children', 'disabled'))) { + $resourceName = (is_null($parentName) ? '' : $parentName . '/') . $resource->getName(); //assigning module for its' children nodes if ($resource->getAttribute('module')) { @@ -98,22 +165,20 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, } } + //check children and run recursion if they exists $children = $resource->children(); - if (empty($children)) { - if ($rawNodes) { - return $resource; - } else { - return $result; + foreach ($children as $key => $child) { + if (1 == $child->disabled) { + $resource->{$key} = null; + continue; } + $this->_buildResourcesArray($child, $resourceName, $level + 1, $represent2Darray, $rawNodes, $module); } - foreach ($children as $child) { - $this->_buildResourcesArray($child, $resourceName, $level+1, $represent2Darray, $rawNodes, $module); - } + if ($rawNodes) { return $resource; } else { return $result; } } - } diff --git a/app/code/core/Mage/Admin/Model/Rules.php b/app/code/core/Mage/Admin/Model/Rules.php index 4d492921..62eeffd1 100644 --- a/app/code/core/Mage/Admin/Model/Rules.php +++ b/app/code/core/Mage/Admin/Model/Rules.php @@ -20,10 +20,32 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Admin Rules Model + * + * @method Mage_Admin_Model_Resource_Rules _getResource() + * @method Mage_Admin_Model_Resource_Rules getResource() + * @method int getRoleId() + * @method Mage_Admin_Model_Rules setRoleId(int $value) + * @method string getResourceId() + * @method Mage_Admin_Model_Rules setResourceId(string $value) + * @method string getPrivileges() + * @method Mage_Admin_Model_Rules setPrivileges(string $value) + * @method int getAssertId() + * @method Mage_Admin_Model_Rules setAssertId(int $value) + * @method string getRoleType() + * @method Mage_Admin_Model_Rules setRoleType(string $value) + * @method string getPermission() + * @method Mage_Admin_Model_Rules setPermission(string $value) + * + * @category Mage + * @package Mage_Admin + * @author Magento Core Team + */ class Mage_Admin_Model_Rules extends Mage_Core_Model_Abstract { protected function _construct() diff --git a/app/code/core/Mage/Admin/Model/Session.php b/app/code/core/Mage/Admin/Model/Session.php index 7cbebe11..37f79a8f 100644 --- a/app/code/core/Mage/Admin/Model/Session.php +++ b/app/code/core/Mage/Admin/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -86,10 +86,11 @@ public function login($username, $password, $request = null) } try { - /* @var $user Mage_Admin_Model_User */ + /** @var $user Mage_Admin_Model_User */ $user = Mage::getModel('admin/user'); $user->login($username, $password); if ($user->getId()) { + $this->renewSession(); if (Mage::getSingleton('adminhtml/url')->useSecretKey()) { Mage::getSingleton('adminhtml/url')->renewSecretUrls(); @@ -97,18 +98,19 @@ public function login($username, $password, $request = null) $this->setIsFirstPageAfterLogin(true); $this->setUser($user); $this->setAcl(Mage::getResourceModel('admin/acl')->loadAcl()); - if ($requestUri = $this->_getRequestUri($request)) { - Mage::dispatchEvent('admin_session_user_login_success', array('user'=>$user)); + + $requestUri = $this->_getRequestUri($request); + if ($requestUri) { + Mage::dispatchEvent('admin_session_user_login_success', array('user' => $user)); header('Location: ' . $requestUri); exit; } + } else { + Mage::throwException(Mage::helper('adminhtml')->__('Invalid User Name or Password.')); } - else { - Mage::throwException(Mage::helper('adminhtml')->__('Invalid Username or Password.')); - } - } - catch (Mage_Core_Exception $e) { - Mage::dispatchEvent('admin_session_user_login_failed', array('user_name'=>$username, 'exception' => $e)); + } catch (Mage_Core_Exception $e) { + Mage::dispatchEvent('admin_session_user_login_failed', + array('user_name' => $username, 'exception' => $e)); if ($request && !$request->getParam('messageSent')) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); $request->setParam('messageSent', true); @@ -124,7 +126,7 @@ public function login($username, $password, $request = null) * @param Mage_Admin_Model_User $user * @return Mage_Admin_Model_Session */ - public function refreshAcl($user=null) + public function refreshAcl($user = null) { if (is_null($user)) { $user = $this->getUser(); @@ -152,14 +154,14 @@ public function refreshAcl($user=null) * @param string $privilege * @return boolean */ - public function isAllowed($resource, $privilege=null) + public function isAllowed($resource, $privilege = null) { $user = $this->getUser(); $acl = $this->getAcl(); if ($user && $acl) { if (!preg_match('/^admin/', $resource)) { - $resource = 'admin/'.$resource; + $resource = 'admin/' . $resource; } try { diff --git a/app/code/core/Mage/Admin/Model/User.php b/app/code/core/Mage/Admin/Model/User.php index 6eecf223..34d8b355 100644 --- a/app/code/core/Mage/Admin/Model/User.php +++ b/app/code/core/Mage/Admin/Model/User.php @@ -20,35 +20,81 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Admin user model * + * @method Mage_Admin_Model_Resource_User _getResource() + * @method Mage_Admin_Model_Resource_User getResource() + * @method string getFirstname() + * @method Mage_Admin_Model_User setFirstname(string $value) + * @method string getLastname() + * @method Mage_Admin_Model_User setLastname(string $value) + * @method string getEmail() + * @method Mage_Admin_Model_User setEmail(string $value) + * @method string getUsername() + * @method Mage_Admin_Model_User setUsername(string $value) + * @method string getPassword() + * @method Mage_Admin_Model_User setPassword(string $value) + * @method string getCreated() + * @method Mage_Admin_Model_User setCreated(string $value) + * @method string getModified() + * @method Mage_Admin_Model_User setModified(string $value) + * @method string getLogdate() + * @method Mage_Admin_Model_User setLogdate(string $value) + * @method int getLognum() + * @method Mage_Admin_Model_User setLognum(int $value) + * @method int getReloadAclFlag() + * @method Mage_Admin_Model_User setReloadAclFlag(int $value) + * @method int getIsActive() + * @method Mage_Admin_Model_User setIsActive(int $value) + * @method string getExtra() + * @method Mage_Admin_Model_User setExtra(string $value) + * * @category Mage * @package Mage_Admin * @author Magento Core Team */ class Mage_Admin_Model_User extends Mage_Core_Model_Abstract { + /** + * Configuration paths for email templates and identities + */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'admin/emails/forgot_email_template'; const XML_PATH_FORGOT_EMAIL_IDENTITY = 'admin/emails/forgot_email_identity'; const XML_PATH_STARTUP_PAGE = 'admin/startup/page'; + + /** + * Minimum length of admin password + */ const MIN_PASSWORD_LENGTH = 7; + /** + * Model event prefix + * + * @var string + */ protected $_eventPrefix = 'admin_user'; /** + * Admin role + * * @var Mage_Admin_Model_Roles */ protected $_role; + /** + * Available resources flag + * + * @var boolean + */ protected $_hasAvailableResources = true; /** - * Varien constructor + * Initialize user model */ protected function _construct() { @@ -78,13 +124,15 @@ protected function _beforeSave() $data['username'] = $this->getUsername(); } - if ($this->getNewPassword()) { // change password + if ($this->getNewPassword()) { + // Change password $data['password'] = $this->_getEncodedPassword($this->getNewPassword()); - } elseif ($this->getPassword() && $this->getPassword() != $this->getOrigData('password')) { // new user password + } elseif ($this->getPassword() && $this->getPassword() != $this->getOrigData('password')) { + // New user password $data['password'] = $this->_getEncodedPassword($this->getPassword()); } - if ( !is_null($this->getIsActive()) ) { + if (!is_null($this->getIsActive())) { $data['is_active'] = intval($this->getIsActive()); } @@ -119,6 +167,11 @@ public function saveRelations() return $this; } + /** + * Retrieve user roles + * + * @return array + */ public function getRoles() { return $this->_getResource()->getRoles($this); @@ -141,30 +194,55 @@ public function getRole() return $this->_role; } + /** + * Unassign user from his current role + * + * @return Mage_Admin_Model_User + */ public function deleteFromRole() { $this->_getResource()->deleteFromRole($this); return $this; } + /** + * Check if such combination role/user exists + * + * @return boolean + */ public function roleUserExists() { $result = $this->_getResource()->roleUserExists($this); - return ( is_array($result) && count($result) > 0 ) ? true : false; + return (is_array($result) && count($result) > 0) ? true : false; } + /** + * Assign user to role + * + * @return Mage_Admin_Model_User + */ public function add() { $this->_getResource()->add($this); return $this; } + /** + * Check if user exists based on its id, username and email + * + * @return boolean + */ public function userExists() { $result = $this->_getResource()->userExists($this); - return ( is_array($result) && count($result) > 0 ) ? true : false; + return (is_array($result) && count($result) > 0) ? true : false; } + /** + * Retrieve admin user collection + * + * @return Mage_Admin_Model_Resource_User_Collection + */ public function getCollection() { return Mage::getResourceModel('admin/user_collection'); } @@ -173,32 +251,54 @@ public function getCollection() { * Send email with new user password * * @return Mage_Admin_Model_User + * @deprecated deprecated since version 1.6.1.0 */ public function sendNewPasswordEmail() { - $translate = Mage::getSingleton('core/translate'); - /* @var $translate Mage_Core_Model_Translate */ - $translate->setTranslateInline(false); - - Mage::getModel('core/email_template') - ->setDesignConfig(array('area' => 'adminhtml', 'store' => $this->getStoreId())) - ->sendTransactional( - Mage::getStoreConfig(self::XML_PATH_FORGOT_EMAIL_TEMPLATE), - Mage::getStoreConfig(self::XML_PATH_FORGOT_EMAIL_IDENTITY), - $this->getEmail(), - $this->getName(), - array('user' => $this, 'password' => $this->getPlainPassword())); + return $this; + } - $translate->setTranslateInline(true); + /** + * Send email with reset password confirmation link + * + * @return Mage_Admin_Model_User + */ + public function sendPasswordResetConfirmationEmail() + { + /** @var $mailer Mage_Core_Model_Email_Template_Mailer */ + $mailer = Mage::getModel('core/email_template_mailer'); + $emailInfo = Mage::getModel('core/email_info'); + $emailInfo->addTo($this->getEmail(), $this->getName()); + $mailer->addEmailInfo($emailInfo); + + // Set all required params and send emails + $mailer->setSender(Mage::getStoreConfig(self::XML_PATH_FORGOT_EMAIL_IDENTITY)); + $mailer->setStoreId(0); + $mailer->setTemplateId(Mage::getStoreConfig(self::XML_PATH_FORGOT_EMAIL_TEMPLATE)); + $mailer->setTemplateParams(array( + 'user' => $this + )); + $mailer->send(); return $this; } - public function getName($separator=' ') + /** + * Retrieve user name + * + * @param string $separator + * @return string + */ + public function getName($separator = ' ') { return $this->getFirstname() . $separator . $this->getLastname(); } + /** + * Retrieve user identifier + * + * @return mixed + */ public function getId() { return $this->getUserId(); @@ -228,8 +328,12 @@ public function authenticate($username, $password) $result = false; try { + Mage::dispatchEvent('admin_user_authenticate_before', array( + 'username' => $username, + 'user' => $this + )); $this->loadByUsername($username); - $sensitive = ($config) ? $username==$this->getUsername() : true; + $sensitive = ($config) ? $username == $this->getUsername() : true; if ($sensitive && $this->getId() && Mage::helper('core')->validateHash($password, $this->getPassword())) { if ($this->getIsActive() != '1') { @@ -274,6 +378,11 @@ public function login($username, $password) return $this; } + /** + * Reload current user + * + * @return Mage_Admin_Model_User + */ public function reload() { $id = $this->getId(); @@ -282,20 +391,38 @@ public function reload() return $this; } + /** + * Load user by its username + * + * @param string $username + * @return Mage_Admin_Model_User + */ public function loadByUsername($username) { $this->setData($this->getResource()->loadByUsername($username)); return $this; } + /** + * Check if user is assigned to any role + * + * @param int|Mage_Core_Admin_Model_User $user + * @return null|boolean|array + */ public function hasAssigned2Role($user) { return $this->getResource()->hasAssigned2Role($user); } - protected function _getEncodedPassword($pwd) + /** + * Retrieve encoded password + * + * @param string $password + * @return string + */ + protected function _getEncodedPassword($password) { - return Mage::helper('core')->getHash($pwd, 2); + return Mage::helper('core')->getHash($password, 2); } /** @@ -306,18 +433,18 @@ protected function _getEncodedPassword($pwd) * @param integer $level * @return string */ - public function findFirstAvailableMenu($parent=null, $path='', $level=0) + public function findFirstAvailableMenu($parent = null, $path = '', $level = 0) { if ($parent == null) { $parent = Mage::getSingleton('admin/config')->getAdminhtmlConfig()->getNode('menu'); } - foreach ($parent->children() as $childName=>$child) { + foreach ($parent->children() as $childName => $child) { $aclResource = 'admin/' . $path . $childName; if (Mage::getSingleton('admin/session')->isAllowed($aclResource)) { if (!$child->children) { return (string)$child->action; } else if ($child->children) { - $action = $this->findFirstAvailableMenu($child->children, $path . $childName . '/', $level+1); + $action = $this->findFirstAvailableMenu($child->children, $path . $childName . '/', $level + 1); return $action ? $action : (string)$child->action; } } @@ -398,7 +525,9 @@ public function validate() $errors[] = Mage::helper('adminhtml')->__('Password must be at least of %d characters.', self::MIN_PASSWORD_LENGTH); } - if (!preg_match('/[a-z]/iu', $this->getNewPassword()) || !preg_match('/[0-9]/u', $this->getNewPassword())) { + if (!preg_match('/[a-z]/iu', $this->getNewPassword()) + || !preg_match('/[0-9]/u', $this->getNewPassword()) + ) { $errors[] = Mage::helper('adminhtml')->__('Password must include both numeric and alphabetic characters.'); } @@ -417,4 +546,55 @@ public function validate() return $errors; } + /** + * Change reset password link token + * + * Stores new reset password link token and its creation time + * + * @param string $newResetPasswordLinkToken + * @return Mage_Admin_Model_User + * @throws Mage_Core_Exception + */ + public function changeResetPasswordLinkToken($newResetPasswordLinkToken) { + if (!is_string($newResetPasswordLinkToken) || empty($newResetPasswordLinkToken)) { + throw Mage::exception('Mage_Core', Mage::helper('adminhtml')->__('Invalid password reset token.')); + } + $this->setRpToken($newResetPasswordLinkToken); + $currentDate = Varien_Date::now(); + $this->setRpTokenCreatedAt($currentDate); + + return $this; + } + + /** + * Check if current reset password link token is expired + * + * @return boolean + */ + public function isResetPasswordLinkTokenExpired() + { + $resetPasswordLinkToken = $this->getRpToken(); + $resetPasswordLinkTokenCreatedAt = $this->getRpTokenCreatedAt(); + + if (empty($resetPasswordLinkToken) || empty($resetPasswordLinkTokenCreatedAt)) { + return true; + } + + $tokenExpirationPeriod = Mage::helper('admin')->getResetPasswordLinkExpirationPeriod(); + + $currentDate = Varien_Date::now(); + $currentTimestamp = Varien_Date::toTimestamp($currentDate); + $tokenTimestamp = Varien_Date::toTimestamp($resetPasswordLinkTokenCreatedAt); + if ($tokenTimestamp > $currentTimestamp) { + return true; + } + + $dayDifference = floor(($currentTimestamp - $tokenTimestamp) / (24 * 60 * 60)); + if ($dayDifference >= $tokenExpirationPeriod) { + return true; + } + + return false; + } + } diff --git a/app/code/core/Mage/Admin/data/admin_setup/data-install-1.6.0.0.php b/app/code/core/Mage/Admin/data/admin_setup/data-install-1.6.0.0.php new file mode 100644 index 00000000..d2c448b4 --- /dev/null +++ b/app/code/core/Mage/Admin/data/admin_setup/data-install-1.6.0.0.php @@ -0,0 +1,48 @@ +setData(array( + 'parent_id' => 0, + 'tree_level' => 1, + 'sort_order' => 1, + 'role_type' => 'G', + 'user_id' => 0, + 'role_name' => 'Administrators' + )) + ->save(); + +Mage::getModel('admin/rules')->setData(array( + 'role_id' => $admGroupRole->getId(), + 'resource_id' => 'all', + 'privileges' => null, + 'assert_id' => 0, + 'role_type' => 'G', + 'permission' => 'allow' + )) + ->save(); diff --git a/app/code/core/Mage/Admin/etc/config.xml b/app/code/core/Mage/Admin/etc/config.xml index f32cec09..db1f823a 100644 --- a/app/code/core/Mage/Admin/etc/config.xml +++ b/app/code/core/Mage/Admin/etc/config.xml @@ -21,55 +21,68 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - - - 0.7.2 - - - - - - - Mage_Admin_Model - admin_mysql4 - - - Mage_Admin_Model_Mysql4 - - admin_user
- admin_role
- admin_rule
- admin_assert
-
-
-
- - - - - Mage_Admin - - - + + + 1.6.1.0 + + + + + + Mage_Admin_Model + admin_resource + + + Mage_Admin_Model_Resource + admin_mysql4 + + + admin_user
+
+ + admin_role
+
+ + admin_rule
+
+ + admin_assert
+
+
+
+
+ + + Mage_Admin_Helper + + + + + + Mage_Admin + + + - Mage_Admin_Block + + Mage_Admin_Block + -
- +
admin_emails_forgot_email_template general + 1 - diff --git a/app/code/core/Mage/Admin/sql/admin_setup/install-1.6.0.0.php b/app/code/core/Mage/Admin/sql/admin_setup/install-1.6.0.0.php new file mode 100644 index 00000000..7f38218e --- /dev/null +++ b/app/code/core/Mage/Admin/sql/admin_setup/install-1.6.0.0.php @@ -0,0 +1,193 @@ +startSetup(); + +/** + * Create table 'admin/assert' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('admin/assert')) + ->addColumn('assert_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Assert ID') + ->addColumn('assert_type', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + 'nullable' => true, + 'default' => null, + ), 'Assert Type') + ->addColumn('assert_data', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Assert Data') + ->setComment('Admin Assert Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'admin/role' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('admin/role')) + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Role ID') + ->addColumn('parent_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Parent Role ID') + ->addColumn('tree_level', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Role Tree Level') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Role Sort Order') + ->addColumn('role_type', Varien_Db_Ddl_Table::TYPE_TEXT, 1, array( + 'nullable' => false, + 'default' => '0', + ), 'Role Type') + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'User ID') + ->addColumn('role_name', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => true, + 'default' => null, + ), 'Role Name') + ->addIndex($installer->getIdxName('admin/role', array('parent_id', 'sort_order')), + array('parent_id', 'sort_order')) + ->addIndex($installer->getIdxName('admin/role', array('tree_level')), + array('tree_level')) + ->setComment('Admin Role Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'admin/rule' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('admin/rule')) + ->addColumn('rule_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Rule ID') + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Role ID') + ->addColumn('resource_id', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Resource ID') + ->addColumn('privileges', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + 'nullable' => true, + ), 'Privileges') + ->addColumn('assert_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Assert ID') + ->addColumn('role_type', Varien_Db_Ddl_Table::TYPE_TEXT, 1, array( + ), 'Role Type') + ->addColumn('permission', Varien_Db_Ddl_Table::TYPE_TEXT, 10, array( + ), 'Permission') + ->addIndex($installer->getIdxName('admin/rule', array('resource_id', 'role_id')), + array('resource_id', 'role_id')) + ->addIndex($installer->getIdxName('admin/rule', array('role_id', 'resource_id')), + array('role_id', 'resource_id')) + ->addForeignKey($installer->getFkName('admin/rule', 'role_id', 'admin/role', 'role_id'), + 'role_id', $installer->getTable('admin/role'), 'role_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Admin Rule Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'admin/user' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('admin/user')) + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'User ID') + ->addColumn('firstname', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + 'nullable' => true, + ), 'User First Name') + ->addColumn('lastname', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + 'nullable' => true, + ), 'User Last Name') + ->addColumn('email', Varien_Db_Ddl_Table::TYPE_TEXT, 128, array( + 'nullable' => true, + ), 'User Email') + ->addColumn('username', Varien_Db_Ddl_Table::TYPE_TEXT, 40, array( + 'nullable' => true, + ), 'User Login') + ->addColumn('password', Varien_Db_Ddl_Table::TYPE_TEXT, 40, array( + 'nullable' => true, + ), 'User Password') + ->addColumn('created', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'User Created Time') + ->addColumn('modified', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'User Modified Time') + ->addColumn('logdate', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'User Last Login Time') + ->addColumn('lognum', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'User Login Number') + ->addColumn('reload_acl_flag', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Reload ACL') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '1', + ), 'User Is Active') + ->addColumn('extra', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'User Extra Data') + ->addIndex($installer->getIdxName('admin/user', array('username'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('username'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Admin User Table'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-install-0.7.0.php index ad1f7946..9b441751 100644 --- a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.0-0.7.1.php index cae86380..83d5bbdf 100644 --- a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.1-0.7.2.php index 44c559c7..f73928ae 100644 --- a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Admin - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..53fd82a0 --- /dev/null +++ b/app/code/core/Mage/Admin/sql/admin_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,329 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$connection = $installer->getConnection()->dropForeignKey( + $installer->getTable('admin/rule'), + 'FK_ADMIN_RULE' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('admin/role'), + 'PARENT_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('admin/role'), + 'TREE_LEVEL' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('admin/rule'), + 'RESOURCE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('admin/rule'), + 'ROLE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('admin/user'), + 'UNQ_ADMIN_USER_USERNAME' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('admin/user') => array( + 'columns' => array( + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'User ID' + ), + 'firstname' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'User First Name' + ), + 'lastname' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'User Last Name' + ), + 'email' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 128, + 'comment' => 'User Email' + ), + 'username' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 40, + 'comment' => 'User Login' + ), + 'password' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'comment' => 'User Password' + ), + 'created' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'User Created Time' + ), + 'modified' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'User Modified Time' + ), + 'logdate' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'User Last Login Time' + ), + 'lognum' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'User Login Number' + ), + 'reload_acl_flag' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Reload ACL' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '1', + 'comment' => 'User Is Active' + ), + 'extra' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'User Extra Data' + ) + ), + 'comment' => 'Admin User Table' + ), + $installer->getTable('admin/role') => array( + 'columns' => array( + 'role_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Role ID' + ), + 'parent_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Parent Role ID' + ), + 'tree_level' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role Tree Level' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role Sort Order' + ), + 'role_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 1, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role Type' + ), + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'User ID' + ), + 'role_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'nullable' => false, + 'comment' => 'Role Name' + ) + ), + 'comment' => 'Admin Role Table' + ), + $installer->getTable('admin/rule') => array( + 'columns' => array( + 'rule_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Rule ID' + ), + 'role_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role ID' + ), + 'resource_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Resource ID' + ), + 'privileges' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'comment' => 'Privileges' + ), + 'assert_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Assert ID' + ), + 'role_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 1, + 'comment' => 'Role Type' + ), + 'permission' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 10, + 'comment' => 'Permission' + ) + ), + 'comment' => 'Admin Rule Table' + ), + $installer->getTable('admin/assert') => array( + 'columns' => array( + 'assert_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Assert ID' + ), + 'assert_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'nullable' => false, + 'comment' => 'Assert Type' + ), + 'assert_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Assert Data' + ) + ), + 'comment' => 'Admin Assert Table' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('admin/role'), + $installer->getIdxName('admin/role', array('parent_id', 'sort_order')), + array('parent_id', 'sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('admin/role'), + $installer->getIdxName('admin/role', array('tree_level')), + array('tree_level') +); + +$installer->getConnection()->addIndex( + $installer->getTable('admin/rule'), + $installer->getIdxName('admin/rule', array('resource_id', 'role_id')), + array('resource_id', 'role_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('admin/rule'), + $installer->getIdxName('admin/rule', array('role_id', 'resource_id')), + array('role_id', 'resource_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('admin/user'), + $installer->getIdxName( + 'admin/user', + array('username'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('username'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('admin/rule', 'role_id', 'admin/role', 'role_id'), + $installer->getTable('admin/rule'), + 'role_id', + $installer->getTable('admin/role'), + 'role_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Admin/sql/admin_setup/upgrade-1.6.0.0-1.6.1.0.php b/app/code/core/Mage/Admin/sql/admin_setup/upgrade-1.6.0.0-1.6.1.0.php new file mode 100644 index 00000000..64df2216 --- /dev/null +++ b/app/code/core/Mage/Admin/sql/admin_setup/upgrade-1.6.0.0-1.6.1.0.php @@ -0,0 +1,48 @@ +startSetup(); + +// Add reset password link token column +$installer->getConnection()->addColumn($installer->getTable('admin/user'), 'rp_token', array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 256, + 'nullable' => true, + 'default' => null, + 'comment' => 'Reset Password Link Token' +)); + +// Add reset password link token creation date column +$installer->getConnection()->addColumn($installer->getTable('admin/user'), 'rp_token_created_at', array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => true, + 'default' => null, + 'comment' => 'Reset Password Link Token Creation Date' +)); + +$installer->endSetup(); diff --git a/app/code/core/Mage/AdminNotification/Helper/Data.php b/app/code/core/Mage/AdminNotification/Helper/Data.php index 4ca99e64..4b1627c1 100644 --- a/app/code/core/Mage/AdminNotification/Helper/Data.php +++ b/app/code/core/Mage/AdminNotification/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -111,6 +111,7 @@ public function getPopupObjectUrl($withExt = false) /** * Check is readable Popup Notification Object + * @deprecated after 1.4.2.0 * * @return bool */ @@ -128,6 +129,8 @@ public function isReadablePopupObject() $this->_popupReadable = true; } } + + $curl->close(); } return $this->_popupReadable; } diff --git a/app/code/core/Mage/AdminNotification/Model/Feed.php b/app/code/core/Mage/AdminNotification/Model/Feed.php index f995cd86..baace045 100644 --- a/app/code/core/Mage/AdminNotification/Model/Feed.php +++ b/app/code/core/Mage/AdminNotification/Model/Feed.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/AdminNotification/Model/Inbox.php b/app/code/core/Mage/AdminNotification/Model/Inbox.php index 44107d55..11fac2ac 100644 --- a/app/code/core/Mage/AdminNotification/Model/Inbox.php +++ b/app/code/core/Mage/AdminNotification/Model/Inbox.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,25 @@ /** * AdminNotification Inbox model * - * @category Mage - * @package Mage_AdminNotification + * @method Mage_AdminNotification_Model_Resource_Inbox _getResource() + * @method Mage_AdminNotification_Model_Resource_Inbox getResource() + * @method int getSeverity() + * @method Mage_AdminNotification_Model_Inbox setSeverity(int $value) + * @method string getDateAdded() + * @method Mage_AdminNotification_Model_Inbox setDateAdded(string $value) + * @method string getTitle() + * @method Mage_AdminNotification_Model_Inbox setTitle(string $value) + * @method string getDescription() + * @method Mage_AdminNotification_Model_Inbox setDescription(string $value) + * @method string getUrl() + * @method Mage_AdminNotification_Model_Inbox setUrl(string $value) + * @method int getIsRead() + * @method Mage_AdminNotification_Model_Inbox setIsRead(int $value) + * @method int getIsRemove() + * @method Mage_AdminNotification_Model_Inbox setIsRemove(int $value) + * + * @category Mage + * @package Mage_AdminNotification * @author Magento Core Team */ class Mage_AdminNotification_Model_Inbox extends Mage_Core_Model_Abstract @@ -98,6 +115,96 @@ public function getNoticeStatus() */ public function parse(array $data) { - return $this->getResource()->parse($this, $data);; + return $this->getResource()->parse($this, $data); + } + + /** + * Add new message + * + * @param int $severity + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function add($severity, $title, $description, $url = '', $isInternal = true) + { + if (!$this->getSeverities($severity)) { + Mage::throwException($this->__('Wrong message type')); + } + if (is_array($description)) { + $description = '
  • ' . implode('
  • ', $description) . '
'; + } + $date = date('Y-m-d H:i:s'); + $this->parse(array(array( + 'severity' => $severity, + 'date_added' => $date, + 'title' => $title, + 'description' => $description, + 'url' => $url, + 'internal' => $isInternal + ))); + return $this; + } + + /** + * Add critical severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addCritical($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_CRITICAL, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add major severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addMajor($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_MAJOR, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add minor severity message + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addMinor($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_MINOR, $title, $description, $url, $isInternal); + return $this; + } + + /** + * Add notice + * + * @param string $title + * @param string|array $description + * @param string $url + * @param bool $isInternal + * @return Mage_AdminNotification_Model_Inbox + */ + public function addNotice($title, $description, $url = '', $isInternal = true) + { + $this->add(self::SEVERITY_NOTICE, $title, $description, $url, $isInternal); + return $this; } } diff --git a/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox.php b/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox.php index dbb8ed5d..331a04bb 100644 --- a/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox.php +++ b/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,65 +28,10 @@ /** * AdminNotification Inbox model * - * @category Mage - * @package Mage_AdminNotification + * @category Mage + * @package Mage_AdminNotification * @author Magento Core Team */ -class Mage_AdminNotification_Model_Mysql4_Inbox extends Mage_Core_Model_Mysql4_Abstract +class Mage_AdminNotification_Model_Mysql4_Inbox extends Mage_AdminNotification_Model_Resource_Inbox { - protected function _construct() - { - $this->_init('adminnotification/inbox', 'notification_id'); - } - - public function loadLatestNotice(Mage_AdminNotification_Model_Inbox $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->order($this->getIdFieldName() . ' desc') - ->where('is_read <> 1') - ->where('is_remove <> 1') - ->limit(1); - $data = $this->_getReadAdapter()->fetchRow($select); - - if ($data) { - $object->setData($data); - } - - $this->_afterLoad($object); - - return $this; - } - - public function getNoticeStatus(Mage_AdminNotification_Model_Inbox $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable(), array( - 'severity' => 'severity', - 'count_notice' => 'COUNT(' . $this->getIdFieldName() . ')')) - ->group('severity') - ->where('is_remove=?', 0) - ->where('is_read=?', 0); - $return = array(); - $rowSet = $this->_getReadAdapter()->fetchAll($select); - foreach ($rowSet as $row) { - $return[$row['severity']] = $row['count_notice']; - } - return $return; - } - - public function parse(Mage_AdminNotification_Model_Inbox $object, array $data) - { - $write = $this->_getWriteAdapter(); - foreach ($data as $item) { - $select = $write->select() - ->from($this->getMainTable()) - ->where('url=?', $item['url']); - $row = $write->fetchRow($select); - - if (!$row) { - $write->insert($this->getMainTable(), $item); - } - } - } } diff --git a/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox/Collection.php b/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox/Collection.php index e16c6ee6..c87bdf2d 100644 --- a/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox/Collection.php +++ b/app/code/core/Mage/AdminNotification/Model/Mysql4/Inbox/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,21 +28,11 @@ /** * AdminNotification Inbox model * - * @category Mage - * @package Mage_AdminNotification + * @category Mage + * @package Mage_AdminNotification * @author Magento Core Team */ -class Mage_AdminNotification_Model_Mysql4_Inbox_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_AdminNotification_Model_Mysql4_Inbox_Collection + extends Mage_AdminNotification_Model_Resource_Inbox_Collection { - protected function _construct() - { - $this->_init('adminnotification/inbox'); - } - - public function addRemoveFilter() - { - $this->getSelect() - ->where('is_remove=?', 0); - return $this; - } } diff --git a/app/code/core/Mage/AdminNotification/Model/Observer.php b/app/code/core/Mage/AdminNotification/Model/Observer.php index 339d5d2f..467c7e0f 100644 --- a/app/code/core/Mage/AdminNotification/Model/Observer.php +++ b/app/code/core/Mage/AdminNotification/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/AdminNotification/Model/Resource/Inbox.php b/app/code/core/Mage/AdminNotification/Model/Resource/Inbox.php new file mode 100755 index 00000000..5c75cec1 --- /dev/null +++ b/app/code/core/Mage/AdminNotification/Model/Resource/Inbox.php @@ -0,0 +1,124 @@ + + */ +class Mage_AdminNotification_Model_Resource_Inbox extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * AdminNotification Resource initialization + * + */ + protected function _construct() + { + $this->_init('adminnotification/inbox', 'notification_id'); + } + + /** + * Load latest notice + * + * @param Mage_AdminNotification_Model_Inbox $object + * @return Mage_AdminNotification_Model_Resource_Inbox + */ + public function loadLatestNotice(Mage_AdminNotification_Model_Inbox $object) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable()) + ->order($this->getIdFieldName() . ' DESC') + ->where('is_read != 1') + ->where('is_remove != 1') + ->limit(1); + $data = $adapter->fetchRow($select); + + if ($data) { + $object->setData($data); + } + + $this->_afterLoad($object); + + return $this; + } + + /** + * Get notifications grouped by severity + * + * @param Mage_AdminNotification_Model_Inbox $object + * @return array + */ + public function getNoticeStatus(Mage_AdminNotification_Model_Inbox $object) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), array( + 'severity' => 'severity', + 'count_notice' => new Zend_Db_Expr('COUNT(' . $this->getIdFieldName() . ')'))) + ->group('severity') + ->where('is_remove=?', 0) + ->where('is_read=?', 0); + $return = $adapter->fetchPairs($select); + return $return; + } + + /** + * Save notifications (if not exists) + * + * @param Mage_AdminNotification_Model_Inbox $object + * @param array $data + */ + public function parse(Mage_AdminNotification_Model_Inbox $object, array $data) + { + $adapter = $this->_getWriteAdapter(); + foreach ($data as $item) { + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('title = ?', $item['title']); + + if (empty($item['url'])) { + $select->where('url IS NULL'); + } else { + $select->where('url = ?', $item['url']); + } + + if (isset($item['internal'])) { + $row = false; + unset($item['internal']); + } else { + $row = $adapter->fetchRow($select); + } + + if (!$row) { + $adapter->insert($this->getMainTable(), $item); + } + } + } +} diff --git a/app/code/core/Mage/AdminNotification/Model/Resource/Inbox/Collection.php b/app/code/core/Mage/AdminNotification/Model/Resource/Inbox/Collection.php new file mode 100755 index 00000000..2fc13c2b --- /dev/null +++ b/app/code/core/Mage/AdminNotification/Model/Resource/Inbox/Collection.php @@ -0,0 +1,57 @@ + + */ +class Mage_AdminNotification_Model_Resource_Inbox_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('adminnotification/inbox'); + } + + /** + * Add remove filter + * + * @return Mage_AdminNotification_Model_Resource_Inbox_Collection + */ + public function addRemoveFilter() + { + $this->getSelect() + ->where('is_remove=?', 0); + return $this; + } +} diff --git a/app/code/core/Mage/AdminNotification/etc/adminhtml.xml b/app/code/core/Mage/AdminNotification/etc/adminhtml.xml index 98f03d6b..e120c50a 100644 --- a/app/code/core/Mage/AdminNotification/etc/adminhtml.xml +++ b/app/code/core/Mage/AdminNotification/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/AdminNotification/etc/config.xml b/app/code/core/Mage/AdminNotification/etc/config.xml index d7f1dde0..57447dfe 100644 --- a/app/code/core/Mage/AdminNotification/etc/config.xml +++ b/app/code/core/Mage/AdminNotification/etc/config.xml @@ -21,31 +21,31 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 1.0.0 + 1.6.0.0 - Mage_AdminNotification_Model - adminnotification_mysql4 + adminnotification_resource - - Mage_AdminNotification_Model_Mysql4 + + Mage_AdminNotification_Model_Resource + adminnotification_mysql4 adminnotification_inbox
-
+
@@ -59,13 +59,7 @@ - - - Mage_AdminNotification_Block - -
- @@ -94,7 +88,6 @@
- diff --git a/app/code/core/Mage/AdminNotification/etc/system.xml b/app/code/core/Mage/AdminNotification/etc/system.xml index 0d63ce4e..fe6b6cd8 100644 --- a/app/code/core/Mage/AdminNotification/etc/system.xml +++ b/app/code/core/Mage/AdminNotification/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/install-1.6.0.0.php b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/install-1.6.0.0.php new file mode 100644 index 00000000..48ec4b51 --- /dev/null +++ b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/install-1.6.0.0.php @@ -0,0 +1,83 @@ + + */ +$installer = $this; +/* @var $installer Mage_Core_Model_Resource_Setup */ + +$installer->startSetup(); +/** + * Create table 'adminnotification/inbox' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('adminnotification/inbox')) + ->addColumn('notification_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Notification id') + ->addColumn('severity', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Problem type') + ->addColumn('date_added', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Create date') + ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Title') + ->addColumn('description', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Description') + ->addColumn('url', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Url') + ->addColumn('is_read', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Flag if notification read') + ->addColumn('is_remove', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Flag if notification might be removed') + ->addIndex($installer->getIdxName('adminnotification/inbox', array('severity')), + array('severity')) + ->addIndex($installer->getIdxName('adminnotification/inbox', array('is_read')), + array('is_read')) + ->addIndex($installer->getIdxName('adminnotification/inbox', array('is_remove')), + array('is_remove')) + ->setComment('Adminnotification Inbox'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-install-1.0.0.php b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-install-1.0.0.php index 403a68fd..b1f71c79 100644 --- a/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-install-1.0.0.php +++ b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-install-1.0.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_AdminNotification - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..041075d3 --- /dev/null +++ b/app/code/core/Mage/AdminNotification/sql/adminnotification_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,135 @@ +startSetup(); + +/** + * Drop indexes + */ +$connection = $installer->getConnection()->dropIndex( + $installer->getTable('adminnotification/inbox'), + 'IDX_SEVERITY' +); + +$connection = $installer->getConnection()->dropIndex( + $installer->getTable('adminnotification/inbox'), + 'IDX_IS_READ' +); + +$connection = $installer->getConnection()->dropIndex( + $installer->getTable('adminnotification/inbox'), + 'IDX_IS_REMOVE' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('adminnotification/inbox') => array( + 'columns' => array( + 'notification_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Notification id' + ), + 'severity' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Problem type' + ), + 'date_added' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Create date' + ), + 'title' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Title' + ), + 'description' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Description' + ), + 'url' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Url' + ), + 'is_read' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Flag if notification read' + ), + 'is_remove' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Flag if notification might be removed' + ) + ), + 'comment' => 'Adminnotification Inbox' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$connection = $installer->getConnection()->addIndex( + $installer->getTable('adminnotification/inbox'), + $installer->getIdxName('adminnotification/inbox', array('severity')), + array('severity') +); + +$connection = $installer->getConnection()->addIndex( + $installer->getTable('adminnotification/inbox'), + $installer->getIdxName('adminnotification/inbox', array('is_read')), + array('is_read') +); + +$connection = $installer->getConnection()->addIndex( + $installer->getTable('adminnotification/inbox'), + $installer->getIdxName('adminnotification/inbox', array('is_remove')), + array('is_remove') +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Adminhtml/Block/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Abstract.php index 82c188e3..7b6d7af6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Buttons.php b/app/code/core/Mage/Adminhtml/Block/Api/Buttons.php index e0db50ba..084c3f4d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Buttons.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Buttons.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Editroles.php b/app/code/core/Mage/Adminhtml/Block/Api/Editroles.php index 61c4362d..ecabda9d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Editroles.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Editroles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Edituser.php b/app/code/core/Mage/Adminhtml/Block/Api/Edituser.php index 5ec0db4f..894b3a5d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Edituser.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Edituser.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Grid/Role.php b/app/code/core/Mage/Adminhtml/Block/Api/Grid/Role.php index 6a2c444a..e7463541 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Grid/Role.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Grid/Role.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Role.php b/app/code/core/Mage/Adminhtml/Block/Api/Role.php index 93cd907c..b36a9294 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Role.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Role.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Role/Grid/User.php b/app/code/core/Mage/Adminhtml/Block/Api/Role/Grid/User.php index 0cdc3aa6..d6355a6b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Role/Grid/User.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Role/Grid/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Roles.php b/app/code/core/Mage/Adminhtml/Block/Api/Roles.php index 754989d2..ef7f1d94 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Roles.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Roles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Roleinfo.php b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Roleinfo.php index 190bed8b..54d9b594 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Roleinfo.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Roleinfo.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesedit.php b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesedit.php index abd256b0..3bd311e2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesedit.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesedit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,7 +38,9 @@ public function __construct() { $selrids = array(); foreach ($rules_set->getItems() as $item) { - if (array_key_exists(strtolower($item->getResource_id()), $resources) && $item->getPermission() == 'allow') { + if (array_key_exists(strtolower($item->getResource_id()), $resources) + && $item->getApiPermission() == 'allow') + { $resources[$item->getResource_id()]['checked'] = true; array_push($selrids, $item->getResource_id()); } diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesusers.php b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesusers.php index 45f26e90..4a0543dc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesusers.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Rolesusers.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Userroles.php b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Userroles.php index 496fa7de..2bba95b0 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Tab/Userroles.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Tab/Userroles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_Api_Tab_Userroles extends Mage_Adminhtml_Block_Widget_Tabs diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User.php b/app/code/core/Mage/Adminhtml/Block/Api/User.php index af031eab..28acc596 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -42,4 +42,14 @@ public function __construct() parent::__construct(); } + /** + * Prepare output HTML + * + * @return string + */ + protected function _toHtml() + { + Mage::dispatchEvent('api_user_html_before', array('block' => $this)); + return parent::_toHtml(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit.php index d4cb43ef..65a41b8d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Form.php index 07c89c95..0b379121 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Main.php index d72b01c9..1cbbcd2b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Roles.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Roles.php index 38ca0419..818c9df7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Roles.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tab/Roles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tabs.php index a380a7f4..eba41abd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/User/Grid.php b/app/code/core/Mage/Adminhtml/Block/Api/User/Grid.php index 80569613..bc0c468e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/User/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/User/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Api/Users.php b/app/code/core/Mage/Adminhtml/Block/Api/Users.php index b53ed607..9eb1a2e3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Api/Users.php +++ b/app/code/core/Mage/Adminhtml/Block/Api/Users.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Backup.php b/app/code/core/Mage/Adminhtml/Block/Backup.php index c4aa363f..2b6ea30a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Backup.php +++ b/app/code/core/Mage/Adminhtml/Block/Backup.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,14 +31,14 @@ * @package Mage_Adminhtml * @author Magento Core Team */ - class Mage_Adminhtml_Block_Backup extends Mage_Adminhtml_Block_Template { - public function __construct() - { - parent::__construct(); - $this->setTemplate('backup/list.phtml'); - } + /** + * Block's template + * + * @var string + */ + protected $_template = 'backup/list.phtml'; protected function _prepareLayout() { @@ -46,14 +46,32 @@ protected function _prepareLayout() $this->setChild('createButton', $this->getLayout()->createBlock('adminhtml/widget_button') ->setData(array( - 'label' => Mage::helper('backup')->__('Create Backup'), - 'onclick' => "window.location.href='" . $this->getUrl('*/*/create') . "'", + 'label' => Mage::helper('backup')->__('Database Backup'), + 'onclick' => "return backup.backup('" . Mage_Backup_Helper_Data::TYPE_DB . "')", 'class' => 'task' )) ); + $this->setChild('createSnapshotButton', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('backup')->__('System Backup'), + 'onclick' => "return backup.backup('" . Mage_Backup_Helper_Data::TYPE_SYSTEM_SNAPSHOT . "')", + 'class' => '' + )) + ); + $this->setChild('createMediaBackupButton', + $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'label' => Mage::helper('backup')->__('Database and Media Backup'), + 'onclick' => "return backup.backup('" . Mage_Backup_Helper_Data::TYPE_MEDIA . "')", + 'class' => '' + )) + ); $this->setChild('backupsGrid', $this->getLayout()->createBlock('adminhtml/backup_grid') ); + + $this->setChild('dialogs', $this->getLayout()->createBlock('adminhtml/backup_dialogs')); } public function getCreateButtonHtml() @@ -61,8 +79,38 @@ public function getCreateButtonHtml() return $this->getChildHtml('createButton'); } + /** + * Generate html code for "Create System Snapshot" button + * + * @return string + */ + public function getCreateSnapshotButtonHtml() + { + return $this->getChildHtml('createSnapshotButton'); + } + + /** + * Generate html code for "Create Media Backup" button + * + * @return string + */ + public function getCreateMediaBackupButtonHtml() + { + return $this->getChildHtml('createMediaBackupButton'); + } + public function getGridHtml() { return $this->getChildHtml('backupsGrid'); } + + /** + * Generate html code for pop-up messages that will appear when user click on "Rollback" link + * + * @return string + */ + public function getDialogsHtml() + { + return $this->getChildHtml('dialogs'); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Backup/Dialogs.php b/app/code/core/Mage/Adminhtml/Block/Backup/Dialogs.php new file mode 100644 index 00000000..bb8dd202 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Backup/Dialogs.php @@ -0,0 +1,53 @@ + + */ +class Mage_Adminhtml_Block_Backup_Dialogs extends Mage_Adminhtml_Block_Template +{ + /** + * Block's template + * + * @var string + */ + protected $_template = 'backup/dialogs.phtml'; + + /** + * Include backup.js file in page before rendering + * + * @see Mage_Core_Block_Abstract::_prepareLayout() + */ + protected function _prepareLayout() + { + $this->getLayout()->getBlock('head')->addJs('mage/adminhtml/backup.js'); + parent::_prepareLayout(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Backup/Grid.php b/app/code/core/Mage/Adminhtml/Block/Backup/Grid.php index 76413087..ce22f7a2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Backup/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Backup/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -51,8 +51,29 @@ protected function _prepareCollection() return parent::_prepareCollection(); } + /** + * Prepare mass action controls + * + * @return Mage_Adminhtml_Block_Backup_Grid + */ + protected function _prepareMassaction() + { + $this->setMassactionIdField('id'); + $this->getMassactionBlock()->setFormFieldName('ids'); + + $this->getMassactionBlock()->addItem('delete', array( + 'label'=> Mage::helper('adminhtml')->__('Delete'), + 'url' => $this->getUrl('*/*/massDelete'), + 'confirm' => Mage::helper('backup')->__('Are you sure you want to delete the selected backup(s)?') + )); + + return $this; + } + /** * Configuration of grid + * + * @return Mage_Adminhtml_Block_Backup_Grid */ protected function _prepareColumns() { @@ -62,45 +83,58 @@ protected function _prepareColumns() 'header' => Mage::helper('backup')->__('Time'), 'index' => 'date_object', 'type' => 'datetime', + 'width' => 200 + )); + + $this->addColumn('display_name', array( + 'header' => Mage::helper('backup')->__('Name'), + 'index' => 'display_name', + 'filter' => false, + 'sortable' => true, + 'width' => 350 )); $this->addColumn('size', array( 'header' => Mage::helper('backup')->__('Size, Bytes'), 'index' => 'size', 'type' => 'number', - 'sortable' => false, + 'sortable' => true, 'filter' => false )); $this->addColumn('type', array( 'header' => Mage::helper('backup')->__('Type'), 'type' => 'options', - 'options' => array('db' => Mage::helper('backup')->__('DB')), - 'index' =>'type' + 'options' => Mage::helper('backup')->getBackupTypes(), + 'index' => 'type', + 'width' => 300 )); $this->addColumn('download', array( 'header' => Mage::helper('backup')->__('Download'), - 'format' => 'gz   ('.$url7zip.')', + 'format' => '$extension   ('.$url7zip.')', 'index' => 'type', 'sortable' => false, 'filter' => false )); - $this->addColumn('action', array( - 'header' => Mage::helper('backup')->__('Action'), - 'type' => 'action', - 'width' => '80px', - 'filter' => false, - 'sortable' => false, - 'actions' => array(array( - 'url' => $this->getUrl('*/*/delete', array('time' => '$time', 'type' => '$type')), - 'caption' => Mage::helper('adminhtml')->__('Delete'), - 'confirm' => Mage::helper('adminhtml')->__('Are you sure you want to do this?') - )), - 'index' => 'type', - 'sortable' => false - )); + if (Mage::helper('backup')->isRollbackAllowed()){ + $this->addColumn('action', array( + 'header' => Mage::helper('backup')->__('Action'), + 'type' => 'action', + 'width' => '80px', + 'filter' => false, + 'sortable' => false, + 'actions' => array(array( + 'url' => '#', + 'caption' => Mage::helper('backup')->__('Rollback'), + 'onclick' => 'return backup.rollback(\'$type\', \'$time\');' + )), + 'index' => 'type', + 'sortable' => false + )); + } return $this; } diff --git a/app/code/core/Mage/Adminhtml/Block/Cache.php b/app/code/core/Mage/Adminhtml/Block/Cache.php index 130f3831..1a647f93 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cache.php +++ b/app/code/core/Mage/Adminhtml/Block/Cache.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -41,7 +41,7 @@ public function __construct() 'class' => 'delete', )); - $message = Mage::helper('core')->__('Cache storage may contain another data. Are you sure that you want flush it?'); + $message = Mage::helper('core')->__('Cache storage may contain additional data. Are you sure that you want flush it?'); $this->_addButton('flush_system', array( 'label' => Mage::helper('core')->__('Flush Cache Storage'), 'onclick' => 'confirmSetLocation(\''.$message.'\', \'' . $this->getFlushStorageUrl() .'\')', diff --git a/app/code/core/Mage/Adminhtml/Block/Cache/Additional.php b/app/code/core/Mage/Adminhtml/Block/Cache/Additional.php index a8ae7792..45838b40 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cache/Additional.php +++ b/app/code/core/Mage/Adminhtml/Block/Cache/Additional.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cache/Grid.php b/app/code/core/Mage/Adminhtml/Block/Cache/Grid.php index 89a2f1de..a5f5d9dd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cache/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Cache/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cache/Notifications.php b/app/code/core/Mage/Adminhtml/Block/Cache/Notifications.php index 0bc9baeb..fc94c590 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cache/Notifications.php +++ b/app/code/core/Mage/Adminhtml/Block/Cache/Notifications.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block.php index 53a869eb..5d36b9c3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit.php index 2fe28f5f..cd7e9ee2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php index 3a5d9c36..e799445d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -60,7 +60,9 @@ protected function _prepareForm() { $model = Mage::registry('cms_block'); - $form = new Varien_Data_Form(array('id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post')); + $form = new Varien_Data_Form( + array('id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post') + ); $form->setHtmlIdPrefix('block_'); @@ -91,13 +93,15 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'multiselect', array( + $field =$fieldset->addField('store_id', 'multiselect', array( 'name' => 'stores[]', 'label' => Mage::helper('cms')->__('Store View'), 'title' => Mage::helper('cms')->__('Store View'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Grid.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Grid.php index 82bb2e68..967a1a34 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Widget/Chooser.php b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Widget/Chooser.php index 524589c9..817b587c 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Block/Widget/Chooser.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Block/Widget/Chooser.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page.php index 5f5af7be..c4832c5b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit.php index ef66ac59..921977fd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,14 +29,18 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Cms_Page_Edit extends Mage_Adminhtml_Block_Widget_Form_Container { - + /** + * Initialize cms page edit block + * + * @return void + */ public function __construct() { - $this->_objectId = 'page_id'; + $this->_objectId = 'page_id'; $this->_controller = 'cms_page'; parent::__construct(); @@ -45,7 +49,7 @@ public function __construct() $this->_updateButton('save', 'label', Mage::helper('cms')->__('Save Page')); $this->_addButton('saveandcontinue', array( 'label' => Mage::helper('adminhtml')->__('Save and Continue Edit'), - 'onclick' => 'saveAndContinueEdit()', + 'onclick' => 'saveAndContinueEdit(\''.$this->_getSaveAndContinueUrl().'\')', 'class' => 'save', ), -100); } else { @@ -57,20 +61,6 @@ public function __construct() } else { $this->_removeButton('delete'); } - - $this->_formScripts[] = " - function toggleEditor() { - if (tinyMCE.getInstanceById('page_content') == null) { - tinyMCE.execCommand('mceAddControl', false, 'page_content'); - } else { - tinyMCE.execCommand('mceRemoveControl', false, 'page_content'); - } - } - - function saveAndContinueEdit(){ - editForm.submit($('edit_form').action+'back/edit/'); - } - "; } /** @@ -98,4 +88,58 @@ protected function _isAllowedAction($action) { return Mage::getSingleton('admin/session')->isAllowed('cms/page/' . $action); } + + /** + * Getter of url for "Save and Continue" button + * tab_id will be replaced by desired by JS later + * + * @return string + */ + protected function _getSaveAndContinueUrl() + { + return $this->getUrl('*/*/save', array( + '_current' => true, + 'back' => 'edit', + 'active_tab' => '{{tab_id}}' + )); + } + + /** + * Prepare layout + * + * @return Mage_Core_Block_Abstract + */ + protected function _prepareLayout() + { + $tabsBlock = $this->getLayout()->getBlock('cms_page_edit_tabs'); + if ($tabsBlock) { + $tabsBlockJsObject = $tabsBlock->getJsObjectName(); + $tabsBlockPrefix = $tabsBlock->getId() . '_'; + } else { + $tabsBlockJsObject = 'page_tabsJsTabs'; + $tabsBlockPrefix = 'page_tabs_'; + } + + $this->_formScripts[] = " + function toggleEditor() { + if (tinyMCE.getInstanceById('page_content') == null) { + tinyMCE.execCommand('mceAddControl', false, 'page_content'); + } else { + tinyMCE.execCommand('mceRemoveControl', false, 'page_content'); + } + } + + function saveAndContinueEdit(urlTemplate) { + var tabsIdValue = " . $tabsBlockJsObject . ".activeTab.id; + var tabsBlockPrefix = '" . $tabsBlockPrefix . "'; + if (tabsIdValue.startsWith(tabsBlockPrefix)) { + tabsIdValue = tabsIdValue.substr(tabsBlockPrefix.length) + } + var template = new Template(urlTemplate, /(^|.|\\r|\\n)({{(\w+)}})/); + var url = template.evaluate({tab_id:tabsIdValue}); + editForm.submit(url); + } + "; + return parent::_prepareLayout(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Form.php index 88a1f289..da640674 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Content.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Content.php index fa06da8e..23d0bc8a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Content.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Content.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Design.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Design.php index 6c363e3d..36ac00a8 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Design.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Design.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_Cms_Page_Edit_Tab_Design diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php index b5a58577..ff19fd95 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -85,14 +85,16 @@ protected function _prepareForm() * Check is single store mode */ if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'multiselect', array( + $field = $fieldset->addField('store_id', 'multiselect', array( 'name' => 'stores[]', 'label' => Mage::helper('cms')->__('Store View'), 'title' => Mage::helper('cms')->__('Store View'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), - 'disabled' => $isElementDisabled + 'disabled' => $isElementDisabled, )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Meta.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Meta.php index 200740f3..051eec9a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Meta.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tab/Meta.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tabs.php index a3a1e53f..334f7534 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid.php index ce8600c0..b77aa341 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid/Renderer/Action.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid/Renderer/Action.php index 48f040c3..94d90d9b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid/Renderer/Action.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Grid/Renderer/Action.php @@ -20,16 +20,22 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Adminhtml_Block_Cms_Page_Grid_Renderer_Action extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Cms_Page_Grid_Renderer_Action + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { public function render(Varien_Object $row) { $urlModel = Mage::getModel('core/url')->setStore($row->getData('_first_store_id')); - $href = $urlModel->getUrl('', array('_current'=>false)) . "{$row->getIdentifier()}?___store={$row->getStoreCode()}"; + $href = $urlModel->getUrl( + $row->getIdentifier(), array( + '_current' => false, + '_query' => '___store='.$row->getStoreCode() + ) + ); return ''.$this->__('Preview').''; } } diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Widget/Chooser.php b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Widget/Chooser.php index e4885e1a..18df170e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Page/Widget/Chooser.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Page/Widget/Chooser.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content.php b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content.php index 3c8c7abc..36825f0c 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Files.php b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Files.php index 77fb2352..45f0530a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Files.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Files.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Newfolder.php b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Newfolder.php index 154e744f..6b8c2418 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Newfolder.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Newfolder.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Uploader.php b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Uploader.php index 27f30a20..b9b7376d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Uploader.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Content/Uploader.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Tree.php b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Tree.php index 6181d579..ddf4d138 100644 --- a/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Tree.php +++ b/app/code/core/Mage/Adminhtml/Block/Cms/Wysiwyg/Images/Tree.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard.php b/app/code/core/Mage/Adminhtml/Block/Dashboard.php index b4a47535..f1d4feaa 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,6 +28,11 @@ class Mage_Adminhtml_Block_Dashboard extends Mage_Adminhtml_Block_Template { protected $_locale; + /** + * Location of the "Enable Chart" config param + */ + const XML_PATH_ENABLE_CHARTS = 'admin/dashboard/enable_charts'; + public function __construct() { parent::__construct(); @@ -57,9 +62,14 @@ protected function _prepareLayout() $this->getLayout()->createBlock('adminhtml/dashboard_searches_top') ); - $this->setChild('diagrams', - $this->getLayout()->createBlock('adminhtml/dashboard_diagrams') - ); + if (Mage::getStoreConfig(self::XML_PATH_ENABLE_CHARTS)) { + $block = $this->getLayout()->createBlock('adminhtml/dashboard_diagrams'); + } else { + $block = $this->getLayout()->createBlock('adminhtml/template') + ->setTemplate('dashboard/graph/disabled.phtml') + ->setConfigUrl($this->getUrl('adminhtml/system_config/edit', array('section'=>'admin'))); + } + $this->setChild('diagrams', $block); $this->setChild('grids', $this->getLayout()->createBlock('adminhtml/dashboard_grids') diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Abstract.php index 111b20bb..1f134e5e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Bar.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Bar.php index 749e8f43..b28909bc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Bar.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Bar.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Diagrams.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Diagrams.php index 8baefc8e..05a5841a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Diagrams.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Diagrams.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Graph.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Graph.php index 877f7f7c..3cc11211 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Graph.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Graph.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,50 +29,136 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Dashboard_Graph extends Mage_Adminhtml_Block_Dashboard_Abstract { + /** + * Api URL + */ + const API_URL = 'http://chart.apis.google.com/chart'; + + /** + * All series + * + * @var array + */ protected $_allSeries = array(); + + /** + * Axis labels + * + * @var array + */ protected $_axisLabels = array(); + + /** + * Axis maps + * + * @var array + */ protected $_axisMaps = array(); + /** + * Data rows + * + * @var array + */ protected $_dataRows = array(); + /** + * Simple encoding chars + * + * @var string + */ protected $_simpleEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - protected $_extendedEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; - const API_URL = 'http://chart.apis.google.com/chart'; + /** + * Extended encoding chars + * + * @var string + */ + protected $_extendedEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'; + /** + * Chart width + * + * @var string + */ protected $_width = '587'; + + /** + * Chart height + * + * @var string + */ protected $_height = '300'; - // Google Chart Api Data Encoding + + /** + * Google chart api data encoding + * + * @var string + */ protected $_encoding = 'e'; + /** + * Html identifier + * + * @var string + */ protected $_htmlId = ''; + /** + * Initialize object + * + * @return void + */ public function __construct() { parent::__construct(); $this->setTemplate('dashboard/graph.phtml'); } + /** + * Get tab template + * + * @return string + */ protected function _getTabTemplate() { return 'dashboard/graph.phtml'; } + /** + * Set data rows + * + * @param mixed $rows + * @return void + */ public function setDataRows($rows) { $this->_dataRows = (array)$rows; } + /** + * Add series + * + * @param string $seriesId + * @param array $options + * @return void + */ public function addSeries($seriesId, array $options) { $this->_allSeries[$seriesId] = $options; } + /** + * Get series + * + * @param string $seriesId + * @return mixed + */ public function getSeries($seriesId) { if (isset($this->_allSeries[$seriesId])) { @@ -82,17 +168,28 @@ public function getSeries($seriesId) } } + /** + * Get all series + * + * @return array + */ public function getAllSeries() { return $this->_allSeries; } + /** + * Get chart url + * + * @param bool $directUrl + * @return string + */ public function getChartUrl($directUrl = true) { $params = array( - 'cht' => 'lc', - 'chf' => 'bg,s,f4f4f4|c,lg,90,ffffff,0.1,ededed,0', - 'chm' => 'B,f4d4b2,0,0,0', + 'cht' => 'lc', + 'chf' => 'bg,s,f4f4f4|c,lg,90,ffffff,0.1,ededed,0', + 'chm' => 'B,f4d4b2,0,0,0', 'chco' => 'db4814' ); @@ -102,11 +199,14 @@ public function getChartUrl($directUrl = true) $this->setAxisLabels($axis, $this->getRowsData($attr, true)); } - $gmtOffset = Mage::getSingleton('core/date')->getGmtOffset(); + $timezoneLocal = Mage::app()->getStore()->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE); list ($dateStart, $dateEnd) = Mage::getResourceModel('reports/order_collection') ->getDateRange($this->getDataHelper()->getParam('period'), '', '', true); + $dateStart->setTimezone($timezoneLocal); + $dateEnd->setTimezone($timezoneLocal); + $dates = array(); $datas = array(); @@ -247,7 +347,8 @@ public function getChartUrl($directUrl = true) } $firstchar = floor($ylocation / 64); $secondchar = $ylocation % 64; - $mappedchar = substr($this->_extendedEncoding, $firstchar, 1) . substr($this->_extendedEncoding, $secondchar, 1); + $mappedchar = substr($this->_extendedEncoding, $firstchar, 1) + . substr($this->_extendedEncoding, $secondchar, 1); array_push($chartdata, $mappedchar . $dataDelimiter); } else { array_push($chartdata, $dataMissing . $dataDelimiter); @@ -281,11 +382,15 @@ public function getChartUrl($directUrl = true) if ($_label != '') { switch ($this->getDataHelper()->getParam('period')) { case '24h': - $this->_axisLabels[$idx][$_index] = $this->formatTime($_label, 'short', false); + $this->_axisLabels[$idx][$_index] = $this->formatTime( + new Zend_Date($_label, 'yyyy-MM-dd HH:00'), 'short', false + ); break; case '7d': case '1m': - $this->_axisLabels[$idx][$_index] = $this->formatDate($_label); + $this->_axisLabels[$idx][$_index] = $this->formatDate( + new Zend_Date($_label, 'yyyy-MM-dd') + ); break; case '1y': case '2y': @@ -346,41 +451,68 @@ public function getChartUrl($directUrl = true) } } + /** + * Get rows data + * + * @param array $attributes + * @param bool $single + * @return array + */ protected function getRowsData($attributes, $single = false) { $items = $this->getCollection()->getItems(); $options = array(); foreach ($items as $item){ if ($single) { - $options[] = $item->getData($attributes); + $options[] = max(0, $item->getData($attributes)); } else { foreach ((array)$attributes as $attr){ - $options[$attr][] = $item->getData($attr); + $options[$attr][] = max(0, $item->getData($attr)); } } } return $options; } - + /** + * Set axis labels + * + * @param string $axis + * @param array $labels + * @return void + */ public function setAxisLabels($axis, $labels) { $this->_axisLabels[$axis] = $labels; } - + /** + * Set html id + * + * @param string $htmlId + * @return void + */ public function setHtmlId($htmlId) { $this->_htmlId = $htmlId; } - + /** + * Get html id + * + * @return string + */ public function getHtmlId() { return $this->_htmlId; } - + /** + * Return pow + * + * @param int $number + * @return int + */ protected function _getPow($number) { $pow = 0; @@ -391,15 +523,38 @@ protected function _getPow($number) return $pow; } - + /** + * Return chart width + * + * @return string + */ protected function getWidth() { return $this->_width; } - + /** + * Return chart height + * + * @return string + */ protected function getHeight() { return $this->_height; } + + /** + * Prepare chart data + * + * @return void + */ + protected function _prepareData() + { + $availablePeriods = array_keys($this->helper('adminhtml/dashboard_data')->getDatePeriods()); + $period = $this->getRequest()->getParam('period'); + + $this->getDataHelper()->setParam('period', + ($period && in_array($period, $availablePeriods)) ? $period : '24h' + ); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Grid.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Grid.php index 3bbb3521..17abc4dd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Grids.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Grids.php index 9a2fbf83..a70f64c4 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Grids.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Grids.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -59,14 +59,14 @@ public function __construct() protected function _prepareLayout() { // load this active tab statically - $this->addTab('reviewed_products', array( + $this->addTab('ordered_products', array( 'label' => $this->__('Bestsellers'), 'content' => $this->getLayout()->createBlock('adminhtml/dashboard_tab_products_ordered')->toHtml(), 'active' => true )); // load other tabs with ajax - $this->addTab('ordered_products', array( + $this->addTab('reviewed_products', array( 'label' => $this->__('Most Viewed Products'), 'url' => $this->getUrl('*/*/productsViewed', array('_current'=>true)), 'class' => 'ajax' diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Orders/Grid.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Orders/Grid.php index b32ddd19..1298ad5a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Orders/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Orders/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -43,6 +43,9 @@ public function __construct() protected function _prepareCollection() { + if (!Mage::helper('core')->isModuleEnabled('Mage_Reports')) { + return $this; + } $collection = Mage::getResourceModel('reports/order_collection') ->addItemCountExpr() ->joinCustomerName('customer') diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Sales.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Sales.php index 18ada207..781fc509 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Sales.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Sales.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,6 +44,9 @@ protected function _construct() protected function _prepareLayout() { + if (!Mage::helper('core')->isModuleEnabled('Mage_Reports')) { + return $this; + } $isFilter = $this->getRequest()->getParam('store') || $this->getRequest()->getParam('website') || $this->getRequest()->getParam('group'); $collection = Mage::getResourceModel('reports/order_collection') diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Last.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Last.php index 0192918b..61d81106 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Last.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Last.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,6 +44,9 @@ public function __construct() protected function _prepareCollection() { + if (!Mage::helper('core')->isModuleEnabled('Mage_CatalogSearch')) { + return parent::_prepareCollection(); + } $this->_collection = Mage::getModel('catalogsearch/query') ->getResourceCollection(); $this->_collection->setRecentQueryFilter(); diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Renderer/Searchquery.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Renderer/Searchquery.php index b09868be..45f269e0 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Renderer/Searchquery.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Renderer/Searchquery.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Top.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Top.php index 415cff9e..1642fccc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Top.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Searches/Top.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,6 +44,9 @@ public function __construct() protected function _prepareCollection() { + if (!Mage::helper('core')->isModuleEnabled('Mage_CatalogSearch')) { + return parent::_prepareCollection(); + } $this->_collection = Mage::getModel('catalogsearch/query') ->getResourceCollection(); @@ -70,7 +73,7 @@ protected function _prepareColumns() $this->addColumn('search_query', array( 'header' => $this->__('Search Term'), 'sortable' => false, - 'index' => 'query_text', + 'index' => 'name', 'renderer' => 'adminhtml/dashboard_searches_renderer_searchquery', )); diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Amounts.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Amounts.php index 8a39baeb..439e5403 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Amounts.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Amounts.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,32 +29,39 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Dashboard_Tab_Amounts extends Mage_Adminhtml_Block_Dashboard_Graph { + /** + * Initialize object + * + * @return void + */ public function __construct() { $this->setHtmlId('amounts'); parent::__construct(); } + /** + * Prepare chart data + * + * @return void + */ protected function _prepareData() { $this->setDataHelperName('adminhtml/dashboard_order'); $this->getDataHelper()->setParam('store', $this->getRequest()->getParam('store')); $this->getDataHelper()->setParam('website', $this->getRequest()->getParam('website')); $this->getDataHelper()->setParam('group', $this->getRequest()->getParam('group')); - $this->getDataHelper()->setParam( - 'period', - $this->getRequest()->getParam('period')?$this->getRequest()->getParam('period'):'24h' - ); $this->setDataRows('revenue'); $this->_axisMaps = array( 'x' => 'range', - 'y' => 'revenue'); + 'y' => 'revenue' + ); parent::_prepareData(); } diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Most.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Most.php index 51c35a1f..0deae7ba 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Most.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Most.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Newest.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Newest.php index 73193b78..bf6c89cb 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Newest.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Customers/Newest.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Orders.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Orders.php index 706c1d4c..53f43aca 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Orders.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Orders.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,32 +29,39 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Dashboard_Tab_Orders extends Mage_Adminhtml_Block_Dashboard_Graph { + /** + * Initialize object + * + * @return void + */ public function __construct() { $this->setHtmlId('orders'); parent::__construct(); } + /** + * Prepare chart data + * + * @return void + */ protected function _prepareData() { $this->setDataHelperName('adminhtml/dashboard_order'); $this->getDataHelper()->setParam('store', $this->getRequest()->getParam('store')); $this->getDataHelper()->setParam('website', $this->getRequest()->getParam('website')); $this->getDataHelper()->setParam('group', $this->getRequest()->getParam('group')); - $this->getDataHelper()->setParam( - 'period', - $this->getRequest()->getParam('period')?$this->getRequest()->getParam('period'):'24h' - ); $this->setDataRows('quantity'); $this->_axisMaps = array( 'x' => 'range', - 'y' => 'quantity'); + 'y' => 'quantity' + ); parent::_prepareData(); } diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Ordered.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Ordered.php index bdf4eae2..e636e907 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Ordered.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Ordered.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -43,6 +43,9 @@ public function __construct() protected function _prepareCollection() { + if (!Mage::helper('core')->isModuleEnabled('Mage_Sales')) { + return $this; + } if ($this->getParam('website')) { $storeIds = Mage::app()->getWebsite($this->getParam('website'))->getStoreIds(); $storeId = array_pop($storeIds); @@ -96,9 +99,25 @@ protected function _prepareColumns() return parent::_prepareColumns(); } + /* + * Returns row url to show in admin dashboard + * $row is bestseller row wrapped in Product model + * + * @param Mage_Catalog_Model_Product $row + * + * @return string + */ public function getRowUrl($row) { - $params = array('id'=>$row->getId()); + // getId() would return id of bestseller row, and product id we get by getProductId() + $productId = $row->getProductId(); + + // No url is possible for non-existing products + if (!$productId) { + return ''; + } + + $params = array('id' => $productId); if ($this->getRequest()->getParam('store')) { $params['store'] = $this->getRequest()->getParam('store'); } diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Viewed.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Viewed.php index 0012898e..577ceb8a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Viewed.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Tab/Products/Viewed.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Dashboard/Totals.php b/app/code/core/Mage/Adminhtml/Block/Dashboard/Totals.php index 149a524a..a48ce71c 100644 --- a/app/code/core/Mage/Adminhtml/Block/Dashboard/Totals.php +++ b/app/code/core/Mage/Adminhtml/Block/Dashboard/Totals.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -37,12 +37,14 @@ class Mage_Adminhtml_Block_Dashboard_Totals extends Mage_Adminhtml_Block_Dashboa protected function _construct() { parent::_construct(); - $this->setTemplate('dashboard/totalbar.phtml'); } protected function _prepareLayout() { + if (!Mage::helper('core')->isModuleEnabled('Mage_Reports')) { + return $this; + } $isFilter = $this->getRequest()->getParam('store') || $this->getRequest()->getParam('website') || $this->getRequest()->getParam('group'); $period = $this->getRequest()->getParam('period', '24h'); diff --git a/app/code/core/Mage/Adminhtml/Block/Denied.php b/app/code/core/Mage/Adminhtml/Block/Denied.php index f2900359..a1c5fafe 100644 --- a/app/code/core/Mage/Adminhtml/Block/Denied.php +++ b/app/code/core/Mage/Adminhtml/Block/Denied.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Html/Date.php b/app/code/core/Mage/Adminhtml/Block/Html/Date.php index 20b32d95..5df7455e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Html/Date.php +++ b/app/code/core/Mage/Adminhtml/Block/Html/Date.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Html/Select.php b/app/code/core/Mage/Adminhtml/Block/Html/Select.php index 167e161a..e421aebd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Html/Select.php +++ b/app/code/core/Mage/Adminhtml/Block/Html/Select.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Media/Editor.php b/app/code/core/Mage/Adminhtml/Block/Media/Editor.php index 96983eba..0741c878 100644 --- a/app/code/core/Mage/Adminhtml/Block/Media/Editor.php +++ b/app/code/core/Mage/Adminhtml/Block/Media/Editor.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Media/Uploader.php b/app/code/core/Mage/Adminhtml/Block/Media/Uploader.php index 51b071bc..033ece14 100644 --- a/app/code/core/Mage/Adminhtml/Block/Media/Uploader.php +++ b/app/code/core/Mage/Adminhtml/Block/Media/Uploader.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -195,11 +195,12 @@ public function getDataMaxSizeInBytes() } /** - * Retrive full uploader SWF's file URL + * Retrieve full uploader SWF's file URL * Implemented to solve problem with cross domain SWFs * Now uploader can be only in the same URL where backend located * - * @param string url to uploader in current theme + * @param string $url url to uploader in current theme + * * @return string full URL */ public function getUploaderUrl($url) diff --git a/app/code/core/Mage/Adminhtml/Block/Messages.php b/app/code/core/Mage/Adminhtml/Block/Messages.php index 85453839..749bfe68 100644 --- a/app/code/core/Mage/Adminhtml/Block/Messages.php +++ b/app/code/core/Mage/Adminhtml/Block/Messages.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Baseurl.php b/app/code/core/Mage/Adminhtml/Block/Notification/Baseurl.php index f7389f25..99bb8b55 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Baseurl.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Baseurl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Grid.php b/app/code/core/Mage/Adminhtml/Block/Notification/Grid.php index 12b8e7a6..32ee28a1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Actions.php b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Actions.php index e9bd5cf3..7a78776f 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Actions.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Actions.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -43,25 +43,26 @@ class Mage_Adminhtml_Block_Notification_Grid_Renderer_Actions */ public function render(Varien_Object $row) { - if (!$row->getIsRead()) { - return sprintf('%s | %s | %s', - $row->getUrl(), - Mage::helper('adminnotification')->__('Read Details'), - $this->getUrl('*/*/markAsRead/', array('_current'=>true, 'id' => $row->getId())), - Mage::helper('adminnotification')->__('Mark as Read'), - $this->getUrl('*/*/remove/', array('_current'=>true, 'id' => $row->getId())), - Mage::helper('adminnotification')->__('Are you sure?'), - Mage::helper('adminnotification')->__('Remove') - ); - } - else { - return sprintf('%s | %s', - $row->getUrl(), - Mage::helper('adminnotification')->__('Read Details'), - $this->getUrl('*/*/remove/', array('_current'=>true, 'id' => $row->getId())), - Mage::helper('adminnotification')->__('Are you sure?'), - Mage::helper('adminnotification')->__('Remove') - ); - } + $readDetailsHtml = ($row->getUrl()) + ? '' . + Mage::helper('adminnotification')->__('Read Details') .' | ' + : ''; + + $markAsReadHtml = (!$row->getIsRead()) + ? '' . + Mage::helper('adminnotification')->__('Mark as Read') .' | ' + : ''; + + return sprintf('%s%s%s', + $readDetailsHtml, + $markAsReadHtml, + $this->getUrl('*/*/remove/', array( + '_current'=>true, + 'id' => $row->getId(), + Mage_Core_Controller_Front_Action::PARAM_NAME_URL_ENCODED => $this->helper('core/url')->getEncodedUrl()) + ), + Mage::helper('adminnotification')->__('Are you sure?'), + Mage::helper('adminnotification')->__('Remove') + ); } } diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Notice.php b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Notice.php index c3c4e972..5b0f3458 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Notice.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Notice.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Severity.php b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Severity.php index 21dcf061..3138ac50 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Severity.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Grid/Renderer/Severity.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Inbox.php b/app/code/core/Mage/Adminhtml/Block/Notification/Inbox.php index b64482c8..53b5bb6e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Inbox.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Inbox.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Security.php b/app/code/core/Mage/Adminhtml/Block/Notification/Security.php new file mode 100644 index 00000000..7e524804 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Security.php @@ -0,0 +1,93 @@ +loadCache(self::VERIFICATION_RESULT_CACHE_KEY)) { + return false; + } + if ($this->_isFileAccessible()) { + return true; + } + $adminSessionLifetime = (int)Mage::getStoreConfig('admin/security/session_cookie_lifetime'); + Mage::app()->saveCache(true, self::VERIFICATION_RESULT_CACHE_KEY, array(), $adminSessionLifetime); + return false; + } + + /** + * If file is accessible return true or false + * + * @return bool + */ + private function _isFileAccessible() + { + $defaultUnsecureBaseURL = (string) Mage::getConfig()->getNode('default/' . Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_URL); + + $http = new Varien_Http_Adapter_Curl(); + $http->setConfig(array('timeout' => $this->_verificationTimeOut)); + $http->write(Zend_Http_Client::POST, $defaultUnsecureBaseURL . $this->_filePath); + $responseBody = $http->read(); + $responseCode = Zend_Http_Response::extractCode($responseBody); + $http->close(); + + return $responseCode == 200; + } + + /** + * Prepare html output + * + * @return string + */ + protected function _toHtml() + { + if (!$this->_canShowNotification()) { + return ''; + } + return parent::_toHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Toolbar.php b/app/code/core/Mage/Adminhtml/Block/Notification/Toolbar.php index 0f27a668..649f2811 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Toolbar.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Toolbar.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -64,9 +64,12 @@ public function isShow() if ($this->getRequest()->getControllerName() == 'notification') { return false; } - if ($this->getCriticalCount() == 0 && $this->getMajorCount() == 0 && $this->getMinorCount() == 0 && $this->getNoticeCount() == 0) { + if ($this->getCriticalCount() == 0 && $this->getMajorCount() == 0 && $this->getMinorCount() == 0 + && $this->getNoticeCount() == 0 + ) { return false; } + return true; } diff --git a/app/code/core/Mage/Adminhtml/Block/Notification/Window.php b/app/code/core/Mage/Adminhtml/Block/Notification/Window.php index 2592b3dc..f442f765 100644 --- a/app/code/core/Mage/Adminhtml/Block/Notification/Window.php +++ b/app/code/core/Mage/Adminhtml/Block/Notification/Window.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -53,17 +53,17 @@ protected function _construct() { parent::_construct(); - $this->setHeaderText(addslashes($this->__('Incoming Message'))); - $this->setCloseText(addslashes($this->__('close'))); - $this->setReadDetailsText(addslashes($this->__('Read details'))); - $this->setNoticeText(addslashes($this->__('NOTICE'))); - $this->setMinorText(addslashes($this->__('MINOR'))); - $this->setMajorText(addslashes($this->__('MAJOR'))); - $this->setCriticalText(addslashes($this->__('CRITICAL'))); + $this->setHeaderText($this->escapeHtml($this->__('Incoming Message'))); + $this->setCloseText($this->escapeHtml($this->__('close'))); + $this->setReadDetailsText($this->escapeHtml($this->__('Read details'))); + $this->setNoticeText($this->escapeHtml($this->__('NOTICE'))); + $this->setMinorText($this->escapeHtml($this->__('MINOR'))); + $this->setMajorText($this->escapeHtml($this->__('MAJOR'))); + $this->setCriticalText($this->escapeHtml($this->__('CRITICAL'))); - $this->setNoticeMessageText(addslashes($this->getLastNotice()->getTitle())); - $this->setNoticeMessageUrl(addslashes($this->getLastNotice()->getUrl())); + $this->setNoticeMessageText($this->escapeHtml($this->getLastNotice()->getTitle())); + $this->setNoticeMessageUrl($this->escapeUrl($this->getLastNotice()->getUrl())); switch ($this->getLastNotice()->getSeverity()) { default: @@ -91,17 +91,16 @@ protected function _construct() */ public function canShow() { - if (!Mage::getSingleton('admin/session')->isFirstPageAfterLogin()) { - $this->_available = false; - return false; + if (!is_null($this->_available)) { + return $this->_available; } - if (!$this->isOutputEnabled('Mage_AdminNotification')) { + if (!Mage::getSingleton('admin/session')->isFirstPageAfterLogin()) { $this->_available = false; return false; } - if (!$this->_getHelper()->isReadablePopupObject()) { + if (!$this->isOutputEnabled('Mage_AdminNotification')) { $this->_available = false; return false; } diff --git a/app/code/core/Mage/Adminhtml/Block/Page.php b/app/code/core/Mage/Adminhtml/Block/Page.php index c9a27711..9d0ba5e6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page.php +++ b/app/code/core/Mage/Adminhtml/Block/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Footer.php b/app/code/core/Mage/Adminhtml/Block/Page/Footer.php index fac5ae3a..f68da9c1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Footer.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Footer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,6 +34,9 @@ */ class Mage_Adminhtml_Block_Page_Footer extends Mage_Adminhtml_Block_Template { + const LOCALE_CACHE_LIFETIME = 7200; + const LOCALE_CACHE_KEY = 'footer_locale'; + const LOCALE_CACHE_TAG = 'adminhtml'; protected function _construct() { @@ -58,15 +61,22 @@ public function getRefererParamName() public function getLanguageSelect() { - $html = $this->getLayout()->createBlock('adminhtml/html_select') - ->setName('locale') - ->setId('interface_locale') - ->setTitle(Mage::helper('page')->__('Interface Language')) - ->setExtraParams('style="width:200px"') - ->setValue(Mage::app()->getLocale()->getLocaleCode()) - ->setOptions(Mage::app()->getLocale()->getTranslatedOptionLocales()) - ->getHtml(); + $locale = Mage::app()->getLocale(); + $cacheId = self::LOCALE_CACHE_KEY . $locale->getLocaleCode(); + $html = Mage::app()->loadCache($cacheId); + + if (!$html) { + $html = $this->getLayout()->createBlock('adminhtml/html_select') + ->setName('locale') + ->setId('interface_locale') + ->setTitle(Mage::helper('page')->__('Interface Language')) + ->setExtraParams('style="width:200px"') + ->setValue($locale->getLocaleCode()) + ->setOptions($locale->getTranslatedOptionLocales()) + ->getHtml(); + Mage::app()->saveCache($html, $cacheId, array(self::LOCALE_CACHE_TAG), self::LOCALE_CACHE_LIFETIME); + } + return $html; } - } diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Head.php b/app/code/core/Mage/Adminhtml/Block/Page/Head.php index 506a34b7..33919334 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Head.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Head.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Header.php b/app/code/core/Mage/Adminhtml/Block/Page/Header.php index 65190971..43f6f852 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Header.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Header.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Menu.php b/app/code/core/Mage/Adminhtml/Block/Page/Menu.php index 89ecd881..5634f0a4 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Menu.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Menu.php @@ -20,13 +20,16 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Adminhtml menu block * + * @method Mage_Adminhtml_Block_Page_Menu setAdditionalCacheKeyInfo(array $cacheKeyInfo) + * @method array getAdditionalCacheKeyInfo() + * * @category Mage * @package Mage_Adminhtml * @author Magento Core Team @@ -71,12 +74,18 @@ public function getCacheLifetime() */ public function getCacheKeyInfo() { - return array( + $cacheKeyInfo = array( 'admin_top_nav', $this->getActive(), Mage::getSingleton('admin/session')->getUser()->getId(), Mage::app()->getLocale()->getLocaleCode() ); + // Add additional key parameters if needed + $additionalCacheKeyInfo = $this->getAdditionalCacheKeyInfo(); + if (is_array($additionalCacheKeyInfo) && !empty($additionalCacheKeyInfo)) { + $cacheKeyInfo = array_merge($cacheKeyInfo, $additionalCacheKeyInfo); + } + return $cacheKeyInfo; } /** @@ -266,4 +275,36 @@ protected function _callbackSecretKey($match) return Mage_Adminhtml_Model_Url::SECRET_KEY_PARAM_NAME . '/' . $this->_url->getSecretKey($match[1], $match[2]); } + + /** + * Get menu level HTML code + * + * @param array $menu + * @param int $level + * @return string + */ + public function getMenuLevel($menu, $level = 0) + { + $html = '
    ' . PHP_EOL; + foreach ($menu as $item) { + $html .= '
  • ' + . $this->escapeHtml($item['label']) . '' . PHP_EOL; + + if (!empty($item['children'])) { + $html .= $this->getMenuLevel($item['children'], $level + 1); + } + $html .= '
  • ' . PHP_EOL; + } + $html .= '
' . PHP_EOL; + + return $html; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Page/Notices.php b/app/code/core/Mage/Adminhtml/Block/Page/Notices.php index c78194d3..0ae1c050 100644 --- a/app/code/core/Mage/Adminhtml/Block/Page/Notices.php +++ b/app/code/core/Mage/Adminhtml/Block/Page/Notices.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Buttons.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Buttons.php index c34507a8..a3cd5bc6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Buttons.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Buttons.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Editroles.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Editroles.php index 5b5e3079..e9bbc97d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Editroles.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Editroles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Edituser.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Edituser.php index dc5caec5..0aa0574e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Edituser.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Edituser.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/Role.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/Role.php index 1169cf2c..89c01a1d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/Role.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/Role.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/User.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/User.php index 8766847a..f1ae1fc1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/User.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Grid/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Role.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Role.php index a6cc290e..5c4a7664 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Role.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Role.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Role/Grid/User.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Role/Grid/User.php index e763e883..00a2d1d3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Role/Grid/User.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Role/Grid/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Roles.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Roles.php index 1e00b90b..dfda51c8 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Roles.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Roles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Roleinfo.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Roleinfo.php index 9092cee1..17ff9988 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Roleinfo.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Roleinfo.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesedit.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesedit.php index b1b8667e..2afc73b3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesedit.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesedit.php @@ -20,11 +20,19 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Adminhtml_Block_Permissions_Tab_Rolesedit extends Mage_Adminhtml_Block_Widget_Form implements Mage_Adminhtml_Block_Widget_Tab_Interface +/** + * Rolesedit Tab Display Block + * + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team + */ +class Mage_Adminhtml_Block_Permissions_Tab_Rolesedit extends Mage_Adminhtml_Block_Widget_Form + implements Mage_Adminhtml_Block_Widget_Tab_Interface { /** * Get tab label @@ -66,6 +74,10 @@ public function isHidden() return false; } + /** + * Class constructor + * + */ public function __construct() { parent::__construct(); @@ -79,9 +91,10 @@ public function __construct() $selrids = array(); foreach ($rules_set->getItems() as $item) { - if (array_key_exists(strtolower($item->getResource_id()), $resources) && $item->getPermission() == 'allow') { - $resources[$item->getResource_id()]['checked'] = true; - array_push($selrids, $item->getResource_id()); + $itemResourceId = $item->getResource_id(); + if (array_key_exists(strtolower($itemResourceId), $resources) && $item->getPermission() == 'allow') { + $resources[$itemResourceId]['checked'] = true; + array_push($selrids, $itemResourceId); } } @@ -92,11 +105,21 @@ public function __construct() //->assign('checkedResources', join(',', $selrids)); } + /** + * Check if everything is allowed + * + * @return boolean + */ public function getEverythingAllowed() { return in_array('all', $this->getSelectedResources()); } + /** + * Get Json Representation of Resource Tree + * + * @return string + */ public function getResTreeJson() { $rid = Mage::app()->getRequest()->getParam('rid', false); @@ -109,21 +132,34 @@ public function getResTreeJson() return $json; } + /** + * Compare two nodes of the Resource Tree + * + * @param array $a + * @param array $b + * @return boolean + */ protected function _sortTree($a, $b) { return $a['sort_order']<$b['sort_order'] ? -1 : ($a['sort_order']>$b['sort_order'] ? 1 : 0); } - - protected function _getNodeJson($node, $level=0) + /** + * Get Node Json + * + * @param mixed $node + * @param int $level + * @return array + */ + protected function _getNodeJson($node, $level = 0) { $item = array(); $selres = $this->getSelectedResources(); if ($level != 0) { - $item['text']= (string)$node->title; - $item['sort_order']= isset($node->sort_order) ? (string)$node->sort_order : 0; - $item['id'] = (string)$node->attributes()->aclpath; + $item['text'] = Mage::helper('adminhtml')->__((string)$node->title); + $item['sort_order'] = isset($node->sort_order) ? (string)$node->sort_order : 0; + $item['id'] = (string)$node->attributes()->aclpath; if (in_array($item['id'], $selres)) $item['checked'] = true; @@ -141,7 +177,7 @@ protected function _getNodeJson($node, $level=0) $item['children'] = array(); //$item['cls'] = 'fiche-node'; foreach ($children as $child) { - if ($child->getName()!='title' && $child->getName()!='sort_order') { + if ($child->getName() != 'title' && $child->getName() != 'sort_order') { if (!(string)$child->title) { continue; } diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesusers.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesusers.php index a13f172b..c22b78bd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesusers.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Rolesusers.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Useredit.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Useredit.php index 5ea476a8..1e3b4d8a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Useredit.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Useredit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_Permissions_Tab_Useredit extends Mage_Adminhtml_Block_Widget_Form diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Userroles.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Userroles.php index adfa1ae9..93227973 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Userroles.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Tab/Userroles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_Permissions_Tab_Userroles extends Mage_Adminhtml_Block_Widget_Tabs diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User.php index 1d283f5c..8265c5ec 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -42,4 +42,14 @@ public function __construct() parent::__construct(); } + /** + * Prepare output HTML + * + * @return string + */ + protected function _toHtml() + { + Mage::dispatchEvent('permissions_user_html_before', array('block' => $this)); + return parent::_toHtml(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit.php index 6e903447..364074f7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Form.php index 33293e55..9dee15e7 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php index 2887bc0d..f6565f5d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Roles.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Roles.php index a1724855..670d91fc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Roles.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tab/Roles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tabs.php index 53317a83..16d33554 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Grid.php b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Grid.php index 9f462949..80b91c1d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/User/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/User/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Usernroles.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Usernroles.php index f6de230c..4cb8cef1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Usernroles.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Usernroles.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_Permissions_UsernRoles extends Mage_Adminhtml_Block_Template diff --git a/app/code/core/Mage/Adminhtml/Block/Permissions/Users.php b/app/code/core/Mage/Adminhtml/Block/Permissions/Users.php index 86a7db73..2000f4e1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Permissions/Users.php +++ b/app/code/core/Mage/Adminhtml/Block/Permissions/Users.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php b/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php index 50f1afc3..a0b83f26 100644 --- a/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php +++ b/app/code/core/Mage/Adminhtml/Block/Store/Switcher.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,17 +29,34 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_Store_Switcher extends Mage_Adminhtml_Block_Template { + /** + * Key in config for store switcher hint + */ + const XPATH_HINT_KEY = 'store_switcher'; + /** * @var array */ protected $_storeIds; + /** + * Name of store variable + * + * @var string + */ protected $_storeVarName = 'store'; + /** + * Url for store switcher hint + * + * @var string + */ + protected $_hintUrl; + /** * @var bool */ @@ -206,4 +223,38 @@ public function hasDefaultOption($hasDefaultOption = null) } return $this->_hasDefaultOption; } + + /** + * Return url for store switcher hint + * + * @return string + */ + public function getHintUrl() + { + if (null === $this->_hintUrl) { + $this->_hintUrl = Mage::helper('core/hint')->getHintByCode(self::XPATH_HINT_KEY); + } + return $this->_hintUrl; + } + + /** + * Return store switcher hint html + * + * @return string + */ + public function getHintHtml() + { + $html = ''; + $url = $this->getHintUrl(); + if ($url) { + $html = '' + . $this->__('What is this?') + . ''; + } + return $html; + } } diff --git a/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset.php b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset.php new file mode 100644 index 00000000..7fde2104 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset.php @@ -0,0 +1,83 @@ + + */ +class Mage_Adminhtml_Block_Store_Switcher_Form_Renderer_Fieldset + extends Mage_Adminhtml_Block_Template implements Varien_Data_Form_Element_Renderer_Interface +{ + /** + * Form element which re-rendering + * + * @var Varien_Data_Form_Element_Fieldset + */ + protected $_element; + + /** + * Constructor + */ + protected function _construct() + { + $this->setTemplate('store/switcher/form/renderer/fieldset.phtml'); + } + + /** + * Retrieve an element + * + * @return Varien_Data_Form_Element_Fieldset + */ + public function getElement() + { + return $this->_element; + } + + /** + * Render element + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + $this->_element = $element; + return $this->toHtml(); + } + + /** + * Return html for store switcher hint + * + * @return string + */ + public function getHintHtml() + { + return Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php new file mode 100644 index 00000000..64f8c451 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/Store/Switcher/Form/Renderer/Fieldset/Element.php @@ -0,0 +1,84 @@ + + */ +class Mage_Adminhtml_Block_Store_Switcher_Form_Renderer_Fieldset_Element + extends Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element + implements Varien_Data_Form_Element_Renderer_Interface +{ + /** + * Form element which re-rendering + * + * @var Varien_Data_Form_Element_Fieldset + */ + protected $_element; + + /** + * Constructor + */ + protected function _construct() + { + $this->setTemplate('store/switcher/form/renderer/fieldset/element.phtml'); + } + + /** + * Retrieve an element + * + * @return Varien_Data_Form_Element_Fieldset + */ + public function getElement() + { + return $this->_element; + } + + /** + * Render element + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + $this->_element = $element; + return $this->toHtml(); + } + + /** + * Return html for store switcher hint + * + * @return string + */ + public function getHintHtml() + { + return Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit.php index 8efa79fe..de4677e7 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php index 1166aac8..d4a519af 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Account/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Cache/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Cache/Edit.php index f66ccb45..99d6b16a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Cache/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Cache/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php index 092c3001..dbc59b67 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Cache/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Dwstree.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Dwstree.php index ce8cb6bf..4b7c2809 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Dwstree.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Dwstree.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Edit.php index 5fbe3522..b49ecb08 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php index 49431ff8..681d1886 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -143,7 +143,11 @@ public function initForm() $form = new Varien_Data_Form(); - $sections = $this->_configFields->getSection($this->getSectionCode(), $this->getWebsiteCode(), $this->getStoreCode()); + $sections = $this->_configFields->getSection( + $this->getSectionCode(), + $this->getWebsiteCode(), + $this->getStoreCode() + ); if (empty($sections)) { $sections = array(); } @@ -179,7 +183,7 @@ public function initForm() $fieldsetConfig = array('legend' => Mage::helper($helperName)->__((string)$group->label)); if (!empty($group->comment)) { - $fieldsetConfig['comment'] = (string)$group->comment; + $fieldsetConfig['comment'] = Mage::helper($helperName)->__((string)$group->comment); } if (!empty($group->expanded)) { $fieldsetConfig['expanded'] = (bool)$group->expanded; @@ -195,7 +199,9 @@ public function initForm() if ($group->clone_model) { $cloneModel = Mage::getModel((string)$group->clone_model); } else { - Mage::throwException('Config form fieldset clone model required to be able to clone fields'); + Mage::throwException( + 'Config form fieldset clone model required to be able to clone fields' + ); } foreach ($cloneModel->getPrefixes() as $prefix) { $this->initFields($fieldset, $group, $section, $prefix['field'], $prefix['label']); @@ -275,7 +281,11 @@ public function initFields($fieldset, $group, $section, $fieldPrefix='', $labelP // Extend config data with new section group $groupPath = substr($path, 0, strrpos($path, '/')); if (!isset($configDataAdditionalGroups[$groupPath])) { - $this->_configData = $this->_configDataObject->extendConfig($groupPath, false, $this->_configData); + $this->_configData = $this->_configDataObject->extendConfig( + $groupPath, + false, + $this->_configData + ); $configDataAdditionalGroups[$groupPath] = true; } } @@ -322,12 +332,41 @@ public function initFields($fieldset, $group, $section, $fieldPrefix='', $labelP if ($e->depends) { foreach ($e->depends->children() as $dependent) { - $dependentId = $section->getName() . '_' . $group->getName() . '_' . $fieldPrefix . $dependent->getName(); - $dependentValue = (string) $dependent; - $this->_getDependence() - ->addFieldMap($id, $id) - ->addFieldMap($dependentId, $dependentId) - ->addFieldDependence($id, $dependentId, $dependentValue); + /* @var $dependent Mage_Core_Model_Config_Element */ + $dependentId = $section->getName() + . '_' . $group->getName() + . '_' . $fieldPrefix + . $dependent->getName(); + $shouldBeAddedDependence = true; + $dependentValue = (string) $dependent; + if (isset($dependent['separator'])) { + $dependentValue = explode((string)$dependent['separator'], $dependentValue); + } + $dependentFieldName = $fieldPrefix . $dependent->getName(); + $dependentField = $group->fields->$dependentFieldName; + /* + * If dependent field can't be shown in current scope and real dependent config value + * is not equal to preferred one, then hide dependence fields by adding dependence + * based on not shown field (not rendered field) + */ + if (!$this->_canShowField($dependentField)) { + $dependentFullPath = $section->getName() + . '/' . $group->getName() + . '/' . $fieldPrefix + . $dependent->getName(); + $dependentValueInStore = Mage::getStoreConfig($dependentFullPath, $this->getStoreCode()); + if (is_array($dependentValue)) { + $shouldBeAddedDependence = !in_array($dependentValueInStore, $dependentValue); + } else { + $shouldBeAddedDependence = $dependentValue != $dependentValueInStore; + } + } + if($shouldBeAddedDependence) { + $this->_getDependence() + ->addFieldMap($id, $id) + ->addFieldMap($dependentId, $dependentId) + ->addFieldDependence($id, $dependentId, $dependentValue); + } } } @@ -353,7 +392,10 @@ public function initFields($fieldset, $group, $section, $fieldPrefix='', $labelP $field->addClass($e->validate); } - if (isset($e->frontend_type) && 'multiselect' === (string)$e->frontend_type && isset($e->can_be_empty)) { + if (isset($e->frontend_type) + && 'multiselect' === (string)$e->frontend_type + && isset($e->can_be_empty) + ) { $field->setCanBeEmpty(true); } @@ -391,6 +433,19 @@ public function initFields($fieldset, $group, $section, $fieldPrefix='', $labelP return $this; } + /** + * Return config root node for current scope + * + * @return Varien_Simplexml_Element + */ + public function getConfigRoot() + { + if (empty($this->_configRoot)) { + $this->_configRoot = Mage::getConfig()->getNode(null, $this->getScope(), $this->getScopeCode()); + } + return $this->_configRoot; + } + /** * Set "original_data" array to the element, composed from nodes with scalar values * @@ -627,8 +682,10 @@ protected function _getAdditionalElementTypes() return array( 'export' => Mage::getConfig()->getBlockClassName('adminhtml/system_config_form_field_export'), 'import' => Mage::getConfig()->getBlockClassName('adminhtml/system_config_form_field_import'), - 'allowspecific' => Mage::getConfig()->getBlockClassName('adminhtml/system_config_form_field_select_allowspecific'), + 'allowspecific' => Mage::getConfig() + ->getBlockClassName('adminhtml/system_config_form_field_select_allowspecific'), 'image' => Mage::getConfig()->getBlockClassName('adminhtml/system_config_form_field_image'), + 'file' => Mage::getConfig()->getBlockClassName('adminhtml/system_config_form_field_file') ); } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field.php index 55237ee5..6dd33695 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Array/Abstract.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Array/Abstract.php index baac69ec..f3cfb39d 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Array/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Array/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Datetime.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Datetime.php index 2bc71763..f51bd9ab 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Datetime.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Datetime.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Export.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Export.php index f8408420..6df56e0c 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Export.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Export.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/File.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/File.php new file mode 100644 index 00000000..23e9ec21 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/File.php @@ -0,0 +1,67 @@ + + */ +class Mage_Adminhtml_Block_System_Config_Form_Field_File extends Varien_Data_Form_Element_File +{ + /** + * Get element html + * + * @return string + */ + public function getElementHtml() + { + $html = parent::getElementHtml(); + $html .= $this->_getDeleteCheckbox(); + return $html; + } + + /** + * Get html for additional delete checkbox field + * + * @return string + */ + protected function _getDeleteCheckbox() + { + $html = ''; + if ((string)$this->getValue()) { + $label = Mage::helper('adminhtml')->__('Delete File'); + $html .= '
'.$this->getValue().' '; + $html .= 'getDisabled() ? ' disabled="disabled"': '').'/>'; + $html .= ''; + $html .= ''; + $html .= '
'; + } + return $html; + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Heading.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Heading.php index 69cc98ce..1b318834 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Heading.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Heading.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -42,8 +42,8 @@ class Mage_Adminhtml_Block_System_Config_Form_Field_Heading public function render(Varien_Data_Form_Element_Abstract $element) { $useContainerId = $element->getData('use_container_id'); - return sprintf('

%s

', - $element->getHtmlId(), $element->getLabel() + return sprintf('

%s

', + $element->getHtmlId(), $element->getHtmlId(), $element->getLabel() ); } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Image.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Image.php index 70d2f055..f6405a23 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Image.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Image.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Import.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Import.php index 2b929f52..73783c15 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Import.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Import.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Notification.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Notification.php index d48bd710..db3f2e55 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Notification.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Notification.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Regexceptions.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Regexceptions.php index c382abda..e708129a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Regexceptions.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Regexceptions.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Select/Allowspecific.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Select/Allowspecific.php index 20e87e12..630dc0c1 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Select/Allowspecific.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Field/Select/Allowspecific.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset.php index 87edddb1..ff53c4df 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -66,20 +66,23 @@ protected function _getHeaderHtml($element) { $default = !$this->getRequest()->getParam('website') && !$this->getRequest()->getParam('store'); - $html = '
'.$element->getLegend().'
'; - $html.= ''; - $html.= '
'; - $html.= ''.$element->getLegend().''; + $html = '
' . $element->getLegend() . '
'; + $html .= ''; + $html .= '
'; + $html .= '' . $element->getLegend() . ''; if ($element->getComment()) { - $html .= '
'.$element->getComment().'
'; + $html .= '' . $element->getComment() . ''; } // field label column - $html.= ''; + $html .= '
'; if (!$default) { - $html.= ''; + $html .= ''; } - $html.= ''; + $html .= ''; return $html; } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php index d72a0ddd..bbc50d59 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Form/Fieldset/Modules/DisableOutput.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,6 +38,13 @@ public function render(Varien_Data_Form_Element_Abstract $element) $modules = array_keys((array)Mage::getConfig()->getNode('modules')->children()); + $dispatchResult = new Varien_Object($modules); + Mage::dispatchEvent( + 'adminhtml_system_config_advanced_disableoutput_render_before', + array('modules' => $dispatchResult) + ); + $modules = $dispatchResult->toArray(); + sort($modules); foreach ($modules as $moduleName) { @@ -82,7 +89,13 @@ protected function _getFieldHtml($fieldset, $moduleName) { $configData = $this->getConfigData(); $path = 'advanced/modules_disable_output/'.$moduleName; //TODO: move as property of form - $data = isset($configData[$path]) ? $configData[$path] : array(); + if (isset($configData[$path])) { + $data = $configData[$path]; + $inherit = false; + } else { + $data = (int)(string)$this->getForm()->getConfigRoot()->descend($path); + $inherit = true; + } $e = $this->_getDummyElement(); @@ -91,8 +104,8 @@ protected function _getFieldHtml($fieldset, $moduleName) 'name' => 'groups[modules_disable_output][fields]['.$moduleName.'][value]', 'label' => $moduleName, 'value' => $data, - 'values' => $this->_getValues(), - 'inherit' => isset($configData[$path]) ? false : true, + 'values' => $this->_getValues(), + 'inherit' => $inherit, 'can_use_default_value' => $this->getForm()->canUseDefaultValue($e), 'can_use_website_value' => $this->getForm()->canUseWebsiteValue($e), ))->setRenderer($this->_getFieldRenderer()); diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Switcher.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Switcher.php index 5caa8008..176fc5ee 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Switcher.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Switcher.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -106,4 +106,13 @@ public function getStoreSelectOptions() return $options; } + /** + * Return store switcher hint html + * + * @return mixed + */ + public function getHintHtml() + { + return Mage::getBlockSingleton('adminhtml/store_switcher')->getHintHtml(); + } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/System/Storage/Media/Synchronize.php b/app/code/core/Mage/Adminhtml/Block/System/Config/System/Storage/Media/Synchronize.php new file mode 100644 index 00000000..805dbe94 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/System/Storage/Media/Synchronize.php @@ -0,0 +1,140 @@ + + */ +class Mage_Adminhtml_Block_System_Config_System_Storage_Media_Synchronize + extends Mage_Adminhtml_Block_System_Config_Form_Field +{ + /* + * Set template + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('system/config/system/storage/media/synchronize.phtml'); + } + + /** + * Remove scope label + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + public function render(Varien_Data_Form_Element_Abstract $element) + { + $element->unsScope()->unsCanUseWebsiteValue()->unsCanUseDefaultValue(); + return parent::render($element); + } + + /** + * Return element html + * + * @param Varien_Data_Form_Element_Abstract $element + * @return string + */ + protected function _getElementHtml(Varien_Data_Form_Element_Abstract $element) + { + return $this->_toHtml(); + } + + /** + * Return ajax url for synchronize button + * + * @return string + */ + public function getAjaxSyncUrl() + { + return Mage::getSingleton('adminhtml/url')->getUrl('*/system_config_system_storage/synchronize'); + } + + /** + * Return ajax url for synchronize button + * + * @return string + */ + public function getAjaxStatusUpdateUrl() + { + return Mage::getSingleton('adminhtml/url')->getUrl('*/system_config_system_storage/status'); + } + + /** + * Generate synchronize button html + * + * @return string + */ + public function getButtonHtml() + { + $button = $this->getLayout()->createBlock('adminhtml/widget_button') + ->setData(array( + 'id' => 'synchronize_button', + 'label' => $this->helper('adminhtml')->__('Synchronize'), + 'onclick' => 'javascript:synchronize(); return false;' + )); + + return $button->toHtml(); + } + + /** + * Retrieve last sync params settings + * + * Return array format: + * array ( + * => storage_type int, + * => connection_name string + * ) + * + * @return array + */ + public function getSyncStorageParams() + { + $flag = Mage::getSingleton('core/file_storage')->getSyncFlag(); + $flagData = $flag->getFlagData(); + + if ($flag->getState() == Mage_Core_Model_File_Storage_Flag::STATE_NOTIFIED + && is_array($flagData) + && isset($flagData['destination_storage_type']) && $flagData['destination_storage_type'] != '' + && isset($flagData['destination_connection_name']) + ) { + $storageType = $flagData['destination_storage_type']; + $connectionName = $flagData['destination_connection_name']; + } else { + $storageType = Mage_Core_Model_File_Storage::STORAGE_MEDIA_FILE_SYSTEM; + $connectionName = ''; + } + + return array( + 'storage_type' => $storageType, + 'connection_name' => $connectionName + ); + } +} diff --git a/app/code/core/Mage/Adminhtml/Block/System/Config/Tabs.php b/app/code/core/Mage/Adminhtml/Block/System/Config/Tabs.php index 8b639f03..87440686 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Config/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Config/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -80,7 +80,7 @@ public function initTabs() $configFields = Mage::getSingleton('adminhtml/config'); $sections = $configFields->getSections($current); $tabs = (array)$configFields->getTabs()->children(); - + $sections = (array)$sections; @@ -99,7 +99,7 @@ public function initTabs() foreach ($sections as $section) { - + Mage::dispatchEvent('adminhtml_block_system_config_init_tab_sections_before', array('section' => $section)); $hasChildren = $configFields->hasChildren($section, $websiteCode, $storeCode); //$code = $section->getPath(); diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui.php index e9c6b540..5b4eba66 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit.php index 4f76afe6..447ba303 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Form.php index c85a5925..78696eab 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Upload.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Upload.php index 5b395602..f3fcc140 100755 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Upload.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Upload.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/View.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/View.php index 7e76aabf..7e3f537a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/View.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/View.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Wizard.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Wizard.php index 90bb2727..a74bd200 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Wizard.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tab/Wizard.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tabs.php index e69b134a..4a0845f3 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Grid.php index 3e58e714..41b28008 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Gui/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,7 +44,7 @@ public function __construct() protected function _prepareCollection() { $collection = Mage::getResourceModel('dataflow/profile_collection') - ->addFieldToFilter('entity_type', array('neq'=>'')); + ->addFieldToFilter('entity_type', array('notnull'=>'')); $this->setCollection($collection); diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile.php index 71f8d92e..78319fbb 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit.php index 7d74886e..fc12b550 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Filter/Action.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Filter/Action.php index 7b02f062..5ff42695 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Filter/Action.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Filter/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Form.php index 797564f9..14932689 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Renderer/Action.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Renderer/Action.php index 7a7b79b3..3b59e298 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Renderer/Action.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Renderer/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Edit.php index 4214db74..fb6d582a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/History.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/History.php index dbd8d460..a4081197 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/History.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/History.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Run.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Run.php index 0c25ff3f..fa6fb019 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Run.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tab/Run.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tabs.php index 3f17fa5f..c08333b6 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Grid.php index 9697672c..65e3032f 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,7 +44,7 @@ public function __construct() protected function _prepareCollection() { $collection = Mage::getResourceModel('dataflow/profile_collection') - ->addFieldToFilter('entity_type', array('eq'=>'')); + ->addFieldToFilter('entity_type', array('null'=>'')); $this->setCollection($collection); diff --git a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Run.php b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Run.php index 0fc41731..4f8ed6cf 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Run.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Convert/Profile/Run.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,112 +33,43 @@ */ class Mage_Adminhtml_Block_System_Convert_Profile_Run extends Mage_Adminhtml_Block_Abstract { - public function getProfile() - { - return Mage::registry('current_convert_profile'); - } - - protected function _toHtml() + /** + * Flag for batch model + * @var boolean + */ + protected $_batchModelPrepared = false; + /** + * Batch model instance + * @var Mage_Dataflow_Model_Batch + */ + protected $_batchModel = null; + /** + * Preparing batch model (initialization) + * @return Mage_Adminhtml_Block_System_Convert_Profile_Run + */ + protected function _prepareBatchModel() { - $profile = $this->getProfile(); - - echo ''; - echo ''; - echo ''; - - $headBlock = $this->getLayout()->createBlock('page/html_head'); - $headBlock->addJs('prototype/prototype.js'); - $headBlock->addJs('mage/adminhtml/loader.js'); - echo $headBlock->getCssJsHtml(); - - echo ' - '.($profile->getId() ? $this->htmlEscape($profile->getName()) : $this->__('No Profile')).' -'; - echo '
    '; - echo '
  • '; - if ($profile->getId()) { - echo ''; - echo $this->__("Starting profile execution, please wait..."); - echo '
  • '; - echo '
  • '; - echo ''; - echo $this->__("Warning: Please do not close the window during importing/exporting data"); - } else { - echo ''; - echo $this->__("No profile loaded..."); + if ($this->_batchModelPrepared) { + return $this; } - echo '
  • '; - echo '
'; - - if ($profile->getId()) { - - echo '
    '; - - ob_implicit_flush(); - $profile->run(); - foreach ($profile->getExceptions() as $e) { - switch ($e->getLevel()) { - case Varien_Convert_Exception::FATAL: - $img = 'error_msg_icon.gif'; - $liStyle = 'background-color:#FBB; '; - break; - case Varien_Convert_Exception::ERROR: - $img = 'error_msg_icon.gif'; - $liStyle = 'background-color:#FDD; '; - break; - case Varien_Convert_Exception::WARNING: - $img = 'fam_bullet_error.gif'; - $liStyle = 'background-color:#FFD; '; - break; - case Varien_Convert_Exception::NOTICE: - $img = 'fam_bullet_success.gif'; - $liStyle = 'background-color:#DDF; '; - break; - } - echo '
  • '; - echo ''; - echo $e->getMessage(); - if ($e->getPosition()) { - echo " (".$e->getPosition().")"; + $this->setShowFinished(true); + $batchModel = Mage::getSingleton('dataflow/batch'); + $this->_batchModel = $batchModel; + if ($batchModel->getId()) { + if ($batchModel->getAdapter()) { + $this->setBatchModelHasAdapter(true); + $numberOfRecords = $this->getProfile()->getData('gui_data/import/number_of_records'); + if (!$numberOfRecords) { + $batchParams = $batchModel->getParams(); + $numberOfRecords = isset($batchParams['number_of_records']) ? $batchParams['number_of_records'] : 1; } - echo "
  • "; - } - - if($profile->getEntityType() == 'product' && $profile->getDirection() == 'import') { - echo ''; - } - - echo ''; - echo "
"; - - $showFinished = true; - $batchModel = Mage::getSingleton('dataflow/batch'); - /* @var $batchModel Mage_Dataflow_Model_Batch */ - if ($batchModel->getId()) { - if ($batchModel->getAdapter()) { - $numberOfRecords = $profile->getData('gui_data/import/number_of_records'); - if (!$numberOfRecords) { - $batchParams = $batchModel->getParams(); - $numberOfRecords = isset($batchParams['number_of_records']) ? $batchParams['number_of_records'] : 1; - } - - $showFinished = false; - $batchImportModel = $batchModel->getBatchImportModel(); - $importIds = $batchImportModel->getIdCollection(); - $countItems = count($importIds); - - $batchConfig = array( + $this->setNumberOfRecords($numberOfRecords); + $this->setShowFinished(false); + $batchImportModel = $batchModel->getBatchImportModel(); + $importIds = $batchImportModel->getIdCollection(); + $this->setBatchItemsCount(count($importIds)); + $this->setBatchConfig( + array( 'styles' => array( 'error' => array( 'icon' => Mage::getDesign()->getSkinUrl('images/error_msg_icon.gif'), @@ -154,157 +85,116 @@ protected function _toHtml() . '' . '#{text}' . '', - 'text' => $this->__('Processed %s%% %s/%d records', '#{percent}', '#{updated}', $countItems), + 'text' => $this->__('Processed %s%% %s/%d records', '#{percent}', '#{updated}', $this->getBatchItemsCount()), 'successText' => $this->__('Imported %s records', '#{updated}') + ) + ); + $jsonIds = array_chunk($importIds, $numberOfRecords); + $importData = array(); + foreach ($jsonIds as $part => $ids) { + $importData[] = array( + 'batch_id' => $batchModel->getId(), + 'rows[]' => $ids ); -echo ' - - -'; - - - $jsonIds = array_chunk($importIds, $numberOfRecords); - foreach ($jsonIds as $part => $ids) { - $data = array( - 'batch_id' => $batchModel->getId(), - 'rows[]' => $ids - ); - echo ''; - } - echo ''; - //print $this->getUrl('*/*/batchFinish', array('id' => $batchModel->getId())); - } - else { - $batchModel->delete(); + /** + * Generating form key + * @return string + */ + public function getFormKey() + { + return Mage::getSingleton('core/session')->getFormKey(); + } + /** + * Return batch model and initialize it if need + * @return Mage_Dataflow_Model_Batch + */ + public function getBatchModel() + { + return $this->_prepareBatchModel() + ->_getBatchModel(); + } + /** + * Generating exceptions data + * @return array + */ + public function getExceptions() + { + if (!is_null(parent::getExceptions())) + return parent::getExceptions(); + $exceptions = array(); + $this->getProfile()->run(); + foreach ($this->getProfile()->getExceptions() as $e) { + switch ($e->getLevel()) { + case Varien_Convert_Exception::FATAL: + $img = 'error_msg_icon.gif'; + $liStyle = 'background-color:#FBB; '; + break; + case Varien_Convert_Exception::ERROR: + $img = 'error_msg_icon.gif'; + $liStyle = 'background-color:#FDD; '; + break; + case Varien_Convert_Exception::WARNING: + $img = 'fam_bullet_error.gif'; + $liStyle = 'background-color:#FFD; '; + break; + case Varien_Convert_Exception::NOTICE: + $img = 'fam_bullet_success.gif'; + $liStyle = 'background-color:#DDF; '; + break; } - } - - if ($showFinished) { - echo ""; - } + $exceptions[] = array( + "style" => $liStyle, + "src" => Mage::getDesign()->getSkinUrl('images/'.$img), + "message" => $e->getMessage(), + "position" => $e->getPosition() + ); } - /* - echo '
  • '; - echo ''; - echo $this->__("Finished profile execution."); - echo '
  • '; - echo ""; - */ - echo ''; + parent::setExceptions($exceptions); + return $exceptions; } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency.php b/app/code/core/Mage/Adminhtml/Block/System/Currency.php index 1db06c44..fbeddc36 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Form.php index bc35895a..c983cf38 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Main.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Main.php index e0d24614..79ab475f 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Main.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Rates.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Rates.php index aeab43ff..d4cbaa4a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Rates.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tab/Rates.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tabs.php index 4ef182f7..b61eb971 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Matrix.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Matrix.php index 7247d80f..42fbb793 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Matrix.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Matrix.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Services.php b/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Services.php index d714c205..ccb20b9d 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Services.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Currency/Rate/Services.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design.php b/app/code/core/Mage/Adminhtml/Block/System/Design.php index 84350cd8..10bdff98 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit.php index f7d81148..c312efd5 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php index 8e35004d..9c38b9bc 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tab/General.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Block_System_Design_Edit_Tab_General extends Mage_Adminhtml_Block_Widget_Form @@ -33,13 +33,15 @@ protected function _prepareForm() $fieldset = $form->addFieldset('general', array('legend'=>Mage::helper('core')->__('General Settings'))); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_id', 'select', array( + $field = $fieldset->addField('store_id', 'select', array( 'label' => Mage::helper('core')->__('Store'), 'title' => Mage::helper('core')->__('Store'), 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), 'name' => 'store_id', 'required' => true, )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } else { $fieldset->addField('store_id', 'hidden', array( 'name' => 'store_id', diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tabs.php b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tabs.php index 9ea3fda3..df764dea 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Design/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Design/Grid.php index 86dbf1f2..10352328 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Design/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Design/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template.php index b1ae6d48..21553f2c 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit.php index 77c8a12c..2746c639 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit/Form.php index edbda7fb..90bd3219 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,7 +45,7 @@ protected function _prepareLayout() if ($head = $this->getLayout()->getBlock('head')) { $head->addItem('js', 'prototype/window.js') ->addItem('js_css', 'prototype/windows/themes/default.css') - ->addItem('js_css', 'prototype/windows/themes/magento.css') + ->addCss('lib/prototype/windows/themes/magento.css') ->addItem('js', 'mage/adminhtml/variables.js'); } return parent::_prepareLayout(); @@ -72,7 +72,8 @@ protected function _prepareForm() 'container_id' => 'used_currently_for', 'after_element_html' => '', )); } @@ -83,7 +84,8 @@ protected function _prepareForm() 'container_id' => 'used_default_for', 'after_element_html' => '', )); } @@ -187,4 +189,3 @@ public function getVariables() return $variables; } } - diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid.php index 38038cd3..bae0cba9 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -37,6 +37,9 @@ class Mage_Adminhtml_Block_System_Email_Template_Grid extends Mage_Adminhtml_Blo protected function _construct() { $this->setEmptyText(Mage::helper('adminhtml')->__('No Templates Found')); + $this->setId('systemEmailTemplateGrid'); + $this->setUseAjax(true); + $this->setSaveParametersInSession(true); } protected function _prepareCollection() diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Filter/Type.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Filter/Type.php index 97d6189e..cbb04013 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Filter/Type.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Filter/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,19 +32,20 @@ * @author Magento Core Team */ -class Mage_Adminhtml_Block_System_Email_Template_Grid_Filter_Type extends Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Select +class Mage_Adminhtml_Block_System_Email_Template_Grid_Filter_Type + extends Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Select { protected static $_types = array( - null => null, + null => null, Mage_Newsletter_Model_Template::TYPE_HTML => 'HTML', - Mage_Newsletter_Model_Template::TYPE_TEXT => 'Text', + Mage_Newsletter_Model_Template::TYPE_TEXT => 'Text', ); protected function _getOptions() { $result = array(); - foreach (self::$_types as $code=>$label) { - $result[] = array('value'=>$code, 'label'=>Mage::helper('adminhtml')->__($label)); + foreach (self::$_types as $code => $label) { + $result[] = array('value' => $code, 'label' => Mage::helper('adminhtml')->__($label)); } return $result; @@ -57,8 +58,6 @@ public function getCondition() return null; } - return array('eq'=>$this->getValue()); + return array('eq' => $this->getValue()); } - - -}// Class Mage_Adminhtml_Block_Newsletter_Queue_Grid_Filter_Status END +} diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Action.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Action.php index 404ebeb7..ee494a42 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Action.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Sender.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Sender.php index 3331da33..7e71a41f 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Sender.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Sender.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Type.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Type.php index 7050f232..308d08f1 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Type.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Grid/Renderer/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,11 +32,12 @@ * @author Magento Core Team */ -class Mage_Adminhtml_Block_System_Email_Template_Grid_Renderer_Type extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_System_Email_Template_Grid_Renderer_Type + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected static $_types = array( - Mage_Newsletter_Model_Template::TYPE_HTML => 'HTML', - Mage_Newsletter_Model_Template::TYPE_TEXT => 'Text', + Mage_Newsletter_Model_Template::TYPE_HTML => 'HTML', + Mage_Newsletter_Model_Template::TYPE_TEXT => 'Text', ); public function render(Varien_Object $row) { diff --git a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Preview.php b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Preview.php index a124a4bf..80cdb05f 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Preview.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Email/Template/Preview.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,15 +29,21 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Block_System_Email_Template_Preview extends Mage_Adminhtml_Block_Widget { - + /** + * Prepare html output + * + * @return string + */ protected function _toHtml() { + /** @var $template Mage_Core_Model_Email_Template */ $template = Mage::getModel('core/email_template'); - if($id = (int)$this->getRequest()->getParam('id')) { + $id = (int)$this->getRequest()->getParam('id'); + if ($id) { $template->load($id); } else { $template->setTemplateType($this->getRequest()->getParam('type')); @@ -45,12 +51,19 @@ protected function _toHtml() $template->setTemplateStyles($this->getRequest()->getParam('styles')); } + /* @var $filter Mage_Core_Model_Input_Filter_MaliciousCode */ + $filter = Mage::getSingleton('core/input_filter_maliciousCode'); + + $template->setTemplateText( + $filter->filter($template->getTemplateText()) + ); + Varien_Profiler::start("email_template_proccessing"); $vars = array(); $templateProcessed = $template->getProcessedTemplate($vars, true); - if($template->isPlain()) { + if ($template->isPlain()) { $templateProcessed = "
    " . htmlspecialchars($templateProcessed) . "
    "; } @@ -58,5 +71,4 @@ protected function _toHtml() return $templateProcessed; } - } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete.php index da023c35..75c97436 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Form.php index aad699ae..37ecbb1a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Group.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Group.php index a164dbe3..6b4d98b4 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Group.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Group.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Website.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Website.php index f2d670b4..3b8b65cd 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Website.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Delete/Website.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Edit.php index c4209e1f..351efd19 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Edit/Form.php index c613896f..6312c521 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -109,8 +109,11 @@ protected function _prepareForm() )); if (Mage::registry('store_action') == 'edit') { - $groups = Mage::getModel('core/store_group')->getCollection()->addWebsiteFilter($websiteModel->getId())->toOptionArray(); - //array_unshift($groups, array('label'=>'', 'value'=>0)); + $groups = Mage::getModel('core/store_group')->getCollection() + ->addWebsiteFilter($websiteModel->getId()) + ->setWithoutStoreViewFilter() + ->toOptionArray(); + $fieldset->addField('website_default_group_id', 'select', array( 'name' => 'website[default_group_id]', 'label' => Mage::helper('core')->__('Default Store'), @@ -202,7 +205,8 @@ protected function _prepareForm() )); if (Mage::registry('store_action') == 'edit') { - $stores = Mage::getModel('core/store')->getCollection()->addGroupFilter($groupModel->getId())->toOptionArray(); + $stores = Mage::getModel('core/store')->getCollection() + ->addGroupFilter($groupModel->getId())->toOptionArray(); //array_unshift($stores, array('label'=>'', 'value'=>0)); $fieldset->addField('group_default_store_id', 'select', array( 'name' => 'group[default_store_id]', @@ -335,6 +339,8 @@ protected function _prepareForm() $form->setUseContainer(true); $this->setForm($form); + Mage::dispatchEvent('adminhtml_store_edit_form_prepare_form', array('block' => $this)); + return parent::_prepareForm(); } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid.php index fb70effa..eb529c19 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Group.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Group.php index dc9bf69a..2b257c1b 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Group.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Group.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,13 +32,16 @@ * @author Magento Core Team */ -class Mage_Adminhtml_Block_System_Store_Grid_Render_Group extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_System_Store_Grid_Render_Group + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { public function render(Varien_Object $row) { if (!$row->getData($this->getColumn()->getIndex())) { return null; } - return '' . $row->getData($this->getColumn()->getIndex()) . ''; + return '' + . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . ''; } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Store.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Store.php index 9827c942..f173cf55 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Store.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,13 +32,16 @@ * @author Magento Core Team */ -class Mage_Adminhtml_Block_System_Store_Grid_Render_Store extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_System_Store_Grid_Render_Store + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { public function render(Varien_Object $row) { if (!$row->getData($this->getColumn()->getIndex())) { return null; } - return '' . $row->getData($this->getColumn()->getIndex()) . ''; + return '' + . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . ''; } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Website.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Website.php index ad57b7cc..5061475d 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Website.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Grid/Render/Website.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,12 +31,16 @@ * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Block_System_Store_Grid_Render_Website extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract + +class Mage_Adminhtml_Block_System_Store_Grid_Render_Website + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { public function render(Varien_Object $row) { - return '' . $row->getData($this->getColumn()->getIndex()) . ''; + return '' + . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . ''; } } diff --git a/app/code/core/Mage/Adminhtml/Block/System/Store/Store.php b/app/code/core/Mage/Adminhtml/Block/System/Store/Store.php index 083730a4..d3872667 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Store/Store.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Store/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Variable.php b/app/code/core/Mage/Adminhtml/Block/System/Variable.php index 77352756..7fb3a5bd 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Variable.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Variable.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit.php b/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit.php index beab426d..f8206559 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit/Form.php index fa3626a6..fe4cd08a 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Variable/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/System/Variable/Grid.php b/app/code/core/Mage/Adminhtml/Block/System/Variable/Grid.php index da32640d..ef2a04b0 100644 --- a/app/code/core/Mage/Adminhtml/Block/System/Variable/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/System/Variable/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Template.php b/app/code/core/Mage/Adminhtml/Block/Template.php index 27a9e09a..a750e7f3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Template.php +++ b/app/code/core/Mage/Adminhtml/Block/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Text/List.php b/app/code/core/Mage/Adminhtml/Block/Text/List.php index f9af0dbf..85cd2bea 100644 --- a/app/code/core/Mage/Adminhtml/Block/Text/List.php +++ b/app/code/core/Mage/Adminhtml/Block/Text/List.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite.php index 80ea7eff..8fa04f09 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit.php index df3bf4ef..b48bcc40 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php index 9baa9948..0d4ca91d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -55,7 +55,13 @@ protected function _prepareForm() $product = Mage::registry('current_product'); $category = Mage::registry('current_category'); - $form = new Varien_Data_Form(array('id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post')); + $form = new Varien_Data_Form( + array( + 'id' => 'edit_form', + 'action' => $this->getData('action'), + 'method' => 'post' + ) + ); // set form data either from model values or from session $formValues = array( @@ -91,23 +97,77 @@ protected function _prepareForm() 'value' => $model->getIsSystem() )); + $isFilterAllowed = false; // get store switcher or a hidden field with its id if (!Mage::app()->isSingleStoreMode()) { + $stores = Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(); + $entityStores = array(); + $noStoreError = false; + + //showing websites that only associated to products + if ($product && $product->getId()) { + $entityStores = $product->getStoreIds() ? $product->getStoreIds() : array(); + if (!$entityStores) { + $stores = array(); //reset the stores + $noStoreError = $this->__('Chosen product does not associated with any website, so url rewrite is not possible.'); + } + //if category is chosen, reset stores which are not related with this category + if ($category && $category->getId()) { + $categoryStores = $category->getStoreIds() ? $category->getStoreIds() : array(); + $entityStores = array_intersect($entityStores, $categoryStores); + + } + $isFilterAllowed = true; + } elseif ($category && $category->getId()) { + $entityStores = $category->getStoreIds() ? $category->getStoreIds() : array(); + if (!$entityStores) { + $stores = array(); //reset the stores + $noStoreError = $this->__('Chosen category does not associated with any website, so url rewrite is not possible.'); + } + $isFilterAllowed = true; + } + + /* + * Stores should be filtered only if product and/or category is specified. + * If we use custom rewrite, all stores are accepted. + */ + if ($stores && $isFilterAllowed) { + foreach ($stores as $i => $store) { + if (isset($store['value']) && $store['value']) { + $found = false; + foreach ($store['value'] as $_k => $_v) { + if (isset($_v['value']) && in_array($_v['value'], $entityStores)) { + $found = true; + } else { + unset($stores[$i]['value'][$_k]); + } + } + if (!$found) { + unset($stores[$i]); + } + } + } + } + $element = $fieldset->addField('store_id', 'select', array( 'label' => Mage::helper('adminhtml')->__('Store'), 'title' => Mage::helper('adminhtml')->__('Store'), 'name' => 'store_id', 'required' => true, - 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(), + 'values' => $stores, 'disabled' => true, 'value' => $formValues['store_id'], )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $element->setRenderer($renderer); + if ($noStoreError) { + $element->setAfterElementHtml($noStoreError); + } if (!$model->getIsSystem()) { $element->unsetData('disabled'); } - } - else { - $fieldset->addField('store_id', ($model->getId() ? 'hidden' : 'select'), array( + } else { + $fieldset->addField('store_id', 'hidden', array( 'name' => 'store_id', 'value' => Mage::app()->getStore(true)->getId() )); @@ -139,16 +199,18 @@ protected function _prepareForm() 'value' => $formValues['target_path'], )); - // auto-generate paths for new urlrewrites + // auto-generate paths for new url rewrites if (!$model->getId()) { $_product = null; $_category = null; if ($category->getId() || $product->getId()) { $_category = $category; } + if ($product->getId()) { $_product = $product; } + if ($_category || $_product) { $catalogUrlModel = Mage::getSingleton('catalog/url'); $idPath->setValue($catalogUrlModel->generatePath('id', $_product, $_category)); @@ -156,13 +218,11 @@ protected function _prepareForm() $requestPath->setValue($catalogUrlModel->generatePath('request', $_product, $_category, '')); } $targetPath->setValue($catalogUrlModel->generatePath('target', $_product, $_category)); - } - else { + } else { $idPath->unsetData('disabled'); $targetPath->unsetData('disabled'); } - } - else { + } else { if (!$model->getProductId() && !$model->getCategoryId()) { $idPath->unsetData('disabled'); $targetPath->unsetData('disabled'); diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Grid.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Grid.php index 304b8438..45c3a56f 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Link.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Link.php index d197132d..545566c6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Link.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Link.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Selector.php b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Selector.php index 6447680f..38ed6468 100644 --- a/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Selector.php +++ b/app/code/core/Mage/Adminhtml/Block/Urlrewrite/Selector.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget.php b/app/code/core/Mage/Adminhtml/Block/Widget.php index e2c6e006..383a0049 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Accordion.php b/app/code/core/Mage/Adminhtml/Block/Widget/Accordion.php index 05ec179f..68ab48fb 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Accordion.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Accordion.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Accordion/Item.php b/app/code/core/Mage/Adminhtml/Block/Widget/Accordion/Item.php index 85aab314..e694b938 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Accordion/Item.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Accordion/Item.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Breadcrumbs.php b/app/code/core/Mage/Adminhtml/Block/Widget/Breadcrumbs.php index 7c025bf8..776e296a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Breadcrumbs.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Breadcrumbs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Button.php b/app/code/core/Mage/Adminhtml/Block/Widget/Button.php index 2382f694..0bb48a08 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Button.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Button.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -56,13 +56,16 @@ protected function _toHtml() $html = $this->getBeforeHtml().''.$this->getAfterHtml(); + . '>' .$this->getLabel().''.$this->getAfterHtml(); return $html; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Container.php b/app/code/core/Mage/Adminhtml/Block/Widget/Container.php index 26f61bfe..d3b84132 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Container.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Container.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -70,13 +70,18 @@ class Mage_Adminhtml_Block_Widget_Container extends Mage_Adminhtml_Block_Templat * @param string|null $placement area, that button should be displayed in ('header', 'footer', null) * @return Mage_Adminhtml_Block_Widget_Container */ - protected function _addButton($id, $data, $level = 0, $sortOrder = 100, $area = 'header') + protected function _addButton($id, $data, $level = 0, $sortOrder = 0, $area = 'header') { if (!isset($this->_buttons[$level])) { $this->_buttons[$level] = array(); } $this->_buttons[$level][$id] = $data; $this->_buttons[$level][$id]['area'] = $area; + if ($sortOrder) { + $this->_buttons[$level][$id]['sort_order'] = $sortOrder; + } else { + $this->_buttons[$level][$id]['sort_order'] = count($this->_buttons[$level]) * 10; + } return $this; } @@ -90,7 +95,7 @@ protected function _addButton($id, $data, $level = 0, $sortOrder = 100, $area = * @param string|null $placement area, that button should be displayed in ('header', 'footer', null) * @return Mage_Adminhtml_Block_Widget_Container */ - public function addButton($id, $data, $level = 0, $sortOrder = 100, $area = 'header') + public function addButton($id, $data, $level = 0, $sortOrder = 0, $area = 'header') { return $this->_addButton($id, $data, $level, $sortOrder, $area); } @@ -216,7 +221,15 @@ public function getButtonsHtml($area = null) { $out = ''; foreach ($this->_buttons as $level => $buttons) { + $_buttons = array(); foreach ($buttons as $id => $data) { + $_buttons[$data['sort_order']]['id'] = $id; + $_buttons[$data['sort_order']]['data'] = $data; + } + ksort($_buttons); + foreach ($_buttons as $button) { + $id = $button['id']; + $data = $button['data']; if ($area && isset($data['area']) && ($area != $data['area'])) { continue; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form.php index 82cdb6e6..a0de4aad 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -52,7 +52,7 @@ protected function _construct() $this->setDestElementId('edit_form'); $this->setShowGlobalIcon(false); } - + /** * Preparing global layout * @@ -71,7 +71,7 @@ protected function _prepareLayout() Varien_Data_Form::setFieldsetElementRenderer( $this->getLayout()->createBlock('adminhtml/widget_form_renderer_fieldset_element') ); - + return parent::_prepareLayout(); } @@ -147,7 +147,7 @@ protected function _beforeToHtml() } /** - * Initialize form fileds values + * Initialize form fields values * Method will be called after prepareForm and can be used for field values initialization * * @return Mage_Adminhtml_Block_Widget_Form @@ -197,11 +197,16 @@ protected function _setFieldset($attributes, $fieldset, $exclude=array()) $element->setAfterElementHtml($this->_getAdditionalElementHtml($element)); - if ($inputType == 'select' || $inputType == 'multiselect') { + if ($inputType == 'select') { $element->setValues($attribute->getSource()->getAllOptions(true, true)); - } elseif ($inputType == 'date') { + } else if ($inputType == 'multiselect') { + $element->setValues($attribute->getSource()->getAllOptions(false, true)); + $element->setCanBeEmpty(true); + } else if ($inputType == 'date') { $element->setImage($this->getSkinUrl('images/grid-cal.gif')); - $element->setFormat(Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT)); + $element->setFormat(Mage::app()->getLocale()->getDateFormatWithLongYear()); + } else if ($inputType == 'multiline') { + $element->setLineCount($attribute->getMultilineCount()); } } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Container.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Container.php index bb0af66b..6730a373 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Container.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Container.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element.php index 6c9fcfb2..eedbbe63 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Dependence.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Dependence.php index 17e27380..d4d53e75 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Dependence.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Dependence.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -73,8 +73,6 @@ public function addFieldMap($fieldId, $fieldName) /** * Register field name dependence one from each other by specified values * - * @TODO: multiple values per dependency is not implemented. The values OR comparison is anticipated - * * @param string $fieldName * @param string $fieldNameFrom * @param string|array $refValues @@ -82,9 +80,6 @@ public function addFieldMap($fieldId, $fieldName) */ public function addFieldDependence($fieldName, $fieldNameFrom, $refValues) { - if (is_array($refValues)) { - Mage::throwException('Dependency from multiple values is not implemented yet. Please fix to your widget.xml'); - } $this->_depends[$fieldName][$fieldNameFrom] = $refValues; return $this; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Gallery.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Gallery.php index 78d42409..dcec7f96 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Gallery.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Element/Gallery.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Element.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Element.php index bd609d6a..78834247 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Element.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset.php index 4492507e..e32cd21c 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset/Element.php b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset/Element.php index b0d5d39c..9e3a39dc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset/Element.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Form/Renderer/Fieldset/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,7 +31,8 @@ * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element extends Mage_Adminhtml_Block_Template implements Varien_Data_Form_Element_Renderer_Interface +class Mage_Adminhtml_Block_Widget_Form_Renderer_Fieldset_Element extends Mage_Adminhtml_Block_Template + implements Varien_Data_Form_Element_Renderer_Interface { protected $_element; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php index 98824b0b..c5c38be9 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -161,6 +161,13 @@ class Mage_Adminhtml_Block_Widget_Grid extends Mage_Adminhtml_Block_Widget */ protected $_exportTypes = array(); + /** + * Rows per page for import + * + * @var int + */ + protected $_exportPageSize = 1000; + /** * Massaction row id field * @@ -168,6 +175,13 @@ class Mage_Adminhtml_Block_Widget_Grid extends Mage_Adminhtml_Block_Widget */ protected $_massactionIdField = null; + /** + * Massaction row id filter + * + * @var string + */ + protected $_massactionIdFilter = null; + /** * Massaction block name * @@ -311,6 +325,23 @@ public function addColumn($columnId, $column) return $this; } + /** + * Remove existing column + * + * @param string $columnId + * @return Mage_Adminhtml_Block_Widget_Grid + */ + public function removeColumn($columnId) + { + if (isset($this->_columns[$columnId])) { + unset($this->_columns[$columnId]); + if ($this->_lastColumnId == $columnId) { + $this->_lastColumnId = key($this->_columns); + } + } + return $this; + } + /** * Add column to grid after specified column. * @@ -418,7 +449,10 @@ public function getColumns() protected function _setFilterValues($data) { foreach ($this->getColumns() as $columnId => $column) { - if (isset($data[$columnId]) && (!empty($data[$columnId]) || strlen($data[$columnId]) > 0) && $column->getFilter()) { + if (isset($data[$columnId]) + && (!empty($data[$columnId]) || strlen($data[$columnId]) > 0) + && $column->getFilter() + ) { $column->getFilter()->setValue($data[$columnId]); $this->_addColumnFilterToCollection($column); } @@ -442,6 +476,23 @@ protected function _addColumnFilterToCollection($column) return $this; } + /** + * Sets sorting order by some column + * + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @return Mage_Adminhtml_Block_Widget_Grid + */ + protected function _setCollectionOrder($column) + { + $collection = $this->getCollection(); + if ($collection) { + $columnIndex = $column->getFilterIndex() ? + $column->getFilterIndex() : $column->getIndex(); + $collection->setOrder($columnIndex, strtoupper($column->getDir())); + } + return $this; + } + /** * Prepare grid collection object * @@ -475,9 +526,7 @@ protected function _prepareCollection() if (isset($this->_columns[$columnId]) && $this->_columns[$columnId]->getIndex()) { $dir = (strtolower($dir)=='desc') ? 'desc' : 'asc'; $this->_columns[$columnId]->setDir($dir); - $column = $this->_columns[$columnId]->getFilterIndex() ? - $this->_columns[$columnId]->getFilterIndex() : $this->_columns[$columnId]->getIndex(); - $this->getCollection()->setOrder($column , $dir); + $this->_setCollectionOrder($this->_columns[$columnId]); } if (!$this->_isExport) { @@ -501,8 +550,8 @@ protected function _decodeFilter(&$value) protected function _preparePage() { - $this->getCollection()->setPageSize($this->getParam($this->getVarNameLimit(), $this->_defaultLimit)); - $this->getCollection()->setCurPage($this->getParam($this->getVarNamePage(), $this->_defaultPage)); + $this->getCollection()->setPageSize((int) $this->getParam($this->getVarNameLimit(), $this->_defaultLimit)); + $this->getCollection()->setCurPage((int) $this->getParam($this->getVarNamePage(), $this->_defaultPage)); } protected function _prepareColumns() @@ -547,11 +596,12 @@ protected function _prepareMassactionColumn() $columnId = 'massaction'; $massactionColumn = $this->getLayout()->createBlock('adminhtml/widget_grid_column') ->setData(array( - 'index' => $this->getMassactionIdField(), - 'type' => 'massaction', - 'name' => $this->getMassactionBlock()->getFormFieldName(), - 'align' => 'center', - 'is_system' => true + 'index' => $this->getMassactionIdField(), + 'filter_index' => $this->getMassactionIdFilter(), + 'type' => 'massaction', + 'name' => $this->getMassactionBlock()->getFormFieldName(), + 'align' => 'center', + 'is_system' => true )); if ($this->getNoFilterMassactionColumn()) { @@ -751,7 +801,7 @@ public function setDefaultFilter($filter) /** * Retrieve grid export types * - * @return array + * @return array|false */ public function getExportTypes() { @@ -786,6 +836,23 @@ public function getRssLists() return empty($this->_rssLists) ? false : $this->_rssLists; } + /** + * Returns url for RSS + * Can be overloaded in descendant classes to perform custom changes to url passed to addRssList() + * + * @param string $url + * @return string + */ + protected function _getRssUrl($url) + { + $urlModel = Mage::getModel('core/url'); + if (Mage::app()->getStore()->getStoreInUrl()) { + // Url in 'admin' store view won't be accessible, so form it in default store view frontend + $urlModel->setStore(Mage::app()->getDefaultStoreView()); + } + return $urlModel->getUrl($url); + } + /** * Add new rss list to grid * @@ -797,7 +864,7 @@ public function addRssList($url, $label) { $this->_rssLists[] = new Varien_Object( array( - 'url' => Mage::getModel('core/url')->getUrl($url), + 'url' => $this->_getRssUrl($url), 'label' => $label ) ); @@ -879,7 +946,7 @@ public function _exportIterateCollection($callback, array $args) while ($break !== true) { $collection = clone $originalCollection; - $collection->setPageSize(1000); + $collection->setPageSize($this->_exportPageSize); $collection->setCurPage($page); $collection->load(); if (is_null($count)) { @@ -981,7 +1048,8 @@ public function getCsv() $data = array(); foreach ($this->_columns as $column) { if (!$column->getIsSystem()) { - $data[] = '"'.str_replace(array('"', '\\'), array('""', '\\\\'), $column->getRowFieldExport($item)).'"'; + $data[] = '"' . str_replace(array('"', '\\'), array('""', '\\\\'), + $column->getRowFieldExport($item)) . '"'; } } $csv.= implode(',', $data)."\n"; @@ -992,7 +1060,8 @@ public function getCsv() $data = array(); foreach ($this->_columns as $column) { if (!$column->getIsSystem()) { - $data[] = '"'.str_replace(array('"', '\\'), array('""', '\\\\'), $column->getRowFieldExport($this->getTotals())).'"'; + $data[] = '"' . str_replace(array('"', '\\'), array('""', '\\\\'), + $column->getRowFieldExport($this->getTotals())) . '"'; } } $csv.= implode(',', $data)."\n"; @@ -1226,7 +1295,7 @@ public function getRowId($row) } /** - * Retrive massaction row identifier field + * Retrieve massaction row identifier field * * @return string */ @@ -1247,6 +1316,28 @@ public function setMassactionIdField($idField) return $this; } + /** + * Retrieve massaction row identifier filter + * + * @return string + */ + public function getMassactionIdFilter() + { + return $this->_massactionIdFilter; + } + + /** + * Set massaction row identifier filter + * + * @param string $idFilter + * @return Mage_Adminhtml_Block_Widget_Grid + */ + public function setMassactionIdFilter($idFilter) + { + $this->_massactionIdFilter = $idFilter; + return $this; + } + /** * Retrive massaction block name * @@ -1571,4 +1662,17 @@ public function setEmptyCellLabel($label) $this->_emptyCellLabel = $label; return $this; } + + /** + * Return row url for js event handlers + * + * @param Mage_Catalog_Model_Product|Varien_Object + * @return string + */ + public function getRowUrl($item) + { + $res = parent::getRowUrl($item); + return ($res ? $res : '#'); + } + } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Block.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Block.php index 72eb8006..1b1ee2cf 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Block.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Block.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column.php index 463837cd..0ef258bf 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -293,8 +293,10 @@ public function getRenderer() return $this->_renderer; } - public function setFilter($column) + public function setFilter($filterClass) { + $this->_filter = $this->getLayout()->createBlock($filterClass) + ->setColumn($this); } protected function _getFilterByType() diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Abstract.php index 09624d3f..c84bfd68 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,51 +31,102 @@ * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract extends Mage_Adminhtml_Block_Abstract implements Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Interface +class Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract extends Mage_Adminhtml_Block_Abstract + implements Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Interface { + /** + * Column related to filter + * + * @var Mage_Adminhtml_Block_Widget_Grid_Column + */ protected $_column; + /** + * Set column related to filter + * + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @return Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract + */ public function setColumn($column) { $this->_column = $column; return $this; } + /** + * Retrieve column related to filter + * + * @return Mage_Adminhtml_Block_Widget_Grid_Column + */ public function getColumn() { return $this->_column; } + /** + * Retrieve html name of filter + * + * @return string + */ protected function _getHtmlName() { return $this->getColumn()->getId(); } + /** + * Retrieve html id of filter + * + * @return string + */ protected function _getHtmlId() { - return $this->getColumn()->getGrid()->getVarNameFilter().'_'.$this->getColumn()->getId(); + return $this->getColumn()->getGrid()->getId() . '_' + . $this->getColumn()->getGrid()->getVarNameFilter() . '_' + . $this->getColumn()->getId(); } - public function getEscapedValue($index=null) + /** + * Retrieve escaped value + * + * @param mixed $index + * @return string + */ + public function getEscapedValue($index = null) { return htmlspecialchars($this->getValue($index)); } + /** + * Retrieve condition + * + * @return array + */ public function getCondition() { - return array('like'=>'%'.$this->_escapeValue($this->getValue()).'%'); + $helper = Mage::getResourceHelper('core'); + $likeExpression = $helper->addLikeEscape($this->getValue(), array('position' => 'any')); + return array('like' => $likeExpression); } + /** + * @deprecated after 1.5.0.0 + * @param $value + * @return mixed + */ protected function _escapeValue($value) { - return str_replace('_', '\_', $value); + return str_replace('_', '\_', str_replace('\\', '\\\\', $value)); } + /** + * Retrieve filter html + * + * @return string + */ public function getHtml() { return ''; } } - diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Checkbox.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Checkbox.php index 68b4606c..ffa8effd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Checkbox.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Checkbox.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Country.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Country.php index 330dfff8..2323bff2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Country.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Country.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Date.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Date.php index 6a5c2627..a25feddc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Date.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Date.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Datetime.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Datetime.php index 8a63e383..89f4eab9 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Datetime.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Datetime.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -51,8 +51,13 @@ public function getValue($index=null) } if (!empty($value['to']) && !$this->getColumn()->getFilterTime()) { $datetimeTo = $value['to']; - //set end of the day - $datetimeTo->addSecond(self::END_OF_DAY_IN_SECONDS); + + //calculate end date considering timezone specification + $datetimeTo->setTimezone( + Mage::app()->getStore()->getConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_TIMEZONE) + ); + $datetimeTo->addDay(1)->subSecond(1); + $datetimeTo->setTimezone(Mage_Core_Model_Locale::DEFAULT_TIMEZONE); } return $value; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Interface.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Interface.php index 076a789b..3458a3f6 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Interface.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Massaction.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Massaction.php index 6229a25e..5b25af02 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Massaction.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Massaction.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Price.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Price.php index 9f589003..845241db 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Price.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Price.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Radio.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Radio.php index 4d138eb2..5ed44ff0 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Radio.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Radio.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Range.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Range.php index d33c7851..2ca40bb4 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Range.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Range.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Select.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Select.php index 54282b0d..1b54ddc9 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Select.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Select.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Store.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Store.php index 81733b65..1c089335 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Store.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,11 +30,17 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Store extends Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Store + extends Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract { + /** + * Render HTML of the element + * + * @return string + */ public function getHtml() { $storeModel = Mage::getSingleton('adminhtml/system_store'); @@ -45,10 +51,12 @@ public function getHtml() $allShow = $this->getColumn()->getStoreAll(); - $html = 'getColumn()->getValidateClass() . '>'; $value = $this->getColumn()->getValue(); if ($allShow) { - $html .= ''; + $html .= ''; } else { $html .= ''; } @@ -65,14 +73,17 @@ public function getHtml() } if (!$websiteShow) { $websiteShow = true; - $html .= ''; + $html .= ''; } if (!$groupShow) { $groupShow = true; - $html .= ''; + $html .= ''; } $value = $this->getValue(); - $html .= ''; + $selected = $value == $store->getId() ? ' selected="selected"' : ''; + $html .= ''; } if ($groupShow) { $html .= ''; @@ -87,6 +98,11 @@ public function getHtml() return $html; } + /** + * Form condition from element's value + * + * @return array|null + */ public function getCondition() { if (is_null($this->getValue())) { @@ -94,8 +110,7 @@ public function getCondition() } if ($this->getValue() == '_deleted_') { return array('null' => true); - } - else { + } else { return array('eq' => $this->getValue()); } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Text.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Text.php index 9551c885..533d326f 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Text.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Text.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Theme.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Theme.php index 24969060..7e48fd4d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Theme.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Filter/Theme.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Abstract.php index c5bfdc0c..786864e3 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,7 +32,8 @@ * @author Magento Core Team */ -abstract class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract extends Mage_Adminhtml_Block_Abstract implements Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Interface +abstract class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract + extends Mage_Adminhtml_Block_Abstract implements Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Interface { protected $_defaultWidth; protected $_column; @@ -58,7 +59,7 @@ public function render(Varien_Object $row) { if ($this->getColumn()->getEditable()) { $value = $this->_getValue($row); - return $value + return $value . ($this->getColumn()->getEditOnly() ? '' : ($value != '' ? '' : ' ')) . $this->_getInputValueElement($row); } @@ -91,7 +92,10 @@ protected function _getValue(Varien_Object $row) public function _getInputValueElement(Varien_Object $row) { - return ''; + return ''; } protected function _getInputValue(Varien_Object $row) @@ -101,19 +105,17 @@ protected function _getInputValue(Varien_Object $row) public function renderHeader() { - $out = ''; - if ( (false !== $this->getColumn()->getGrid()->getSortable()) && (false !== $this->getColumn()->getSortable()) ) { - + if (false !== $this->getColumn()->getGrid()->getSortable() && false !== $this->getColumn()->getSortable()) { $className = 'not-sort'; $dir = strtolower($this->getColumn()->getDir()); $nDir= ($dir=='asc') ? 'desc' : 'asc'; if ($this->getColumn()->getDir()) { $className = 'sort-arrow-' . $dir; } - $out = ''.$this->getColumn()->getHeader().''; - } - else { + $out = '' + . $this->getColumn()->getHeader().''; + } else { $out = $this->getColumn()->getHeader(); } return $out; @@ -122,13 +124,6 @@ public function renderHeader() public function renderProperty() { $out = ''; - /** - * Now we generate 2 "col" definition instead span=2 - */ -// if ($this->getColumn()->getEditable() && !$this->getColumn()->getEditOnly()) { -// $out .= ' span="2"'; -// } - $width = $this->_defaultWidth; if ($this->getColumn()->hasData('width')) { diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Action.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Action.php index 669796d2..02253426 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Action.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,9 +29,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Action extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Action + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text { /** @@ -44,11 +45,11 @@ public function render(Varien_Object $row) { $actions = $this->getColumn()->getActions(); if ( empty($actions) || !is_array($actions) ) { - return ' '; + return ' '; } if(sizeof($actions)==1 && !$this->getColumn()->getNoLink()) { - foreach ($actions as $action){ + foreach ($actions as $action) { if ( is_array($action) ) { return $this->_toLinkHtml($action, $row); } @@ -82,7 +83,7 @@ protected function _toOptionHtml($action, Varien_Object $row) $actionCaption = ''; $this->_transformActionData($action, $actionCaption, $row); - $htmlAttibutes = array('value'=>$this->htmlEscape(Mage::helper('core')->jsonEncode($action))); + $htmlAttibutes = array('value'=>$this->escapeHtml(Mage::helper('core')->jsonEncode($action))); $actionAttributes->setData($htmlAttibutes); return ''; } @@ -103,7 +104,7 @@ protected function _toLinkHtml($action, Varien_Object $row) if(isset($action['confirm'])) { $action['onclick'] = 'return window.confirm(\'' - . addslashes($this->htmlEscape($action['confirm'])) + . addslashes($this->escapeHtml($action['confirm'])) . '\')'; unset($action['confirm']); } @@ -122,15 +123,15 @@ protected function _toLinkHtml($action, Varien_Object $row) */ protected function _transformActionData(&$action, &$actionCaption, Varien_Object $row) { - foreach ( $action as $attibute => $value ) { - if(isset($action[$attibute]) && !is_array($action[$attibute])) { - $this->getColumn()->setFormat($action[$attibute]); - $action[$attibute] = parent::render($row); + foreach ( $action as $attribute => $value ) { + if(isset($action[$attribute]) && !is_array($action[$attribute])) { + $this->getColumn()->setFormat($action[$attribute]); + $action[$attribute] = parent::render($row); } else { $this->getColumn()->setFormat(null); } - switch ($attibute) { + switch ($attribute) { case 'caption': $actionCaption = $action['caption']; unset($action['caption']); @@ -151,7 +152,8 @@ protected function _transformActionData(&$action, &$actionCaption, Varien_Object break; case 'popup': - $action['onclick'] = 'popWin(this.href, \'_blank\', \'width=800,height=700,resizable=1,scrollbars=1\');return false;'; + $action['onclick'] = + 'popWin(this.href,\'_blank\',\'width=800,height=700,resizable=1,scrollbars=1\');return false;'; break; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Checkbox.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Checkbox.php index 1fb3282d..7d3861f5 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Checkbox.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Checkbox.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,13 +29,19 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Checkbox extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Checkbox + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 55; protected $_values; + /** + * Returns values of the column + * + * @return array + */ public function getValues() { if (is_null($this->_values)) { @@ -79,11 +85,26 @@ public function render(Varien_Object $row) return $this->_getCheckboxHtml($v, $checked); } + /** + * @param string $value Value of the element + * @param bool $checked Whether it is checked + * @return string + */ protected function _getCheckboxHtml($value, $checked) { - return 'getDisabled().'/>'; + $html = 'getColumn()->getFieldName() . '" '; + $html .= 'value="' . $this->escapeHtml($value) . '" '; + $html .= 'class="'. ($this->getColumn()->getInlineCss() ? $this->getColumn()->getInlineCss() : 'checkbox') .'"'; + $html .= $checked . $this->getDisabled() . '/>'; + return $html; } + /** + * Renders header of the column + * + * @return string + */ public function renderHeader() { if($this->getColumn()->getHeader()) { @@ -99,6 +120,11 @@ public function renderHeader() if ($this->getColumn()->getDisabled()) { $disabled = ' disabled="disabled"'; } - return ''; + $html = 'getColumn()->getFieldName() . '" '; + $html .= 'onclick="' . $this->getColumn()->getGrid()->getJsObjectName() . '.checkCheckboxes(this)" '; + $html .= 'class="checkbox"' . $checked . $disabled . ' '; + $html .= 'title="'.Mage::helper('adminhtml')->__('Select All') . '"/>'; + return $html; } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Concat.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Concat.php index 471c01dc..bb1dae1b 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Concat.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Concat.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Concat extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Concat + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Country.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Country.php index 4eab53f2..3f76302e 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Country.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Country.php @@ -20,16 +20,19 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Country column renderer * - * @author Magento Core Team + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Country extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Country + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Render country grid column @@ -42,7 +45,7 @@ public function render(Varien_Object $row) if ($data = $row->getData($this->getColumn()->getIndex())) { $name = Mage::app()->getLocale()->getCountryTranslation($data); if (empty($name)) { - $name = $data; + $name = $this->escapeHtml($data); } return $name; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Currency.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Currency.php index 175334cc..f27ec269 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Currency.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Currency extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Currency + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 100; @@ -49,7 +50,7 @@ class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Currency extends Mage_Adm */ public function render(Varien_Object $row) { - if ($data = $row->getData($this->getColumn()->getIndex())) { + if ($data = (string)$row->getData($this->getColumn()->getIndex())) { $currency_code = $this->_getCurrencyCode($row); if (!$currency_code) { @@ -65,6 +66,12 @@ public function render(Varien_Object $row) return $this->getColumn()->getDefault(); } + /** + * Returns currency code, false on error + * + * @param $row + * @return string|false + */ protected function _getCurrencyCode($row) { if ($code = $this->getColumn()->getCurrencyCode()) { @@ -76,6 +83,12 @@ protected function _getCurrencyCode($row) return false; } + /** + * Get rate for current row, 1 by default + * + * @param $row + * @return float|int + */ protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { @@ -87,6 +100,11 @@ protected function _getRate($row) return 1; } + /** + * Returns HTML for CSS + * + * @return string + */ public function renderCss() { return parent::renderCss() . ' a-right'; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Date.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Date.php index 81607370..1d911635 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Date.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Date.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Date extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Date + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 160; /** @@ -56,7 +57,7 @@ protected function _getFormat() ); } catch (Exception $e) { - + Mage::logException($e); } } $format = self::$_format; @@ -76,15 +77,18 @@ public function render(Varien_Object $row) $format = $this->_getFormat(); try { if($this->getColumn()->getGmtoffset()) { - $data = Mage::app()->getLocale()->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); + $data = Mage::app()->getLocale() + ->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); } else { - $data = Mage::getSingleton('core/locale')->date($data, Zend_Date::ISO_8601, null, false)->toString($format); + $data = Mage::getSingleton('core/locale') + ->date($data, Zend_Date::ISO_8601, null, false)->toString($format); } } catch (Exception $e) { if($this->getColumn()->getTimezone()) { - $data = Mage::app()->getLocale()->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); + $data = Mage::app()->getLocale() + ->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); } else { $data = Mage::getSingleton('core/locale')->date($data, null, null, false)->toString($format); } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Datetime.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Datetime.php index 0a4db0f9..863c0731 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Datetime.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Datetime.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Datetime extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Datetime + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Date format string @@ -55,7 +56,7 @@ protected function _getFormat() ); } catch (Exception $e) { - + Mage::logException($e); } } $format = self::$_format; @@ -74,11 +75,13 @@ public function render(Varien_Object $row) if ($data = $this->_getValue($row)) { $format = $this->_getFormat(); try { - $data = Mage::app()->getLocale()->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); + $data = Mage::app()->getLocale() + ->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); } catch (Exception $e) { - $data = Mage::app()->getLocale()->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); + $data = Mage::app()->getLocale() + ->date($data, Varien_Date::DATETIME_INTERNAL_FORMAT)->toString($format); } return $data; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Input.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Input.php index 004e4d83..395ecaf2 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Input.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Input.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,9 +29,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Input extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Input + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_values; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Interface.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Interface.php index e6f063c1..baaa488a 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Interface.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,13 +29,26 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ interface Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Interface { + /** + * Set column for renderer + * + * @abstract + * @param $column + * @return void + */ public function setColumn($column); + /** + * Returns row associated with the renderer + * + * @abstract + * @return void + */ public function getColumn(); /** diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Ip.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Ip.php index 51774ae3..d24a3193 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Ip.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Ip.php @@ -20,12 +20,16 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Long INT to IP renderer + * + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team */ class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Ip extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Longtext.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Longtext.php index 34c65fd0..d21a36b1 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Longtext.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Longtext.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,9 +29,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Longtext extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Longtext + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Render contents as a long text @@ -40,7 +41,7 @@ class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Longtext extends Mage_Adm * Also it can be html-escaped and nl2br() * * @param Varien_Object $row - * @return unknown + * @return string */ public function render(Varien_Object $row) { @@ -54,7 +55,7 @@ public function render(Varien_Object $row) } $text = Mage::helper('core/string')->truncate(parent::_getValue($row), $truncateLength); if ($this->getColumn()->getEscape()) { - $text = $this->htmlEscape($text); + $text = $this->escapeHtml($text); } if ($this->getColumn()->getNl2br()) { $text = nl2br($text); diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Massaction.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Massaction.php index c96025dc..ea9f3c65 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Massaction.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Massaction.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,17 +30,28 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Massaction extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Checkbox +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Massaction + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Checkbox { protected $_defaultWidth = 20; + /** + * Render header of the row + * + * @return string + */ public function renderHeader() { return ' '; } + /** + * Render HTML properties + * + * @return string + */ public function renderProperty() { $out = parent::renderProperty(); @@ -49,18 +60,32 @@ public function renderProperty() return $out; } + /** + * Returns HTML of the object + * + * @param Varien_Object $row + * @return string + */ public function render(Varien_Object $row) { - if ($this->getColumn()->getGrid()->getMassactionIdFieldOnlyIndexValue()){ - $this->setNoObjectId(true); - } + if ($this->getColumn()->getGrid()->getMassactionIdFieldOnlyIndexValue()) { + $this->setNoObjectId(true); + } return parent::render($row); } - // + /** + * Returns HTML of the checkbox + * + * @param string $value + * @param bool $checked + * @return string + */ protected function _getCheckboxHtml($value, $checked) { - return ''; + $html = 'escapeHtml($value) . '" class="massaction-checkbox"' . $checked . '/>'; + return $html; } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Number.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Number.php index 66dab14c..5fd16110 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Number.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Number.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,13 +29,19 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ - -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Number extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Number + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 100; + /** + * Returns value of the row + * + * @param Varien_Object $row + * @return mixed|string + */ protected function _getValue(Varien_Object $row) { $data = parent::_getValue($row); @@ -50,6 +56,11 @@ protected function _getValue(Varien_Object $row) return $this->getColumn()->getDefault(); } + /** + * Renders CSS + * + * @return string + */ public function renderCss() { return parent::renderCss() . ' a-right'; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Options.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Options.php index 43fcdf91..6c0238bc 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Options.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Options.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,9 +29,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Options extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Options + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text { /** * Render a grid cell as options @@ -49,18 +50,18 @@ public function render(Varien_Object $row) $res = array(); foreach ($value as $item) { if (isset($options[$item])) { - $res[] = $options[$item]; + $res[] = $this->escapeHtml($options[$item]); } elseif ($showMissingOptionValues) { - $res[] = $item; + $res[] = $this->escapeHtml($item); } } return implode(', ', $res); + } elseif (isset($options[$value])) { + return $this->escapeHtml($options[$value]); + } elseif (in_array($value, $options)) { + return $this->escapeHtml($value); } - elseif (isset($options[$value])) { - return $options[$value]; - } - return ''; } } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Price.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Price.php index 9153923a..c647cf71 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Price.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Price.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ - -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Price extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Price + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 100; /** @@ -63,6 +63,12 @@ public function render(Varien_Object $row) return $this->getColumn()->getDefault(); } + /** + * Returns currency code for the row, false on error + * + * @param Varien_Object $row + * @return string|bool + */ protected function _getCurrencyCode($row) { if ($code = $this->getColumn()->getCurrencyCode()) { @@ -74,6 +80,12 @@ protected function _getCurrencyCode($row) return false; } + /** + * Returns rate for the row, 1 by default + * + * @param Varien_Object $row + * @return float|int + */ protected function _getRate($row) { if ($rate = $this->getColumn()->getRate()) { @@ -85,6 +97,11 @@ protected function _getRate($row) return 1; } + /** + * Renders CSS + * + * @return string + */ public function renderCss() { return parent::renderCss() . ' a-right'; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Radio.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Radio.php index 71505c3b..a14b0076 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Radio.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Radio.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,13 +29,19 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Radio extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Radio + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_defaultWidth = 55; protected $_values; + /** + * Returns all values for the column + * + * @return array + */ public function getValues() { if (is_null($this->_values)) { @@ -55,11 +61,12 @@ public function render(Varien_Object $row) $value = $row->getData($this->getColumn()->getIndex()); if (is_array($values)) { $checked = in_array($value, $values) ? ' checked="checked"' : ''; - } - else { + } else { $checked = ($value === $this->getColumn()->getValue()) ? ' checked="checked"' : ''; } - return ''; + $html = 'getId() . '" class="radio"' . $checked . '/>'; + return $html; } /* diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Select.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Select.php index e572f744..f63c5dcd 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Select.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Select.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Select extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Select + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** @@ -43,11 +44,13 @@ class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Select extends Mage_Admin */ public function render(Varien_Object $row) { - $html = 'getColumn()->getValidateClass() . '>'; $value = $row->getData($this->getColumn()->getIndex()); foreach ($this->getColumn()->getOptions() as $val => $label){ $selected = ( ($val == $value && (!is_null($value))) ? ' selected="selected"' : '' ); - $html.= ''; + $html .= ''; } $html.=''; return $html; diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Store.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Store.php index df006f18..69850cff 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Store.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,9 +30,10 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Store extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Store + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { protected $_skipAllStoresLabel = false; protected $_skipEmptyStoresLabel = false; @@ -93,7 +94,7 @@ public function render(Varien_Object $row) return $out; } - if (empty($origStores)&& !$skipEmptyStoresLabel) { + if (empty($origStores) && !$skipEmptyStoresLabel) { return ''; } if (!is_array($origStores)) { diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Text.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Text.php index 82aeb13c..691bf741 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Text.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Text.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,10 +29,11 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Text + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Format variables pattern @@ -55,18 +56,18 @@ public function _getValue(Varien_Object $row) // If no format and it column not filtered specified return data as is. $data = parent::_getValue($row); $string = is_null($data) ? $defaultValue : $data; - return htmlspecialchars($string); + return $this->escapeHtml($string); } elseif (preg_match_all($this->_variablePattern, $format, $matches)) { // Parsing of format string - $formatedString = $format; + $formattedString = $format; foreach ($matches[0] as $matchIndex=>$match) { $value = $row->getData($matches[1][$matchIndex]); - $formatedString = str_replace($match, $value, $formatedString); + $formattedString = str_replace($match, $value, $formattedString); } - return $formatedString; + return $formattedString; } else { - return htmlspecialchars($format); + return $this->escapeHtml($format); } } } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Theme.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Theme.php index f6115738..85d2a882 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Theme.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Theme.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,7 +31,8 @@ * @package Mage_Adminhtml * @author Magento Core Team */ -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Theme extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Theme + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Renders grid column @@ -47,7 +48,7 @@ public function render(Varien_Object $row) $value = 'all'; } - return $this->_getValueLabel($options, $value); + return $this->escapeHtml($this->_getValueLabel($options, $value)); } /** diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Wrapline.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Wrapline.php index e49ce982..e593b178 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Wrapline.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Wrapline.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,9 +29,10 @@ * * @category Mage * @package Mage_Adminhtml + * @author Magento Core Team */ - -class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Wrapline extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Wrapline + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract { /** * Default max length of a line at one row @@ -50,9 +51,11 @@ public function render(Varien_Object $row) { $line = parent::_getValue($row); $wrappedLine = ''; - $lineLength = ($this->getColumn()->getData('lineLength')?$this->getColumn()->getData('lineLength'):$this->_defaultMaxLineLength); - for($i=0, $n=floor(Mage::helper('core/string')->strlen($line)/$lineLength); $i<=$n; $i++) { - $wrappedLine .= Mage::helper('core/string')->substr($line, ($lineLength*$i), $lineLength)."
    "; + $lineLength = $this->getColumn()->getData('lineLength') + ? $this->getColumn()->getData('lineLength') + : $this->_defaultMaxLineLength; + for($i = 0, $n = floor(Mage::helper('core/string')->strlen($line) / $lineLength); $i <= $n; $i++) { + $wrappedLine .= Mage::helper('core/string')->substr($line, ($lineLength * $i), $lineLength) . "
    "; } return $wrappedLine; } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Container.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Container.php index 0e85d045..dae7990d 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Container.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Container.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,12 +35,19 @@ class Mage_Adminhtml_Block_Widget_Grid_Container extends Mage_Adminhtml_Block_Widget_Container { - protected $_addButtonLabel = 'Add New'; - protected $_backButtonLabel = 'Back'; + protected $_addButtonLabel; + protected $_backButtonLabel; protected $_blockGroup = 'adminhtml'; public function __construct() { + if (is_null($this->_addButtonLabel)) { + $this->_addButtonLabel = $this->__('Add New'); + } + if(is_null($this->_backButtonLabel)) { + $this->_backButtonLabel = $this->__('Back'); + } + parent::__construct(); $this->setTemplate('widget/grid/container.phtml'); diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction.php index f40d65b6..51a2db9c 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Abstract.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Abstract.php index 762e3f82..974d53ce 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Abstract.php @@ -20,13 +20,15 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Grid widget massaction block * + * @method Mage_Sales_Model_Quote setHideFormElement(boolean $value) Hide Form element to prevent IE errors + * @method boolean getHideFormElement() * @category Mage * @package Mage_Adminhtml * @author Magento Core Team @@ -40,6 +42,9 @@ abstract class Mage_Adminhtml_Block_Widget_Grid_Massaction_Abstract extends Mage */ protected $_items = array(); + /** + * Sets Massaction template + */ public function __construct() { parent::__construct(); @@ -187,10 +192,8 @@ public function getSelectedJson() if($selected = $this->getRequest()->getParam($this->getFormFieldNameInternal())) { $selected = explode(',', $selected); return join(',', $selected); -// return Mage::helper('core')->jsonEncode($selected); } else { return ''; -// return '[]'; } } @@ -221,13 +224,14 @@ public function getApplyButtonHtml() public function getJavaScript() { - return " - var {$this->getJsObjectName()} = new varienGridMassaction('{$this->getHtmlId()}', {$this->getGridJsObjectName()}, '{$this->getSelectedJson()}', '{$this->getFormFieldNameInternal()}', '{$this->getFormFieldName()}'); - {$this->getJsObjectName()}.setItems({$this->getItemsJson()}); - {$this->getJsObjectName()}.setGridIds('{$this->getGridIdsJson()}'); - ". ($this->getUseAjax() ? "{$this->getJsObjectName()}.setUseAjax(true);" : '') . " - ". ($this->getUseSelectAll() ? "{$this->getJsObjectName()}.setUseSelectAll(true);" : '') . - "{$this->getJsObjectName()}.errorText = '{$this->getErrorText()}';"; + return " var {$this->getJsObjectName()} = new varienGridMassaction('{$this->getHtmlId()}', " + . "{$this->getGridJsObjectName()}, '{$this->getSelectedJson()}'" + . ", '{$this->getFormFieldNameInternal()}', '{$this->getFormFieldName()}');" + . "{$this->getJsObjectName()}.setItems({$this->getItemsJson()}); " + . "{$this->getJsObjectName()}.setGridIds('{$this->getGridIdsJson()}');" + . ($this->getUseAjax() ? "{$this->getJsObjectName()}.setUseAjax(true);" : '') + . ($this->getUseSelectAll() ? "{$this->getJsObjectName()}.setUseSelectAll(true);" : '') + . "{$this->getJsObjectName()}.errorText = '{$this->getErrorText()}';"; } public function getGridIdsJson() @@ -240,10 +244,8 @@ public function getGridIdsJson() if(!empty($gridIds)) { return join(",", $gridIds); - //return Mage::helper('core')->jsonEncode($gridIds); } return ''; - //return '[]'; } public function getHtmlId() @@ -288,4 +290,3 @@ public function setUseSelectAll($flag) return $this; } } - // Class Mage_Adminhtml_Block_Widget_Grid_Massaction_Abstract End diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item.php index d7ad849c..28a6d25f 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Default.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Default.php index 11a20055..f6a27990 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Default.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Default.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Interface.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Interface.php index 92cec34e..222c39f8 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Interface.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Massaction/Item/Additional/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Serializer.php b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Serializer.php index e04aaeae..47190937 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Serializer.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Grid/Serializer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Tab/Interface.php b/app/code/core/Mage/Adminhtml/Block/Widget/Tab/Interface.php index 42e32f44..716b4173 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Tab/Interface.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Tab/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,8 +34,31 @@ */ interface Mage_Adminhtml_Block_Widget_Tab_Interface { + /** + * Return Tab label + * + * @return string + */ public function getTabLabel(); + + /** + * Return Tab title + * + * @return string + */ public function getTabTitle(); + + /** + * Can show tab in tabs + * + * @return boolean + */ public function canShowTab(); + + /** + * Tab is hidden + * + * @return boolean + */ public function isHidden(); } diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Tabs.php b/app/code/core/Mage/Adminhtml/Block/Widget/Tabs.php index c319dd4d..db804f33 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Tabs.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -75,6 +75,20 @@ public function setDestElementId($elementId) return $this; } + /** + * Add new tab after another + * + * @param string $tabId new tab Id + * @param array|Varien_Object $tab + * @param string $afterTabId + * @return Mage_Adminhtml_Block_Widget_Tabs + */ + public function addTabAfter($tabId, $tab, $afterTabId) + { + $this->addTab($tabId, $tab); + $this->_tabs[$tabId]->setAfter($afterTabId); + } + /** * Add new tab * diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/Tree.php b/app/code/core/Mage/Adminhtml/Block/Widget/Tree.php index f3e38c77..7b648553 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/Tree.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/Tree.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Block/Widget/View/Container.php b/app/code/core/Mage/Adminhtml/Block/Widget/View/Container.php index e02a5bc6..5965bb10 100644 --- a/app/code/core/Mage/Adminhtml/Block/Widget/View/Container.php +++ b/app/code/core/Mage/Adminhtml/Block/Widget/View/Container.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Controller/Action.php b/app/code/core/Mage/Adminhtml/Controller/Action.php index 6383749c..b569f129 100644 --- a/app/code/core/Mage/Adminhtml/Controller/Action.php +++ b/app/code/core/Mage/Adminhtml/Controller/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,8 +33,16 @@ */ class Mage_Adminhtml_Controller_Action extends Mage_Core_Controller_Varien_Action { + /** + * Name of "is URLs checked" flag + */ const FLAG_IS_URLS_CHECKED = 'check_url_settings'; + /** + * Session namespace to refer in other places + */ + const SESSION_NAMESPACE = 'adminhtml'; + /** * Array of actions which can be processed without secret key validation * @@ -59,7 +67,7 @@ class Mage_Adminhtml_Controller_Action extends Mage_Core_Controller_Varien_Actio * * @var string */ - protected $_sessionNamespace = 'adminhtml'; + protected $_sessionNamespace = self::SESSION_NAMESPACE; protected function _isAllowed() { @@ -213,8 +221,12 @@ protected function _checkUrlSettings() $configData = Mage::getModel('core/config_data'); - $defaultUnsecure= (string) Mage::getConfig()->getNode('default/'.Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_URL); - $defaultSecure = (string) Mage::getConfig()->getNode('default/'.Mage_Core_Model_Store::XML_PATH_SECURE_BASE_URL); + $defaultUnsecure = (string)Mage::getConfig()->getNode( + 'default/' . Mage_Core_Model_Store::XML_PATH_UNSECURE_BASE_URL + ); + $defaultSecure = (string)Mage::getConfig()->getNode( + 'default/' . Mage_Core_Model_Store::XML_PATH_SECURE_BASE_URL + ); if ($defaultSecure == '{{base_url}}' || $defaultUnsecure == '{{base_url}}') { $this->_getSession()->addNotice( @@ -324,68 +336,6 @@ protected function _redirectReferer($defaultUrl=null) return $this; } - /** - * Declare headers and content file in responce for file download - * - * @param string $fileName - * @param string|array $content set to null to avoid starting output, $contentLength should be set explicitly in - * that case - * @param string $contentType - * @param int $contentLength explicit content length, if strlen($content) isn't applicable - * @return Mage_Adminhtml_Controller_Action - */ - protected function _prepareDownloadResponse($fileName, $content, $contentType = 'application/octet-stream', $contentLength = null) - { - $session = Mage::getSingleton('admin/session'); - if ($session->isFirstPageAfterLogin()) { - $this->_redirect($session->getUser()->getStartupPageUrl()); - return $this; - } - - $isFile = false; - $file = null; - if (is_array($content)) { - if (!isset($content['type']) || !isset($content['value'])) { - return $this; - } - if ($content['type'] == 'filename') { - $isFile = true; - $file = $content['value']; - $contentLength = filesize($file); - } - } - - $this->getResponse() - ->setHttpResponseCode(200) - ->setHeader('Pragma', 'public', true) - ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) - ->setHeader('Content-type', $contentType, true) - ->setHeader('Content-Length', is_null($contentLength) ? strlen($content) : $contentLength) - ->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'"') - ->setHeader('Last-Modified', date('r')); - - if (!is_null($content)) { - if ($isFile) { - $this->getResponse()->clearBody(); - $this->getResponse()->sendHeaders(); - - $ioAdapter = new Varien_Io_File(); - $ioAdapter->open(array('path' => $ioAdapter->dirname($file))); - $ioAdapter->streamOpen($file, 'r'); - while ($buffer = $ioAdapter->streamRead()) { - print $buffer; - } - $ioAdapter->streamClose(); - if (!empty($content['rm'])) { - $ioAdapter->rm($file); - } - } else { - $this->getResponse()->setBody($content); - } - } - return $this; - } - /** * Set redirect into responce * diff --git a/app/code/core/Mage/Adminhtml/Exception.php b/app/code/core/Mage/Adminhtml/Exception.php index edcada9e..62a57f03 100644 --- a/app/code/core/Mage/Adminhtml/Exception.php +++ b/app/code/core/Mage/Adminhtml/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Abstract.php b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Abstract.php index 0f7f9d8e..66c59d0e 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Data.php b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Data.php index dd8d8399..6119ac7c 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Data.php +++ b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -69,11 +69,11 @@ public function countStores() public function getDatePeriods() { return array( - '24h'=>$this->__('Last 24 Hours'), - '7d'=>$this->__('Last 7 Days'), - '1m'=>$this->__('Current Month'), - '1y'=>$this->__('YTD'), - '2y'=>$this->__('2YTD') + '24h' => $this->__('Last 24 Hours'), + '7d' => $this->__('Last 7 Days'), + '1m' => $this->__('Current Month'), + '1y' => $this->__('YTD'), + '2y' => $this->__('2YTD') ); } diff --git a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Order.php b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Order.php index c5ecea16..e226d4ee 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Dashboard/Order.php +++ b/app/code/core/Mage/Adminhtml/Helper/Dashboard/Order.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Helper/Data.php b/app/code/core/Mage/Adminhtml/Helper/Data.php index 43d64fac..31744a39 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Data.php +++ b/app/code/core/Mage/Adminhtml/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,6 +33,10 @@ */ class Mage_Adminhtml_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_PATH_ADMINHTML_ROUTER_FRONTNAME = 'admin/routers/adminhtml/args/frontName'; + const XML_PATH_USE_CUSTOM_ADMIN_URL = 'default/admin/url/use_custom'; + const XML_PATH_USE_CUSTOM_ADMIN_PATH = 'default/admin/url/use_custom_path'; + const XML_PATH_CUSTOM_ADMIN_PATH = 'default/admin/url/custom_path'; protected $_pageHelpUrl; diff --git a/app/code/core/Mage/Adminhtml/Helper/Js.php b/app/code/core/Mage/Adminhtml/Helper/Js.php index 40989f67..9b30e7f8 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Js.php +++ b/app/code/core/Mage/Adminhtml/Helper/Js.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Helper/Media/Js.php b/app/code/core/Mage/Adminhtml/Helper/Media/Js.php index 39e1dbf7..523da5ba 100644 --- a/app/code/core/Mage/Adminhtml/Helper/Media/Js.php +++ b/app/code/core/Mage/Adminhtml/Helper/Media/Js.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,9 +28,10 @@ /** * Media library js helper * + * @deprecated since 1.7.0.0 * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Helper_Media_Js extends Mage_Core_Helper_Js { diff --git a/app/code/core/Mage/Adminhtml/Model/Config.php b/app/code/core/Mage/Adminhtml/Model/Config.php index 9a3ebc1a..a0832197 100644 --- a/app/code/core/Mage/Adminhtml/Model/Config.php +++ b/app/code/core/Mage/Adminhtml/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/Config/Data.php b/app/code/core/Mage/Adminhtml/Model/Config/Data.php index da7aa83b..43f8a06b 100644 --- a/app/code/core/Mage/Adminhtml/Model/Config/Data.php +++ b/app/code/core/Mage/Adminhtml/Model/Config/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,6 +45,8 @@ public function save() $this->_validate(); $this->_getScope(); + Mage::dispatchEvent('model_config_data_save_before', array('object' => $this)); + $section = $this->getSection(); $website = $this->getWebsite(); $store = $this->getStore(); diff --git a/app/code/core/Mage/Adminhtml/Model/Customer/Renderer/Region.php b/app/code/core/Mage/Adminhtml/Model/Customer/Renderer/Region.php index 54c10ade..7f20e001 100644 --- a/app/code/core/Mage/Adminhtml/Model/Customer/Renderer/Region.php +++ b/app/code/core/Mage/Adminhtml/Model/Customer/Renderer/Region.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -58,12 +58,13 @@ public function render(Varien_Data_Form_Element_Abstract $element) if (!isset(self::$_regionCollections[$countryId])) { self::$_regionCollections[$countryId] = Mage::getModel('directory/country') ->setId($countryId) - ->getLoadedRegionCollection(); + ->getLoadedRegionCollection() + ->toOptionArray(); } $regionCollection = self::$_regionCollections[$countryId]; } - $regionId = $element->getForm()->getElement('region_id')->getValue(); + $regionId = intval($element->getForm()->getElement('region_id')->getValue()); $htmlAttributes = $element->getHtmlAttributes(); foreach ($htmlAttributes as $key => $attribute) { @@ -72,28 +73,45 @@ public function render(Varien_Data_Form_Element_Abstract $element) break; } } - if ($regionCollection && $regionCollection->getSize()) { + + // Output two elements - for 'region' and for 'region_id'. + // Two elements are needed later upon form post - to properly set data to address model, + // otherwise old value can be left in region_id attribute and saved to DB. + // Depending on country selected either 'region' (input text) or 'region_id' (selectbox) is visible to user + $regionHtmlName = $element->getName(); + $regionIdHtmlName = str_replace('region', 'region_id', $regionHtmlName); + $regionHtmlId = $element->getHtmlId(); + $regionIdHtmlId = str_replace('region', 'region_id', $regionHtmlId); + + if ($regionCollection && count($regionCollection) > 0) { $elementClass = $element->getClass(); - $element->setClass(str_replace('input-text', '', $elementClass)); $html.= '
    '; - $html.= ''; + $html.= '' . "\n"; + + $html .= ''; + + $html.= ''; $element->setClass($elementClass); - } - else { + } else { $element->setClass('input-text'); $html.= ''; $element->setRequired(false); - $html.= ''."\n"; + $html.= ''."\n"; } $html.= ''."\n"; return $html; diff --git a/app/code/core/Mage/Adminhtml/Model/Email/Template.php b/app/code/core/Mage/Adminhtml/Model/Email/Template.php index a423ed28..8be9b265 100644 --- a/app/code/core/Mage/Adminhtml/Model/Email/Template.php +++ b/app/code/core/Mage/Adminhtml/Model/Email/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/Extension.php b/app/code/core/Mage/Adminhtml/Model/Extension.php index 3be8f494..959b6675 100644 --- a/app/code/core/Mage/Adminhtml/Model/Extension.php +++ b/app/code/core/Mage/Adminhtml/Model/Extension.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/Giftmessage/Save.php b/app/code/core/Mage/Adminhtml/Model/Giftmessage/Save.php index b873c056..c3fe1aa9 100644 --- a/app/code/core/Mage/Adminhtml/Model/Giftmessage/Save.php +++ b/app/code/core/Mage/Adminhtml/Model/Giftmessage/Save.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,13 +45,6 @@ public function saveAllInQuote() { $giftmessages = $this->getGiftmessages(); - // remove disabled giftmessages - foreach ($this->_getQuote()->getAllItems() as $item) { - if($item->getGiftMessageId() && !in_array($item->getId(), $this->getAllowQuoteItems())) { - $this->_deleteOne($item); - } - } - if (!is_array($giftmessages)) { return $this; } @@ -91,25 +84,29 @@ public function saveAllInOrder() * @return Mage_Adminhtml_Model_Giftmessage_Save */ protected function _saveOne($entityId, $giftmessage) { + /* @var $giftmessageModel Mage_Giftmessage_Model_Message */ $giftmessageModel = Mage::getModel('giftmessage/message'); + $entityType = $this->_getMappedType($giftmessage['type']); - if ($this->_getMappedType($giftmessage['type'])!='quote_item') { - $entityModel = $giftmessageModel->getEntityModelByType($this->_getMappedType($giftmessage['type'])); - } else { - $entityModel = $this->_getQuote()->getItemById($entityId); - } - + switch($entityType) { + case 'quote': + $entityModel = $this->_getQuote(); + break; + case 'quote_item': + $entityModel = $this->_getQuote()->getItemById($entityId); + break; - if ($this->_getMappedType($giftmessage['type'])=='quote') { - $entityModel->setStoreId($this->_getQuote()->getStoreId()); + default: + $entityModel = $giftmessageModel->getEntityModelByType($entityType) + ->load($entityId); + break; } - if ($this->_getMappedType($giftmessage['type'])!='quote_item') { - $entityModel->load($entityId); + if (!$entityModel) { + return $this; } - if ($entityModel->getGiftMessageId()) { $giftmessageModel->load($entityModel->getGiftMessageId()); } @@ -122,8 +119,10 @@ protected function _saveOne($entityId, $giftmessage) { $this->_saved = false; } elseif (!$giftmessageModel->isMessageEmpty()) { $giftmessageModel->save(); - $entityModel->setGiftMessageId($giftmessageModel->getId()) - ->save(); + $entityModel->setGiftMessageId($giftmessageModel->getId()); + if($entityType != 'quote') { + $entityModel->save(); + } $this->_saved = true; } diff --git a/app/code/core/Mage/Adminhtml/Model/LayoutUpdate/Validator.php b/app/code/core/Mage/Adminhtml/Model/LayoutUpdate/Validator.php new file mode 100644 index 00000000..cada0ebc --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/LayoutUpdate/Validator.php @@ -0,0 +1,122 @@ + + */ +class Mage_Adminhtml_Model_LayoutUpdate_Validator extends Zend_Validate_Abstract +{ + const XML_INVALID = 'invalidXml'; + const PROTECTED_ATTR_HELPER_IN_TAG_ACTION_VAR = 'protectedAttrHelperInActionVar'; + + /** + * The Varien SimpleXml object + * + * @var Varien_Simplexml_Element + */ + protected $_value; + + /** + * Protected expressions + * + * @var array + */ + protected $_protectedExpressions = array( + self::PROTECTED_ATTR_HELPER_IN_TAG_ACTION_VAR => '//action/*[@helper]', + ); + + /** + * Construct + */ + public function __construct() + { + $this->_initMessageTemplates(); + } + + /** + * Initialize messages templates with translating + * + * @return Mage_Adminhtml_Model_LayoutUpdate_Validator + */ + protected function _initMessageTemplates() + { + if (!$this->_messageTemplates) { + $this->_messageTemplates = array( + self::PROTECTED_ATTR_HELPER_IN_TAG_ACTION_VAR => + Mage::helper('adminhtml')->__('Helper attributes should not be used in custom layout updates.'), + self::XML_INVALID => Mage::helper('adminhtml')->__('XML data is invalid.'), + ); + } + return $this; + } + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @throws Exception Throw exception when xml object is not + * instance of Varien_Simplexml_Element + * @param Varien_Simplexml_Element|string $value + * @return bool + */ + public function isValid($value) + { + if (is_string($value)) { + $value = trim($value); + try { + //wrap XML value in the "config" tag because config cannot + //contain multiple root tags + $value = new Varien_Simplexml_Element('' . $value . ''); + } catch (Exception $e) { + $this->_error(self::XML_INVALID); + return false; + } + } elseif (!($value instanceof Varien_Simplexml_Element)) { + throw new Exception( + Mage::helper('adminhtml')->__('XML object is not instance of "Varien_Simplexml_Element".')); + } + + $this->_setValue($value); + + foreach ($this->_protectedExpressions as $key => $xpr) { + if ($this->_value->xpath($xpr)) { + $this->_error($key); + return false; + } + } + return true; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/Newsletter/Renderer/Text.php b/app/code/core/Mage/Adminhtml/Model/Newsletter/Renderer/Text.php index 770f1c34..b679b993 100644 --- a/app/code/core/Mage/Adminhtml/Model/Newsletter/Renderer/Text.php +++ b/app/code/core/Mage/Adminhtml/Model/Newsletter/Renderer/Text.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/Observer.php b/app/code/core/Mage/Adminhtml/Model/Observer.php index 48bdd5c0..1d6c3e67 100644 --- a/app/code/core/Mage/Adminhtml/Model/Observer.php +++ b/app/code/core/Mage/Adminhtml/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -65,4 +65,15 @@ public function massactionPrepareKey() } return $this; } + + /** + * Clear result of configuration files access level verification in system cache + * + * @return Mage_Adminhtml_Model_Observer + */ + public function clearCacheConfigurationFilesAccessLevelVerification() + { + Mage::app()->removeCache(Mage_Adminhtml_Block_Notification_Security::VERIFICATION_RESULT_CACHE_KEY); + return $this; + } } diff --git a/app/code/core/Mage/Adminhtml/Model/Report/Item.php b/app/code/core/Mage/Adminhtml/Model/Report/Item.php index 098bac4f..44b86db6 100644 --- a/app/code/core/Mage/Adminhtml/Model/Report/Item.php +++ b/app/code/core/Mage/Adminhtml/Model/Report/Item.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_Report_Item extends Varien_Object diff --git a/app/code/core/Mage/Adminhtml/Model/Session.php b/app/code/core/Mage/Adminhtml/Model/Session.php index 09129530..7ebb30e7 100644 --- a/app/code/core/Mage/Adminhtml/Model/Session.php +++ b/app/code/core/Mage/Adminhtml/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/Session/Quote.php b/app/code/core/Mage/Adminhtml/Model/Session/Quote.php index fadedbd6..969e81be 100644 --- a/app/code/core/Mage/Adminhtml/Model/Session/Quote.php +++ b/app/code/core/Mage/Adminhtml/Model/Session/Quote.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php index d2bc15e9..36c769f4 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custom.php @@ -20,17 +20,17 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** - * Adminhtml backend model for "Use Secure URLs in Admin" option + * Adminhtml backend model for "Custom Admin URL" option * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom extends Mage_Core_Model_Config_Data { @@ -39,7 +39,14 @@ class Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom extends Mage_Core_ const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; + const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url'; + const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url'; + /** + * Validate value before save + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom + */ protected function _beforeSave() { $value = $this->getValue(); @@ -52,6 +59,11 @@ protected function _beforeSave() return $this; } + /** + * Change secure/unsecure base_url after use_custom_url was modified + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom + */ public function _afterSave() { $useCustomUrl = $this->getData('groups/url/fields/use_custom/value'); @@ -62,12 +74,18 @@ public function _afterSave() } if ($useCustomUrl == 1) { - Mage::getConfig()->saveConfig(self::XML_PATH_SECURE_BASE_URL, $value, self::CONFIG_SCOPE, self::CONFIG_SCOPE_ID); - Mage::getConfig()->saveConfig(self::XML_PATH_UNSECURE_BASE_URL, $value, self::CONFIG_SCOPE, self::CONFIG_SCOPE_ID); - } - else { - Mage::getConfig()->deleteConfig(self::XML_PATH_SECURE_BASE_URL, self::CONFIG_SCOPE, self::CONFIG_SCOPE_ID); - Mage::getConfig()->deleteConfig(self::XML_PATH_UNSECURE_BASE_URL, self::CONFIG_SCOPE, self::CONFIG_SCOPE_ID); + Mage::getConfig()->saveConfig( + self::XML_PATH_SECURE_BASE_URL, + $value, + self::CONFIG_SCOPE, + self::CONFIG_SCOPE_ID + ); + Mage::getConfig()->saveConfig( + self::XML_PATH_UNSECURE_BASE_URL, + $value, + self::CONFIG_SCOPE, + self::CONFIG_SCOPE_ID + ); } return $this; diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custompath.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custompath.php new file mode 100644 index 00000000..e1475539 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Custompath.php @@ -0,0 +1,49 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Admin_Custompath extends Mage_Core_Model_Config_Data +{ + /** + * Check whether redirect should be set + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom + */ + protected function _beforeSave() + { + if ($this->getOldValue() != $this->getValue()) { + Mage::register('custom_admin_path_redirect', true, true); + } + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Observer.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Observer.php new file mode 100644 index 00000000..75e9b332 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Observer.php @@ -0,0 +1,54 @@ +unsetAll(); + $adminSession->getCookie()->delete($adminSession->getSessionName()); + + $route = ((bool)(string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_PATH)) + ? Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_PATH) + : Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_ADMINHTML_ROUTER_FRONTNAME); + + Mage::app()->getResponse() + ->setRedirect(Mage::getBaseUrl() . $route) + ->sendResponse(); + exit(0); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Password/Link/Expirationperiod.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Password/Link/Expirationperiod.php new file mode 100644 index 00000000..55d59675 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Password/Link/Expirationperiod.php @@ -0,0 +1,53 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Admin_Password_Link_Expirationperiod + extends Mage_Core_Model_Config_Data +{ + /** + * Validate expiration period value before saving + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Password_Link_Expirationperiod + */ + protected function _beforeSave() + { + parent::_beforeSave(); + $resetPasswordLinkExpirationPeriod = (int)$this->getValue(); + + if ($resetPasswordLinkExpirationPeriod < 1) { + $resetPasswordLinkExpirationPeriod = (int)$this->getOldValue(); + } + $this->setValue((string)$resetPasswordLinkExpirationPeriod); + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php index cf278b9a..5e1c6c48 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustom.php @@ -20,23 +20,27 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** - * Adminhtml backend model for "Use Secure URLs in Admin" option + * Adminhtml backend model for "Use Custom Admin URL" option * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Adminhtml_Model_System_Config_Backend_Admin_Usecustom extends Mage_Core_Model_Config_Data { + /** + * Validate custom url + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Usecustom + */ protected function _beforeSave() { - $value = $this->getValue(); if ($value == 1) { $customUrl = $this->getData('groups/url/fields/custom/value'); @@ -47,4 +51,29 @@ protected function _beforeSave() return $this; } + + /** + * Delete custom admin url from configuration if "Use Custom Admin Url" option disabled + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Usecustom + */ + protected function _afterSave() + { + $value = $this->getValue(); + + if (!$value) { + Mage::getConfig()->deleteConfig( + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::XML_PATH_SECURE_BASE_URL, + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::CONFIG_SCOPE, + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::CONFIG_SCOPE_ID + ); + Mage::getConfig()->deleteConfig( + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::XML_PATH_UNSECURE_BASE_URL, + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::CONFIG_SCOPE, + Mage_Adminhtml_Model_System_Config_Backend_Admin_Custom::CONFIG_SCOPE_ID + ); + } + + return $this; + } } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustompath.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustompath.php new file mode 100644 index 00000000..bcdcc7e0 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usecustompath.php @@ -0,0 +1,50 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Admin_Usecustompath extends Mage_Core_Model_Config_Data +{ + /** + * Check whether redirect should be set + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Admin_Usecustompath + */ + protected function _beforeSave() + { + if ($this->getOldValue() != $this->getValue()) { + Mage::register('custom_admin_path_redirect', true, true); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usesecretkey.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usesecretkey.php index 2b157119..4e3d701f 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usesecretkey.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Admin/Usesecretkey.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Baseurl.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Baseurl.php index 5f3bb8d1..9e2563a0 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Baseurl.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Baseurl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php index ed5d6a30..f9f6af5e 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cache.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Category.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Category.php index 457b8408..71213d61 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Category.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Category.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php new file mode 100644 index 00000000..17dbcf27 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Cookie.php @@ -0,0 +1,37 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Cookie extends Mage_Core_Model_Config_Data +{ + protected $_eventPrefix = 'adminhtml_system_config_backend_cookie'; +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Abstract.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Abstract.php index 9750dde0..c8e8deab 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Abstract.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Allow.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Allow.php index 41d09877..f7225170 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Allow.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Allow.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Base.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Base.php index d9111d64..59208878 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Base.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Base.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Cron.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Cron.php index 4332e6c5..c3e359b5 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Cron.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Cron.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Default.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Default.php index b8e58640..61bc5aef 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Default.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Currency/Default.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Datashare.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Datashare.php index 4f05df64..7c472a0b 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Datashare.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Datashare.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Design/Exception.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Design/Exception.php new file mode 100644 index 00000000..a1648d1c --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Design/Exception.php @@ -0,0 +1,30 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Email_Logo extends Mage_Adminhtml_Model_System_Config_Backend_Image +{ + /** + * The tail part of directory path for uploading + */ + const UPLOAD_DIR = 'email/logo'; + + /** + * Token for the root part of directory path for uploading + */ + const UPLOAD_ROOT_TOKEN = 'system/filesystem/media'; + + /** + * Upload max file size in kilobytes + * + * @var int + */ + protected $_maxFileSize = 2048; + + /** + * Return path to directory for upload file + * + * @return string + */ + protected function _getUploadDir() + { + $uploadDir = $this->_appendScopeInfo(self::UPLOAD_DIR); + $uploadRoot = $this->_getUploadRoot(self::UPLOAD_ROOT_TOKEN); + $uploadDir = $uploadRoot . DS . $uploadDir; + return $uploadDir; + } + + /** + * Makes a decision about whether to add info about the scope + * + * @return boolean + */ + protected function _addWhetherScopeInfo() + { + return true; + } + + /** + * Save uploaded file before saving config value + * + * Save changes and delete file if "delete" option passed + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Email_Logo + */ + protected function _beforeSave() + { + $value = $this->getValue(); + $deleteFlag = (is_array($value) && !empty($value['delete'])); + $fileTmpName = $_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']; + + if ($this->getOldValue() && ($fileTmpName || $deleteFlag)) { + $io = new Varien_Io_File(); + $io->rm($this->_getUploadRoot(self::UPLOAD_ROOT_TOKEN) . DS . self::UPLOAD_DIR . DS . $this->getOldValue()); + } + return parent::_beforeSave(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Sender.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Sender.php index afa622fd..5b8e8b43 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Sender.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Email/Sender.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Encrypted.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Encrypted.php index 6e9248ca..f971146c 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Encrypted.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Encrypted.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php new file mode 100644 index 00000000..11d421d4 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/File.php @@ -0,0 +1,205 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_File extends Mage_Core_Model_Config_Data +{ + /** + * Upload max file size in kilobytes + * + * @var int + */ + protected $_maxFileSize = 0; + + /** + * Save uploaded file before saving config value + * + * @return Mage_Adminhtml_Model_System_Config_Backend_File + */ + protected function _beforeSave() + { + $value = $this->getValue(); + if ($_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']){ + + $uploadDir = $this->_getUploadDir(); + + try { + $file = array(); + $tmpName = $_FILES['groups']['tmp_name']; + $file['tmp_name'] = $tmpName[$this->getGroupId()]['fields'][$this->getField()]['value']; + $name = $_FILES['groups']['name']; + $file['name'] = $name[$this->getGroupId()]['fields'][$this->getField()]['value']; + $uploader = new Mage_Core_Model_File_Uploader($file); + $uploader->setAllowedExtensions($this->_getAllowedExtensions()); + $uploader->setAllowRenameFiles(true); + $uploader->addValidateCallback('size', $this, 'validateMaxSize'); + $result = $uploader->save($uploadDir); + + } catch (Exception $e) { + Mage::throwException($e->getMessage()); + return $this; + } + + $filename = $result['file']; + if ($filename) { + if ($this->_addWhetherScopeInfo()) { + $filename = $this->_prependScopeInfo($filename); + } + $this->setValue($filename); + } + } else { + if (is_array($value) && !empty($value['delete'])) { + $this->setValue(''); + } else { + $this->unsValue(); + } + } + + return $this; + } + + /** + * Validation callback for checking max file size + * + * @param string $filePath Path to temporary uploaded file + * @throws Mage_Core_Exception + */ + public function validateMaxSize($filePath) + { + if ($this->_maxFileSize > 0 && filesize($filePath) > ($this->_maxFileSize * 1024)) { + throw Mage::exception('Mage_Core', Mage::helper('adminhtml')->__('Uploaded file is larger than %.2f kilobytes allowed by server', $this->_maxFileSize)); + } + } + + /** + * Makes a decision about whether to add info about the scope. + * + * @return boolean + */ + protected function _addWhetherScopeInfo() + { + $fieldConfig = $this->getFieldConfig(); + $el = $fieldConfig->descend('upload_dir'); + return (!empty($el['scope_info'])); + } + + /** + * Return path to directory for upload file + * + * @return string + * @throw Mage_Core_Exception + */ + protected function _getUploadDir() + { + $fieldConfig = $this->getFieldConfig(); + /* @var $fieldConfig Varien_Simplexml_Element */ + + if (empty($fieldConfig->upload_dir)) { + Mage::throwException(Mage::helper('catalog')->__('The base directory to upload file is not specified.')); + } + + $uploadDir = (string)$fieldConfig->upload_dir; + + $el = $fieldConfig->descend('upload_dir'); + + /** + * Add scope info + */ + if (!empty($el['scope_info'])) { + $uploadDir = $this->_appendScopeInfo($uploadDir); + } + + /** + * Take root from config + */ + if (!empty($el['config'])) { + $uploadRoot = $this->_getUploadRoot((string)$el['config']); + $uploadDir = $uploadRoot . '/' . $uploadDir; + } + return $uploadDir; + } + + /** + * Return the root part of directory path for uploading + * + * @var string + * @return string + */ + protected function _getUploadRoot($token) + { + return Mage::getBaseDir('media'); + } + + /** + * Prepend path with scope info + * + * E.g. 'stores/2/path' , 'websites/3/path', 'default/path' + * + * @param string $path + * @return string + */ + protected function _prependScopeInfo($path) + { + $scopeInfo = $this->getScope(); + if ('default' != $this->getScope()) { + $scopeInfo .= '/' . $this->getScopeId(); + } + return $scopeInfo . '/' . $path; + } + + /** + * Add scope info to path + * + * E.g. 'path/stores/2' , 'path/websites/3', 'path/default' + * + * @param string $path + * @return string + */ + protected function _appendScopeInfo($path) + { + $path .= '/' . $this->getScope(); + if ('default' != $this->getScope()) { + $path .= '/' . $this->getScopeId(); + } + return $path; + } + + /** + * Getter for allowed extensions of uploaded files + * + * @return array + */ + protected function _getAllowedExtensions() + { + return array(); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Filename.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Filename.php new file mode 100644 index 00000000..83a0c76c --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Filename.php @@ -0,0 +1,37 @@ +getValue(); + $value = basename($value); + $this->setValue($value); + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image.php index 8eb4c4b4..02c40237 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,115 +30,15 @@ * * @category Mage * @package Mage_Adminhtml - * @author Magento Core Team + * @author Magento Core Team */ -class Mage_Adminhtml_Model_System_Config_Backend_Image extends Mage_Core_Model_Config_Data +class Mage_Adminhtml_Model_System_Config_Backend_Image extends Mage_Adminhtml_Model_System_Config_Backend_File { - - /** - * Save uploaded file before saving config value - * - * @return Mage_Adminhtml_Model_System_Config_Backend_Image - */ - protected function _beforeSave() - { - $value = $this->getValue(); - if (is_array($value) && !empty($value['delete'])) { - $this->setValue(''); - } - - if ($_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']){ - - $fieldConfig = $this->getFieldConfig(); - /* @var $fieldConfig Varien_Simplexml_Element */ - - if (empty($fieldConfig->upload_dir)) { - Mage::throwException(Mage::helper('catalog')->__('The base directory to upload image file is not specified.')); - } - - $uploadDir = (string)$fieldConfig->upload_dir; - - $el = $fieldConfig->descend('upload_dir'); - - /** - * Add scope info - */ - if (!empty($el['scope_info'])) { - $uploadDir = $this->_appendScopeInfo($uploadDir); - } - - /** - * Take root from config - */ - if (!empty($el['config'])) { - $uploadRoot = (string)Mage::getConfig()->getNode((string)$el['config'], $this->getScope(), $this->getScopeId()); - $uploadRoot = Mage::getConfig()->substDistroServerVars($uploadRoot); - $uploadDir = $uploadRoot . '/' . $uploadDir; - } - - try { - $file = array(); - $file['tmp_name'] = $_FILES['groups']['tmp_name'][$this->getGroupId()]['fields'][$this->getField()]['value']; - $file['name'] = $_FILES['groups']['name'][$this->getGroupId()]['fields'][$this->getField()]['value']; - $uploader = new Varien_File_Uploader($file); - $uploader->setAllowedExtensions($this->_getAllowedExtensions()); - $uploader->setAllowRenameFiles(true); - $uploader->save($uploadDir); - } catch (Exception $e) { - Mage::throwException($e->getMessage()); - return $this; - } - - if ($filename = $uploader->getUploadedFileName()) { - - /** - * Add scope info - */ - if (!empty($el['scope_info'])) { - $filename = $this->_prependScopeInfo($filename); - } - - $this->setValue($filename); - } - } - - return $this; - } - /** - * Prepend path with scope info + * Getter for allowed extensions of uploaded files * - * E.g. 'stores/2/path' , 'websites/3/path', 'default/path' - * - * @param string $path - * @return string + * @return array */ - protected function _prependScopeInfo($path) - { - $scopeInfo = $this->getScope(); - if ('default' != $this->getScope()) { - $scopeInfo .= '/' . $this->getScopeId(); - } - return $scopeInfo . '/' . $path; - } - - /** - * Add scope info to path - * - * E.g. 'path/stores/2' , 'path/websites/3', 'path/default' - * - * @param string $path - * @return string - */ - protected function _appendScopeInfo($path) - { - $path .= '/' . $this->getScope(); - if ('default' != $this->getScope()) { - $path .= '/' . $this->getScopeId(); - } - return $path; - } - protected function _getAllowedExtensions() { return array('jpg', 'jpeg', 'gif', 'png'); diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Favicon.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Favicon.php new file mode 100644 index 00000000..a5621168 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Favicon.php @@ -0,0 +1,92 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Image_Favicon extends Mage_Adminhtml_Model_System_Config_Backend_Image +{ + /** + * The tail part of directory path for uploading + * + */ + const UPLOAD_DIR = 'favicon'; + + /** + * Token for the root part of directory path for uploading + * + */ + const UPLOAD_ROOT = 'media'; + + /** + * Return path to directory for upload file + * + * @return string + * @throw Mage_Core_Exception + */ + protected function _getUploadDir() + { + $uploadDir = $this->_appendScopeInfo(self::UPLOAD_DIR); + $uploadRoot = $this->_getUploadRoot(self::UPLOAD_ROOT); + $uploadDir = $uploadRoot . '/' . $uploadDir; + return $uploadDir; + } + + /** + * Makes a decision about whether to add info about the scope. + * + * @return boolean + */ + protected function _addWhetherScopeInfo() + { + return true; + } + + /** + * Getter for allowed extensions of uploaded files. + * + * @return array + */ + protected function _getAllowedExtensions() + { + return array('ico', 'png', 'gif', 'jpg', 'jpeg', 'apng', 'svg'); + } + + /** + * Get real media dir path + * + * @param $token + * @return string + */ + protected function _getUploadRoot($token) { + return Mage::getBaseDir($token); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Pdf.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Pdf.php index afeabdb0..575dffbb 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Pdf.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Image/Pdf.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Layer/Children.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Layer/Children.php index 1f08b163..005643e5 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Layer/Children.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Layer/Children.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale.php index 6bb22e5b..19ee8d6d 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -48,8 +48,11 @@ protected function _afterSave() $values = explode(',', $this->getValue()); $exceptions = array(); + foreach ($collection as $data) { $match = false; + $scopeName = Mage::helper('adminhtml')->__('Default scope'); + if (preg_match('/(base|default)$/', $data->getPath(), $match)) { if (!in_array($data->getValue(), $values)) { $currencyName = Mage::app()->getLocale()->currency($data->getValue())->getName(); @@ -76,11 +79,7 @@ protected function _afterSave() break; } - $exceptions[] = Mage::helper('adminhtml')->__('Currency "%s" is used as %s in %s.', - $currencyName, - $fieldName, - $scopeName - ); + $exceptions[] = Mage::helper('adminhtml')->__('Currency "%s" is used as %s in %s.', $currencyName, $fieldName, $scopeName); } } } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale/Timezone.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale/Timezone.php new file mode 100644 index 00000000..4c8e7951 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Locale/Timezone.php @@ -0,0 +1,58 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Locale_Timezone extends Mage_Core_Model_Config_Data +{ + /** + * Const for PHP 5.3+ compatibility + * This value copied from DateTimeZone::ALL_WITH_BC in PHP 5.3+ + * + * @constant ALL_WITH_BC + */ + const ALL_WITH_BC = 4095; + + protected function _beforeSave() + { + $allWithBc = self::ALL_WITH_BC; + if (defined('DateTimeZone::ALL_WITH_BC')) { + $allWithBc = DateTimeZone::ALL_WITH_BC; + } + + if (!in_array($this->getValue(), DateTimeZone::listIdentifiers($allWithBc))) { + Mage::throwException(Mage::helper('adminhtml')->__('Invalid timezone')); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Price/Scope.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Price/Scope.php index 11419325..3c22b87e 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Price/Scope.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Price/Scope.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Secure.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Secure.php new file mode 100644 index 00000000..7f668bd3 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Secure.php @@ -0,0 +1,39 @@ +isValueChanged()) { + Mage::getModel('core/design_package')->cleanMergedJsCss(); + } + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized.php index 8a015313..3355f177 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,7 +29,8 @@ class Mage_Adminhtml_Model_System_Config_Backend_Serialized extends Mage_Core_Mo protected function _afterLoad() { if (!is_array($this->getValue())) { - $this->setValue(unserialize($this->getValue())); + $value = $this->getValue(); + $this->setValue(empty($value) ? false : unserialize($value)); } } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized/Array.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized/Array.php index 2fae4abf..df16b1a1 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized/Array.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Serialized/Array.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Sitemap.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Sitemap.php index 66c702da..be8cbaad 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Sitemap.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Sitemap.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Storage/Media/Database.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Storage/Media/Database.php new file mode 100644 index 00000000..780926da --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Storage/Media/Database.php @@ -0,0 +1,42 @@ +getStorageModel(null, array('init' => true)); + + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Store.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Store.php index ab1447cb..95ed09a1 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Store.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Translate.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Translate.php new file mode 100644 index 00000000..f92dd2d7 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Backend/Translate.php @@ -0,0 +1,57 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Backend_Translate extends Mage_Core_Model_Config_Data +{ + /** + * Path to config node with list of caches + * + * @var string + */ + const XML_PATH_INVALID_CACHES = 'dev/translate_inline/invalid_caches'; + + /** + * Set status 'invalidate' for blocks and other output caches + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Translate + */ + protected function _afterSave() + { + $types = array_keys(Mage::getStoreConfig(self::XML_PATH_INVALID_CACHES)); + if ($this->isValueChanged()) { + Mage::app()->getCacheInstance()->invalidateType($types); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Clone/Media/Image.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Clone/Media/Image.php index d436e22a..1c1f8b52 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Clone/Media/Image.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Clone/Media/Image.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -47,7 +47,7 @@ public function getPrefixes() //$entityTypeId = $entityType->loadByCode('catalog_product')->getEntityTypeId(); // use cached eav config - $entityTypeId = Mage::getSingleton('eav/config')->getEntityType('catalog_product')->getId(); + $entityTypeId = Mage::getSingleton('eav/config')->getEntityType(Mage_Catalog_Model_Product::ENTITY)->getId(); /* @var $collection Mage_Catalog_Model_Resource_Eav_Mysql4_Product_Attribute_Collection */ $collection = Mage::getResourceModel('catalog/product_attribute_collection'); diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Admin/Page.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Admin/Page.php index c5383ba0..40623b7e 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Admin/Page.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Admin/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -66,7 +66,7 @@ protected function _createOptions(&$optionArray, $menuNode) $children = array(); if(isset($menu['children'])) { - $this->_createOptions($optionArray, $menu['children']); + $this->_createOptions($children, $menu['children']); } $optionArray[] = array( @@ -94,8 +94,9 @@ protected function _buildMenuArray(Varien_Simplexml_Element $parent=null, $path= $parentArr = array(); $sortOrder = 0; foreach ($parent->children() as $childName=>$child) { - - if ($child->depends && !$this->_checkDepends($child->depends)) { + if ((1 == $child->disabled) + || ($child->depends && !$this->_checkDepends($child->depends)) + ) { continue; } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Allregion.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Allregion.php index 59a39d08..b47a524f 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Allregion.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Allregion.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Checktype.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Checktype.php index cf4a2081..2c33b7d5 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Checktype.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Checktype.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Page.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Page.php index 12463e53..9e340abe 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Page.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_System_Config_Source_Cms_Page diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Wysiwyg/Enabled.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Wysiwyg/Enabled.php index d721ec3d..e5a2e978 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Wysiwyg/Enabled.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cms/Wysiwyg/Enabled.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country.php index 4bed5f5f..43a059a1 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country/Full.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country/Full.php index 905d59f4..f1e4a7be 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country/Full.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Country/Full.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cron/Frequency.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cron/Frequency.php index bab3d48c..4b6897fb 100755 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cron/Frequency.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Cron/Frequency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency.php index aba31805..44d0862e 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency/Service.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency/Service.php index a55748a6..b15df5aa 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency/Service.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Currency/Service.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Date/Short.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Date/Short.php index dac27c23..b0d9bee8 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Date/Short.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Date/Short.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Package.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Package.php index 0ea6a8be..dba7cb50 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Package.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Package.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_System_Config_Source_Package diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Robots.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Robots.php index 360ce166..2b63840f 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Robots.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Design/Robots.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Dev/Dbautoup.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Dev/Dbautoup.php index 55054287..d57fd584 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Dev/Dbautoup.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Dev/Dbautoup.php @@ -20,18 +20,17 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_System_Config_Source_Dev_Dbautoup { public function toOptionArray() { - $hlp = Mage::helper('adminhtml'); return array( - array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_ALWAYS, 'label'=>$hlp->__('Always (during development)')), - array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_ONCE, 'label'=>$hlp->__('Only Once (version upgrade)')), - array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_NEVER, 'label'=>$hlp->__('Never (production)')), + array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_ALWAYS, 'label' => Mage::helper('adminhtml')->__('Always (during development)')), + array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_ONCE, 'label' => Mage::helper('adminhtml')->__('Only Once (version upgrade)')), + array('value'=>Mage_Core_Model_Resource::AUTO_UPDATE_NEVER, 'label' => Mage::helper('adminhtml')->__('Never (production)')), ); } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Identity.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Identity.php index f757dcde..a5f98efd 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Identity.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Identity.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Method.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Method.php index 9654512b..b077a25e 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Method.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Method.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Smtpauth.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Smtpauth.php index 21ba6d9e..4918cbe6 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Smtpauth.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Smtpauth.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Template.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Template.php index 59e4ccd5..3bf9a70d 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Template.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Email/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Enabledisable.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Enabledisable.php index 831ae158..0bb530fd 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Enabledisable.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Enabledisable.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Frequency.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Frequency.php index 0dac2c45..bedd0db5 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Frequency.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Frequency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Language.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Language.php index 29d9168b..fbe38454 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Language.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Language.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale.php index 9af0ec42..f2d2ab83 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Country.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Country.php index adb43628..cb509591 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Country.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Country.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency.php index ff34ea37..ab5b2dfa 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency/All.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency/All.php index 040cfe5d..ff1a617b 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency/All.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Currency/All.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Timezone.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Timezone.php index a57dd619..9a319c11 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Timezone.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Timezone.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Weekdays.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Weekdays.php index 421d811b..d5905b73 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Weekdays.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Locale/Weekdays.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Nooptreq.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Nooptreq.php index 0d575417..ad41c586 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Nooptreq.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Nooptreq.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_System_Config_Source_Nooptreq diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Notification/Frequency.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Notification/Frequency.php new file mode 100644 index 00000000..55bd4c24 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Notification/Frequency.php @@ -0,0 +1,47 @@ + + */ +class Mage_Adminhtml_Model_System_Config_Source_Notification_Frequency +{ + public function toOptionArray() + { + return array( + 1 => Mage::helper('adminhtml')->__('1 Hour'), + 2 => Mage::helper('adminhtml')->__('2 Hours'), + 6 => Mage::helper('adminhtml')->__('6 Hours'), + 12 => Mage::helper('adminhtml')->__('12 Hours'), + 24 => Mage::helper('adminhtml')->__('24 Hours') + ); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Scope.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Scope.php index 5d1b16e7..96168b4c 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Scope.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Scope.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php new file mode 100644 index 00000000..4c5de773 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Price/Step.php @@ -0,0 +1,46 @@ + Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_AUTO, + 'label' => Mage::helper('adminhtml')->__('Automatic (equalize price ranges)') + ), + array( + 'value' => Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_IMPROVED, + 'label' => Mage::helper('adminhtml')->__('Automatic (equalize product counts)') + ), + array( + 'value' => Mage_Catalog_Model_Layer_Filter_Price::RANGE_CALCULATION_MANUAL, + 'label' => Mage::helper('adminhtml')->__('Manual') + ), + ); + } +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Database.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Database.php new file mode 100644 index 00000000..c2670add --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Database.php @@ -0,0 +1,87 @@ +_connections[$connectionName])) { + $connection = $this->_connections[$connectionName]; + $connection = (array) $connection->descend('connection'); + + if (isset($connection['use'])) { + $config = $this->_collectConnectionConfig((string) $connection['use']); + } + + $config = array_merge($config, $connection); + } + + return $config; + } + + /** + * Options getter + * + * @return array + */ + public function toOptionArray() + { + $media_storages = array(); + + $this->_connections = (array) Mage::app()->getConfig()->getNode('global/resources')->children(); + foreach (array_keys($this->_connections) as $connectionName) { + $connection = $this->_collectConnectionConfig($connectionName); + if (!isset($connection['active']) || $connection['active'] != 1) { + continue; + } + + $media_storages[] = array('value' => $connectionName, 'label' => $connectionName); + } + sort($media_storages); + reset($media_storages); + + return $media_storages; + } + +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Storage.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Storage.php new file mode 100644 index 00000000..a2f100ae --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Storage/Media/Storage.php @@ -0,0 +1,51 @@ + Mage_Core_Model_File_Storage::STORAGE_MEDIA_FILE_SYSTEM, + 'label' => Mage::helper('adminhtml')->__('File System') + ), + array( + 'value' => Mage_Core_Model_File_Storage::STORAGE_MEDIA_DATABASE, + 'label' => Mage::helper('adminhtml')->__('Database') + ) + ); + } + +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Store.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Store.php index bd98e0b8..5b85b3b9 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Store.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Watermark/Position.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Watermark/Position.php index 417128b2..81058626 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Watermark/Position.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Watermark/Position.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Protocol.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Protocol.php index 92d95a08..5ecb4795 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Protocol.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Protocol.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_System_Config_Source_Web_Protocol diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Redirect.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Redirect.php new file mode 100644 index 00000000..a5612e5c --- /dev/null +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Web/Redirect.php @@ -0,0 +1,38 @@ + 0, 'label'=>Mage::helper('adminhtml')->__('No')), + array('value' => 1, 'label'=>Mage::helper('adminhtml')->__('Yes (302 Found)')), + array('value' => 301, 'label'=>Mage::helper('adminhtml')->__('Yes (301 Moved Permanently)')), + ); + } + +} diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Website.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Website.php index 9420133e..1cd91b35 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Website.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Website.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php index 2b9f357a..1c261991 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesno.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,4 +44,17 @@ public function toOptionArray() ); } + /** + * Get options in "key-value" format + * + * @return array + */ + public function toArray() + { + return array( + 0 => Mage::helper('adminhtml')->__('No'), + 1 => Mage::helper('adminhtml')->__('Yes'), + ); + } + } diff --git a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesnocustom.php b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesnocustom.php index 459422db..b9c474cd 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesnocustom.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Config/Source/Yesnocustom.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/Model/System/Store.php b/app/code/core/Mage/Adminhtml/Model/System/Store.php index c64782a8..0a5ee6ad 100644 --- a/app/code/core/Mage/Adminhtml/Model/System/Store.php +++ b/app/code/core/Mage/Adminhtml/Model/System/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -488,7 +488,7 @@ public function getStoreNamePath($storeId) if (isset($this->_storeCollection[$storeId])) { $data = $this->_storeCollection[$storeId]; $name .= $this->getWebsiteName($data->getWebsiteId()); - $name .= ($name ? '/' : '').$this->getGroupName($data->getGroupId()); + $name .= ($name ? '/' : '') . $this->getGroupName($data->getGroupId()); } } return $name; diff --git a/app/code/core/Mage/Adminhtml/Model/Url.php b/app/code/core/Mage/Adminhtml/Model/Url.php index ee821f8e..6fdde6b9 100644 --- a/app/code/core/Mage/Adminhtml/Model/Url.php +++ b/app/code/core/Mage/Adminhtml/Model/Url.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Model_Url extends Mage_Core_Model_Url diff --git a/app/code/core/Mage/Adminhtml/controllers/AjaxController.php b/app/code/core/Mage/Adminhtml/controllers/AjaxController.php new file mode 100644 index 00000000..55f54862 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/controllers/AjaxController.php @@ -0,0 +1,55 @@ + + */ +class Mage_Adminhtml_AjaxController extends Mage_Adminhtml_Controller_Action +{ + /** + * Ajax action for inline translation + * + */ + public function translateAction() + { + $translation = $this->getRequest()->getPost('translate'); + $area = $this->getRequest()->getPost('area'); + + //filtering + /** @var $filter Mage_Core_Model_Input_Filter_MaliciousCode */ + $filter = Mage::getModel('core/input_filter_maliciousCode'); + foreach ($translation as &$item) { + $item['custom'] = $filter->filter($item['custom']); + } + + echo Mage::helper('core/translate')->apply($translation, $area); + exit(); + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/Api/RoleController.php b/app/code/core/Mage/Adminhtml/controllers/Api/RoleController.php index 54391298..1724e682 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Api/RoleController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Api/RoleController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -206,6 +206,6 @@ protected function _addUserToRole($userId, $roleId) protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('api/roles'); + return Mage::getSingleton('admin/session')->isAllowed('system/api/roles'); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/Api/UserController.php b/app/code/core/Mage/Adminhtml/controllers/Api/UserController.php index fd5ad041..b1b35886 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Api/UserController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Api/UserController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Api_UserController extends Mage_Adminhtml_Controller_Action @@ -177,7 +177,7 @@ public function roleGridAction() protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('api/users'); + return Mage::getSingleton('admin/session')->isAllowed('system/api/users'); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/CacheController.php b/app/code/core/Mage/Adminhtml/controllers/CacheController.php index 98769b5f..e2e1154e 100644 --- a/app/code/core/Mage/Adminhtml/controllers/CacheController.php +++ b/app/code/core/Mage/Adminhtml/controllers/CacheController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -53,6 +53,7 @@ public function indexAction() */ public function flushAllAction() { + Mage::dispatchEvent('adminhtml_cache_flush_all'); Mage::app()->getCacheInstance()->flush(); $this->_getSession()->addSuccess(Mage::helper('adminhtml')->__("The cache storage has been flushed.")); $this->_redirect('*/*'); @@ -64,6 +65,7 @@ public function flushAllAction() public function flushSystemAction() { Mage::app()->cleanCache(); + Mage::dispatchEvent('adminhtml_cache_flush_system'); $this->_getSession()->addSuccess(Mage::helper('adminhtml')->__("The Magento cache storage has been flushed.")); $this->_redirect('*/*'); } @@ -123,6 +125,7 @@ public function massRefreshAction() if (!empty($types)) { foreach ($types as $type) { $tags = Mage::app()->getCacheInstance()->cleanType($type); + Mage::dispatchEvent('adminhtml_cache_refresh_type', array('type' => $type)); $updatedTypes++; } } @@ -187,6 +190,6 @@ public function cleanImagesAction() */ protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('cache'); + return Mage::getSingleton('admin/session')->isAllowed('system/cache'); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/Block/WidgetController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/Block/WidgetController.php index 5ffe7b35..88432621 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/Block/WidgetController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/Block/WidgetController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/BlockController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/BlockController.php index 8dcc11a0..8098daac 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/BlockController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/BlockController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/Page/WidgetController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/Page/WidgetController.php index 30c83e31..200866dc 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/Page/WidgetController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/Page/WidgetController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/PageController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/PageController.php index 54b334d0..c600585c 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/PageController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/PageController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -90,7 +90,8 @@ public function editAction() if ($id) { $model->load($id); if (! $model->getId()) { - Mage::getSingleton('adminhtml/session')->addError(Mage::helper('cms')->__('This page no longer exists.')); + Mage::getSingleton('adminhtml/session')->addError( + Mage::helper('cms')->__('This page no longer exists.')); $this->_redirect('*/*/'); return; } @@ -109,7 +110,11 @@ public function editAction() // 5. Build edit form $this->_initAction() - ->_addBreadcrumb($id ? Mage::helper('cms')->__('Edit Page') : Mage::helper('cms')->__('New Page'), $id ? Mage::helper('cms')->__('Edit Page') : Mage::helper('cms')->__('New Page')); + ->_addBreadcrumb( + $id ? Mage::helper('cms')->__('Edit Page') + : Mage::helper('cms')->__('New Page'), + $id ? Mage::helper('cms')->__('Edit Page') + : Mage::helper('cms')->__('New Page')); $this->renderLayout(); } @@ -133,18 +138,25 @@ public function saveAction() Mage::dispatchEvent('cms_page_prepare_save', array('page' => $model, 'request' => $this->getRequest())); + //validating + if (!$this->_validatePostData($data)) { + $this->_redirect('*/*/edit', array('page_id' => $model->getId(), '_current' => true)); + return; + } + // try to save it try { // save the data $model->save(); // display success message - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('cms')->__('The page has been saved.')); + Mage::getSingleton('adminhtml/session')->addSuccess( + Mage::helper('cms')->__('The page has been saved.')); // clear previously saved data from session Mage::getSingleton('adminhtml/session')->setFormData(false); // check if 'Save and Continue' if ($this->getRequest()->getParam('back')) { - $this->_redirect('*/*/edit', array('page_id' => $model->getId())); + $this->_redirect('*/*/edit', array('page_id' => $model->getId(), '_current'=>true)); return; } // go to grid @@ -155,7 +167,8 @@ public function saveAction() $this->_getSession()->addError($e->getMessage()); } catch (Exception $e) { - $this->_getSession()->addException($e, Mage::helper('cms')->__('An error occurred while saving the page.')); + $this->_getSession()->addException($e, + Mage::helper('cms')->__('An error occurred while saving the page.')); } $this->_getSession()->setFormData($data); @@ -180,7 +193,8 @@ public function deleteAction() $title = $model->getTitle(); $model->delete(); // display success message - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('cms')->__('The page has been deleted.')); + Mage::getSingleton('adminhtml/session')->addSuccess( + Mage::helper('cms')->__('The page has been deleted.')); // go to grid Mage::dispatchEvent('adminhtml_cmspage_on_delete', array('title' => $title, 'status' => 'success')); $this->_redirect('*/*/'); @@ -233,4 +247,30 @@ protected function _filterPostData($data) $data = $this->_filterDates($data, array('custom_theme_from', 'custom_theme_to')); return $data; } + + /** + * Validate post data + * + * @param array $data + * @return bool Return FALSE if someone item is invalid + */ + protected function _validatePostData($data) + { + $errorNo = true; + if (!empty($data['layout_update_xml']) || !empty($data['custom_layout_update_xml'])) { + /** @var $validatorCustomLayout Mage_Adminhtml_Model_LayoutUpdate_Validator */ + $validatorCustomLayout = Mage::getModel('adminhtml/layoutUpdate_validator'); + if (!empty($data['layout_update_xml']) && !$validatorCustomLayout->isValid($data['layout_update_xml'])) { + $errorNo = false; + } + if (!empty($data['custom_layout_update_xml']) + && !$validatorCustomLayout->isValid($data['custom_layout_update_xml'])) { + $errorNo = false; + } + foreach ($validatorCustomLayout->getMessages() as $message) { + $this->_getSession()->addError($message); + } + } + return $errorNo; + } } diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php index 989c6b52..92a1bd3f 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/Wysiwyg/ImagesController.php @@ -20,17 +20,16 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Images manage controller for Cms WYSIWYG editor * - * @category Mage - * @package Mage_Adminhtml - * @author Magento Core Team + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team */ class Mage_Adminhtml_Cms_Wysiwyg_ImagesController extends Mage_Adminhtml_Controller_Action { @@ -111,15 +110,30 @@ public function deleteFolderAction() } } + /** + * Delete file from media storage + * + * @return void + */ public function deleteFilesAction() { - $files = Mage::helper('core')->jsonDecode($this->getRequest()->getParam('files')); try { + if (!$this->getRequest()->isPost()) { + throw new Exception ('Wrong request.'); + } + $files = Mage::helper('core')->jsonDecode($this->getRequest()->getParam('files')); + + /** @var $helper Mage_Cms_Helper_Wysiwyg_Images */ $helper = Mage::helper('cms/wysiwyg_images'); $path = $this->getStorage()->getSession()->getCurrentPath(); foreach ($files as $file) { $file = $helper->idDecode($file); - $this->getStorage()->deleteFile($path . DS . $file); + $_filePath = realpath($path . DS . $file); + if (strpos($_filePath, realpath($path)) === 0 && + strpos($_filePath, realpath($helper->getStorageRoot())) === 0 + ) { + $this->getStorage()->deleteFile($path . DS . $file); + } } } catch (Exception $e) { $result = array('error' => true, 'message' => $e->getMessage()); @@ -155,7 +169,7 @@ public function onInsertAction() $filename = $this->getRequest()->getParam('filename'); $filename = $helper->idDecode($filename); $asIs = $this->getRequest()->getParam('as_is'); - + Mage::helper('catalog')->setStoreId($storeId); $helper->setStoreId($storeId); @@ -183,7 +197,7 @@ public function thumbnailAction() /** * Register storage model and return it * - * @return Mage_Cms_Model_Page_Wysiwyg_Images_Storage + * @return Mage_Cms_Model_Wysiwyg_Images_Storage */ public function getStorage() { @@ -206,4 +220,14 @@ protected function _saveSessionCurrentPath() ->setCurrentPath(Mage::helper('cms/wysiwyg_images')->getCurrentPath()); return $this; } + + /** + * Check current user permission on resource and privilege + * + * @return bool + */ + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('cms/media_gallery'); + } } diff --git a/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php b/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php index 8a6f5c2b..595ed689 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Cms/WysiwygController.php @@ -20,17 +20,16 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Wysiwyg controller for different purposes * - * @category Mage - * @package Mage_Adminhtml - * @author Magento Core Team + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team */ class Mage_Adminhtml_Cms_WysiwygController extends Mage_Adminhtml_Controller_Action { diff --git a/app/code/core/Mage/Adminhtml/controllers/DashboardController.php b/app/code/core/Mage/Adminhtml/controllers/DashboardController.php index 722ffb03..8afc56e7 100644 --- a/app/code/core/Mage/Adminhtml/controllers/DashboardController.php +++ b/app/code/core/Mage/Adminhtml/controllers/DashboardController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -43,19 +43,34 @@ public function indexAction() $this->renderLayout(); } + /** + * Gets most viewed products list + * + */ public function productsViewedAction() { - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/dashboard_tab_products_viewed')->toHtml()); + $this->loadLayout(); + $this->renderLayout(); } + /** + * Gets latest customers list + * + */ public function customersNewestAction() { - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/dashboard_tab_customers_newest')->toHtml()); + $this->loadLayout(); + $this->renderLayout(); } + /** + * Gets the list of most active customers + * + */ public function customersMostAction() { - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/dashboard_tab_customers_most')->toHtml()); + $this->loadLayout(); + $this->renderLayout(); } public function ajaxBlockAction() diff --git a/app/code/core/Mage/Adminhtml/controllers/IndexController.php b/app/code/core/Mage/Adminhtml/controllers/IndexController.php index 0ee8cdee..78d2845b 100644 --- a/app/code/core/Mage/Adminhtml/controllers/IndexController.php +++ b/app/code/core/Mage/Adminhtml/controllers/IndexController.php @@ -20,21 +20,35 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - +/** + * Index admin controller + * + * @category Mage + * @package Mage_Adminhtml + * @author Magento Core Team + */ class Mage_Adminhtml_IndexController extends Mage_Adminhtml_Controller_Action { - protected function _outTemplate($tplName, $data=array()) + /** + * Render specified template + * + * @param string $tplName + * @param array $data parameters required by template + */ + protected function _outTemplate($tplName, $data = array()) { $this->_initLayoutMessages('adminhtml/session'); $block = $this->getLayout()->createBlock('adminhtml/template')->setTemplate("$tplName.phtml"); - foreach ($data as $index=>$value) { + foreach ($data as $index => $value) { $block->assign($index, $value); } - $this->getResponse()->setBody($block->toHtml()); + $html = $block->toHtml(); + Mage::getSingleton('core/translate_inline')->processResponseBody($html); + $this->getResponse()->setBody($html); } /** @@ -45,12 +59,16 @@ public function indexAction() { $session = Mage::getSingleton('admin/session'); $url = $session->getUser()->getStartupPageUrl(); - if ($session->isFirstPageAfterLogin()) { // retain the "first page after login" value in session (before redirect) + if ($session->isFirstPageAfterLogin()) { + // retain the "first page after login" value in session (before redirect) $session->setIsFirstPageAfterLogin(true); } $this->_redirect($url); } + /** + * Administrator login action + */ public function loginAction() { if (Mage::getSingleton('admin/session')->isLoggedIn()) { @@ -58,41 +76,50 @@ public function loginAction() return; } $loginData = $this->getRequest()->getParam('login'); - $data = array(); + $username = (is_array($loginData) && array_key_exists('username', $loginData)) ? $loginData['username'] : null; - if( is_array($loginData) && array_key_exists('username', $loginData) ) { - $data['username'] = $loginData['username']; - } else { - $data['username'] = null; - } - #print_r($data); - $this->_outTemplate('login', $data); + $this->loadLayout(); + $this->renderLayout(); } + /** + * Administrator logout action + */ public function logoutAction() { - $auth = Mage::getSingleton('admin/session')->unsetAll(); - Mage::getSingleton('adminhtml/session')->unsetAll(); - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('You have logged out.')); + /** @var $adminSession Mage_Admin_Model_Session */ + $adminSession = Mage::getSingleton('admin/session'); + $adminSession->unsetAll(); + $adminSession->getCookie()->delete($adminSession->getSessionName()); + $adminSession->addSuccess(Mage::helper('adminhtml')->__('You have logged out.')); + $this->_redirect('*'); } + /** + * Global Search Action + */ public function globalSearchAction() { $searchModules = Mage::getConfig()->getNode("adminhtml/global_search"); $items = array(); - if ( !Mage::getSingleton('admin/session')->isAllowed('admin/global_search') ) { + if (!Mage::getSingleton('admin/session')->isAllowed('admin/global_search')) { $items[] = array( - 'id'=>'error', - 'type'=>'Error', - 'name'=>Mage::helper('adminhtml')->__('Access Denied'), - 'description'=>Mage::helper('adminhtml')->__('You have not enough permissions to use this functionality.') + 'id' => 'error', + 'type' => Mage::helper('adminhtml')->__('Error'), + 'name' => Mage::helper('adminhtml')->__('Access Denied'), + 'description' => Mage::helper('adminhtml')->__('You have not enough permissions to use this functionality.') ); $totalCount = 1; } else { if (empty($searchModules)) { - $items[] = array('id'=>'error', 'type'=>'Error', 'name'=>Mage::helper('adminhtml')->__('No search modules were registered'), 'description'=>Mage::helper('adminhtml')->__('Please make sure that all global admin search modules are installed and activated.')); + $items[] = array( + 'id' => 'error', + 'type' => Mage::helper('adminhtml')->__('Error'), + 'name' => Mage::helper('adminhtml')->__('No search modules were registered'), + 'description' => Mage::helper('adminhtml')->__('Please make sure that all global admin search modules are installed and activated.') + ); $totalCount = 1; } else { $start = $this->getRequest()->getParam('start', 1); @@ -110,7 +137,11 @@ public function globalSearchAction() continue; } $searchInstance = new $className(); - $results = $searchInstance->setStart($start)->setLimit($limit)->setQuery($query)->load()->getResults(); + $results = $searchInstance->setStart($start) + ->setLimit($limit) + ->setQuery($query) + ->load() + ->getResults(); $items = array_merge_recursive($items, $results); } $totalCount = sizeof($items); @@ -124,16 +155,25 @@ public function globalSearchAction() $this->getResponse()->setBody($block->toHtml()); } + /** + * Example action + */ public function exampleAction() { $this->_outTemplate('example'); } + /** + * Test action + */ public function testAction() { echo $this->getLayout()->createBlock('core/profiler')->toHtml(); } + /** + * Change locale action + */ public function changeLocaleAction() { $locale = $this->getRequest()->getParam('locale'); @@ -143,70 +183,206 @@ public function changeLocaleAction() $this->_redirectReferer(); } + /** + * Denied JSON action + */ public function deniedJsonAction() { $this->getResponse()->setBody($this->_getDeniedJson()); } + /** + * Retrieve response for deniedJsonAction() + */ protected function _getDeniedJson() { - return Mage::helper('core')->jsonEncode( - array( - 'ajaxExpired' => 1, - 'ajaxRedirect' => $this->getUrl('*/index/login') - ) - ); + return Mage::helper('core')->jsonEncode(array( + 'ajaxExpired' => 1, + 'ajaxRedirect' => $this->getUrl('*/index/login') + )); } + /** + * Denied IFrame action + */ public function deniedIframeAction() { $this->getResponse()->setBody($this->_getDeniedIframe()); } + /** + * Retrieve response for deniedIframeAction() + */ protected function _getDeniedIframe() { - return ''; + return ''; } - public function forgotpasswordAction () + /** + * Forgot administrator password action + */ + public function forgotpasswordAction() { - $email = $this->getRequest()->getParam('email'); + $email = (string) $this->getRequest()->getParam('email'); $params = $this->getRequest()->getParams(); + if (!empty($email) && !empty($params)) { - $collection = Mage::getResourceModel('admin/user_collection'); - /* @var $collection Mage_Admin_Model_Mysql4_User_Collection */ - $collection->addFieldToFilter('email', $email); - $collection->load(false); - - if ($collection->getSize() > 0) { - foreach ($collection as $item) { - $user = Mage::getModel('admin/user')->load($item->getId()); - if ($user->getId()) { - $pass = substr(md5(uniqid(rand(), true)), 0, 7); - $user->setPassword($pass); - $user->save(); - $user->setPlainPassword($pass); - $user->sendNewPasswordEmail(); - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('A new password was sent to your email address. Please check your email and click Back to Login.')); - $email = ''; + // Validate received data to be an email address + if (Zend_Validate::is($email, 'EmailAddress')) { + $collection = Mage::getResourceModel('admin/user_collection'); + /** @var $collection Mage_Admin_Model_Resource_User_Collection */ + $collection->addFieldToFilter('email', $email); + $collection->load(false); + + if ($collection->getSize() > 0) { + foreach ($collection as $item) { + $user = Mage::getModel('admin/user')->load($item->getId()); + if ($user->getId()) { + $newResetPasswordLinkToken = Mage::helper('admin')->generateResetPasswordLinkToken(); + $user->changeResetPasswordLinkToken($newResetPasswordLinkToken); + $user->save(); + $user->sendPasswordResetConfirmationEmail(); + } + break; } - break; } + $this->_getSession() + ->addSuccess(Mage::helper('adminhtml')->__('If there is an account associated with %s you will receive an email with a link to reset your password.', Mage::helper('adminhtml')->escapeHtml($email))); + $this->_redirect('*/*/login'); + return; } else { - Mage::getSingleton('adminhtml/session')->addError(Mage::helper('adminhtml')->__('Cannot find the email address.')); + $this->_getSession()->addError($this->__('Invalid email address.')); } } elseif (!empty($params)) { - Mage::getSingleton('adminhtml/session')->addError(Mage::helper('adminhtml')->__('The email address is empty.')); + $this->_getSession()->addError(Mage::helper('adminhtml')->__('The email address is empty.')); } + $this->loadLayout(); + $this->renderLayout(); + } + /** + * Display reset forgotten password form + * + * User is redirected on this action when he clicks on the corresponding link in password reset confirmation email + */ + public function resetPasswordAction() + { + $resetPasswordLinkToken = (string) $this->getRequest()->getQuery('token'); + $userId = (int) $this->getRequest()->getQuery('id'); + try { + $this->_validateResetPasswordLinkToken($userId, $resetPasswordLinkToken); + $data = array( + 'userId' => $userId, + 'resetPasswordLinkToken' => $resetPasswordLinkToken + ); + $this->_outTemplate('resetforgottenpassword', $data); + } catch (Exception $exception) { + $this->_getSession()->addError(Mage::helper('adminhtml')->__('Your password reset link has expired.')); + $this->_redirect('*/*/forgotpassword', array('_nosecret' => true)); + } + } + + /** + * Reset forgotten password + * + * Used to handle data recieved from reset forgotten password form + */ + public function resetPasswordPostAction() + { + $resetPasswordLinkToken = (string) $this->getRequest()->getQuery('token'); + $userId = (int) $this->getRequest()->getQuery('id'); + $password = (string) $this->getRequest()->getPost('password'); + $passwordConfirmation = (string) $this->getRequest()->getPost('confirmation'); + + try { + $this->_validateResetPasswordLinkToken($userId, $resetPasswordLinkToken); + } catch (Exception $exception) { + $this->_getSession()->addError(Mage::helper('adminhtml')->__('Your password reset link has expired.')); + $this->_redirect('*/*/'); + return; + } + + $errorMessages = array(); + if (iconv_strlen($password) <= 0) { + array_push($errorMessages, Mage::helper('adminhtml')->__('New password field cannot be empty.')); + } + /** @var $user Mage_Admin_Model_User */ + $user = Mage::getModel('admin/user')->load($userId); + + $user->setNewPassword($password); + $user->setPasswordConfirmation($passwordConfirmation); + $validationErrorMessages = $user->validate(); + if (is_array($validationErrorMessages)) { + $errorMessages = array_merge($errorMessages, $validationErrorMessages); + } + + if (!empty($errorMessages)) { + foreach ($errorMessages as $errorMessage) { + $this->_getSession()->addError($errorMessage); + } + $data = array( + 'userId' => $userId, + 'resetPasswordLinkToken' => $resetPasswordLinkToken + ); + $this->_outTemplate('resetforgottenpassword', $data); + return; + } - $data = array( - 'email' => $email - ); - $this->_outTemplate('forgotpassword', $data); + try { + // Empty current reset password token i.e. invalidate it + $user->setRpToken(null); + $user->setRpTokenCreatedAt(null); + $user->setPasswordConfirmation(null); + $user->save(); + $this->_getSession()->addSuccess(Mage::helper('adminhtml')->__('Your password has been updated.')); + $this->_redirect('*/*/login'); + } catch (Exception $exception) { + $this->_getSession()->addError($exception->getMessage()); + $data = array( + 'userId' => $userId, + 'resetPasswordLinkToken' => $resetPasswordLinkToken + ); + $this->_outTemplate('resetforgottenpassword', $data); + return; + } } + /** + * Check if password reset token is valid + * + * @param int $userId + * @param string $resetPasswordLinkToken + * @throws Mage_Core_Exception + */ + protected function _validateResetPasswordLinkToken($userId, $resetPasswordLinkToken) + { + if (!is_int($userId) + || !is_string($resetPasswordLinkToken) + || empty($resetPasswordLinkToken) + || empty($userId) + || $userId < 0 + ) { + throw Mage::exception('Mage_Core', Mage::helper('adminhtml')->__('Invalid password reset token.')); + } + + /** @var $user Mage_Admin_Model_User */ + $user = Mage::getModel('admin/user')->load($userId); + if (!$user || !$user->getId()) { + throw Mage::exception('Mage_Core', Mage::helper('adminhtml')->__('Wrong account specified.')); + } + $userToken = $user->getRpToken(); + if (strcmp($userToken, $resetPasswordLinkToken) != 0 || $user->isResetPasswordLinkTokenExpired()) { + throw Mage::exception('Mage_Core', Mage::helper('adminhtml')->__('Your password reset link has expired.')); + } + } + + /** + * Check if user has permissions to access this controller + * + * @return boolean + */ protected function _isAllowed() { return true; diff --git a/app/code/core/Mage/Adminhtml/controllers/JsonController.php b/app/code/core/Mage/Adminhtml/controllers/JsonController.php index 6d554ef1..9008f236 100644 --- a/app/code/core/Mage/Adminhtml/controllers/JsonController.php +++ b/app/code/core/Mage/Adminhtml/controllers/JsonController.php @@ -20,17 +20,24 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Json controller * + * @category Mage + * @package Mage_Adminhtml * @author Magento Core Team */ class Mage_Adminhtml_JsonController extends Mage_Adminhtml_Controller_Action { + /** + * Return JSON-encoded array of country regions + * + * @return string + */ public function countryRegionAction() { $arrRes = array(); diff --git a/app/code/core/Mage/Adminhtml/controllers/Media/EditorController.php b/app/code/core/Mage/Adminhtml/controllers/Media/EditorController.php index 1e332062..7982ec7c 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Media/EditorController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Media/EditorController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/Media/UploaderController.php b/app/code/core/Mage/Adminhtml/controllers/Media/UploaderController.php index c0825ef8..940e97cd 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Media/UploaderController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Media/UploaderController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/NotificationController.php b/app/code/core/Mage/Adminhtml/controllers/NotificationController.php index 9a3fa512..fd0f92bc 100644 --- a/app/code/core/Mage/Adminhtml/controllers/NotificationController.php +++ b/app/code/core/Mage/Adminhtml/controllers/NotificationController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -62,11 +62,9 @@ public function markAsReadAction() $model->setIsRead(1) ->save(); $session->addSuccess(Mage::helper('adminnotification')->__('The message has been marked as read.')); - } - catch (Mage_Core_Exception $e) { + } catch (Mage_Core_Exception $e) { $session->addError($e->getMessage()); - } - catch (Exception $e) { + } catch (Exception $e) { $session->addException($e, Mage::helper('adminnotification')->__('An error occurred while marking notification as read.')); } @@ -82,8 +80,7 @@ public function massMarkAsReadAction() $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { $session->addError(Mage::helper('adminnotification')->__('Please select messages.')); - } - else { + } else { try { foreach ($ids as $id) { $model = Mage::getModel('adminnotification/inbox') @@ -98,8 +95,7 @@ public function massMarkAsReadAction() ); } catch (Mage_Core_Exception $e) { $session->addError($e->getMessage()); - } - catch (Exception $e) { + } catch (Exception $e) { $session->addException($e, Mage::helper('adminnotification')->__('An error occurred while marking the messages as read.')); } } @@ -122,15 +118,13 @@ public function removeAction() $model->setIsRemove(1) ->save(); $session->addSuccess(Mage::helper('adminnotification')->__('The message has been removed.')); - } - catch (Mage_Core_Exception $e) { + } catch (Mage_Core_Exception $e) { $session->addError($e->getMessage()); - } - catch (Exception $e) { + } catch (Exception $e) { $session->addException($e, Mage::helper('adminnotification')->__('An error occurred while removing the message.')); } - $this->_redirectReferer(); + $this->_redirect('*/*/'); return; } $this->_redirect('*/*/'); @@ -142,8 +136,7 @@ public function massRemoveAction() $ids = $this->getRequest()->getParam('notification'); if (!is_array($ids)) { $session->addError(Mage::helper('adminnotification')->__('Please select messages.')); - } - else { + } else { try { foreach ($ids as $id) { $model = Mage::getModel('adminnotification/inbox') @@ -158,8 +151,7 @@ public function massRemoveAction() ); } catch (Mage_Core_Exception $e) { $session->addError($e->getMessage()); - } - catch (Exception $e) { + } catch (Exception $e) { $session->addException($e, Mage::helper('adminnotification')->__('An error occurred while removing messages.')); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php b/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php index 6854b00a..88ce0189 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Permissions/RoleController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -124,7 +124,9 @@ public function editRoleAction() ->setRoleInfo($role) ->setTemplate('permissions/roleinfo.phtml') ); - $this->_addJs($this->getLayout()->createBlock('adminhtml/template')->setTemplate('permissions/role_users_grid_js.phtml')); + $this->_addJs( + $this->getLayout()->createBlock('adminhtml/template')->setTemplate('permissions/role_users_grid_js.phtml') + ); $this->renderLayout(); } @@ -183,10 +185,15 @@ public function saveRoleAction() } try { - $role->setName($this->getRequest()->getParam('rolename', false)) + $roleName = $this->getRequest()->getParam('rolename', false); + + $role->setName($roleName) ->setPid($this->getRequest()->getParam('parent_id', false)) ->setRoleType('G'); - Mage::dispatchEvent('admin_permissions_role_prepare_save', array('object' => $role, 'request' => $this->getRequest())); + Mage::dispatchEvent( + 'admin_permissions_role_prepare_save', + array('object' => $role, 'request' => $this->getRequest()) + ); $role->save(); Mage::getModel("admin/rules") @@ -203,7 +210,7 @@ public function saveRoleAction() } $rid = $role->getId(); - Mage::getSingleton('adminhtml/session')->addSuccess($this->__('The role has beensuccessfully saved.')); + Mage::getSingleton('adminhtml/session')->addSuccess($this->__('The role has been successfully saved.')); } catch (Mage_Core_Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); } catch (Exception $e) { @@ -211,7 +218,7 @@ public function saveRoleAction() } //$this->getResponse()->setRedirect($this->getUrl("*/*/editrole/rid/$rid")); - $this->_redirect('*/*/editrole', array('rid' => $rid)); + $this->_redirect('*/*/'); return; } @@ -221,7 +228,9 @@ public function saveRoleAction() */ public function editrolegridAction() { - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/permissions_role_grid_user')->toHtml()); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/permissions_role_grid_user')->toHtml() + ); } /** diff --git a/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php b/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php index e7fb1052..b1cacfcc 100644 --- a/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php +++ b/app/code/core/Mage/Adminhtml/controllers/Permissions/UserController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Adminhtml_Permissions_UserController extends Mage_Adminhtml_Controller_Action @@ -81,12 +81,17 @@ public function editAction() Mage::register('permissions_user', $model); + if (isset($id)) { + $breadcrumb = $this->__('Edit User'); + } else { + $breadcrumb = $this->__('New User'); + } $this->_initAction() - ->_addBreadcrumb($id ? $this->__('Edit User') : $this->__('New User'), $id ? $this->__('Edit User') : $this->__('New User')) - ->_addContent($this->getLayout()->createBlock('adminhtml/permissions_user_edit')->setData('action', $this->getUrl('*/permissions_user/save'))) - ->_addLeft($this->getLayout()->createBlock('adminhtml/permissions_user_edit_tabs')); + ->_addBreadcrumb($breadcrumb, $breadcrumb); + + $this->getLayout()->getBlock('adminhtml.permissions.user.edit') + ->setData('action', $this->getUrl('*/permissions_user/save')); - $this->_addJs($this->getLayout()->createBlock('adminhtml/template')->setTemplate('permissions/user_roles_grid_js.phtml')); $this->renderLayout(); } @@ -142,7 +147,7 @@ public function saveAction() } Mage::getSingleton('adminhtml/session')->addSuccess($this->__('The user has been saved.')); Mage::getSingleton('adminhtml/session')->setUserData(false); - $this->_redirect('*/*/edit', array('user_id' => $model->getUserId())); + $this->_redirect('*/*/'); return; } catch (Mage_Core_Exception $e) { Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); @@ -192,7 +197,11 @@ public function rolesGridAction() } Mage::register('permissions_user', $model); - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/permissions_user_edit_tab_roles')->toHtml()); + $this->getResponse()->setBody( + $this->getLayout() + ->createBlock('adminhtml/permissions_user_edit_tab_roles') + ->toHtml() + ); } public function roleGridAction() diff --git a/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php b/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php index 8b69868c..15fd4512 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/AccountController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php b/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php index 551c45c3..7dfa1725 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/BackupController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -66,43 +66,105 @@ public function gridAction() /** * Create backup action + * + * @return Mage_Adminhtml_Controller_Action */ public function createAction() { + if (!$this->getRequest()->isAjax()) { + return $this->getUrl('*/*/index'); + } + + $response = new Varien_Object(); + + /** + * @var Mage_Backup_Helper_Data $helper + */ + $helper = Mage::helper('backup'); + try { - $backupDb = Mage::getModel('backup/db'); - $backup = Mage::getModel('backup/backup') + $type = $this->getRequest()->getParam('type'); + + if ($type == Mage_Backup_Helper_Data::TYPE_SYSTEM_SNAPSHOT + && $this->getRequest()->getParam('exclude_media') + ) { + $type = Mage_Backup_Helper_Data::TYPE_SNAPSHOT_WITHOUT_MEDIA; + } + + $backupManager = Mage_Backup::getBackupInstance($type) + ->setBackupExtension($helper->getExtensionByType($type)) ->setTime(time()) - ->setType('db') - ->setPath(Mage::getBaseDir("var") . DS . "backups"); + ->setBackupsDir($helper->getBackupsDir()); + + $backupManager->setName($this->getRequest()->getParam('backup_name')); - Mage::register('backup_model', $backup); + Mage::register('backup_manager', $backupManager); - $backupDb->createBackup($backup); - $this->_getSession()->addSuccess(Mage::helper('adminhtml')->__('The backup has been created.')); + if ($this->getRequest()->getParam('maintenance_mode')) { + $turnedOn = $helper->turnOnMaintenanceMode(); + + if (!$turnedOn) { + $response->setError( + Mage::helper('backup')->__('You do not have sufficient permissions to enable Maintenance Mode during this operation.') + . ' ' . Mage::helper('backup')->__('Please either unselect the "Put store on the maintenance mode" checkbox or update your permissions to proceed with the backup."') + ); + $backupManager->setErrorMessage(Mage::helper('backup')->__("System couldn't put store on the maintenance mode")); + return $this->getResponse()->setBody($response->toJson()); + } + } + + if ($type != Mage_Backup_Helper_Data::TYPE_DB) { + $backupManager->setRootDir(Mage::getBaseDir()) + ->addIgnorePaths($helper->getBackupIgnorePaths()); + } + + $successMessage = $helper->getCreateSuccessMessageByType($type); + + $backupManager->create(); + + $this->_getSession()->addSuccess($successMessage); + + $response->setRedirectUrl($this->getUrl('*/*/index')); + } catch (Mage_Backup_Exception_NotEnoughFreeSpace $e) { + $errorMessage = Mage::helper('backup')->__('Not enough free space to create backup.'); + } catch (Mage_Backup_Exception_NotEnoughPermissions $e) { + Mage::log($e->getMessage()); + $errorMessage = Mage::helper('backup')->__('Not enough permissions to create backup.'); + } catch (Exception $e) { + Mage::log($e->getMessage()); + $errorMessage = Mage::helper('backup')->__('An error occurred while creating the backup.'); } - catch (Exception $e) { - $this->_getSession()->addException($e, Mage::helper('adminhtml')->__('An error occurred while creating the backup.')); + + if (!empty($errorMessage)) { + $response->setError($errorMessage); + $backupManager->setErrorMessage($errorMessage); } - $this->_redirect('*/*'); + + if ($this->getRequest()->getParam('maintenance_mode')) { + $helper->turnOffMaintenanceMode(); + } + + $this->getResponse()->setBody($response->toJson()); } /** * Download backup action + * + * @return Mage_Adminhtml_Controller_Action */ public function downloadAction() { - $backup = Mage::getModel('backup/backup') - ->setTime((int)$this->getRequest()->getParam('time')) - ->setType($this->getRequest()->getParam('type')) - ->setPath(Mage::getBaseDir("var") . DS . "backups"); /* @var $backup Mage_Backup_Model_Backup */ + $backup = Mage::getModel('backup/backup')->loadByTimeAndType( + $this->getRequest()->getParam('time'), + $this->getRequest()->getParam('type') + ); - if (!$backup->exists()) { - $this->_redirect('*/*'); + if (!$backup->getTime() || !$backup->exists()) { + return $this->_redirect('*/*'); } - $fileName = 'backup-' . date('YmdHis', $backup->getTime()) . '.sql.gz'; + $fileName = Mage::helper('backup')->generateBackupDownloadName($backup); $this->_prepareDownloadResponse($fileName, null, 'application/octet-stream', $backup->getSize()); @@ -113,32 +175,190 @@ public function downloadAction() } /** - * Delete backup action + * Rollback Action + * + * @return Mage_Adminhtml_Controller_Action */ - public function deleteAction() + public function rollbackAction() { + if (!Mage::helper('backup')->isRollbackAllowed()){ + return $this->_forward('denied'); + } + + if (!$this->getRequest()->isAjax()) { + return $this->getUrl('*/*/index'); + } + + $helper = Mage::helper('backup'); + $response = new Varien_Object(); + try { - $backup = Mage::getModel('backup/backup') - ->setTime((int)$this->getRequest()->getParam('time')) - ->setType($this->getRequest()->getParam('type')) - ->setPath(Mage::getBaseDir("var") . DS . "backups") - ->deleteFile(); + /* @var $backup Mage_Backup_Model_Backup */ + $backup = Mage::getModel('backup/backup')->loadByTimeAndType( + $this->getRequest()->getParam('time'), + $this->getRequest()->getParam('type') + ); + + if (!$backup->getTime() || !$backup->exists()) { + return $this->_redirect('*/*'); + } + + if (!$backup->getTime()) { + throw new Mage_Backup_Exception_CantLoadSnapshot(); + } + + $type = $backup->getType(); + + $backupManager = Mage_Backup::getBackupInstance($type) + ->setBackupExtension($helper->getExtensionByType($type)) + ->setTime($backup->getTime()) + ->setBackupsDir($helper->getBackupsDir()) + ->setName($backup->getName(), false) + ->setResourceModel(Mage::getResourceModel('backup/db')); + + Mage::register('backup_manager', $backupManager); + + $passwordValid = Mage::getModel('backup/backup')->validateUserPassword( + $this->getRequest()->getParam('password') + ); - Mage::register('backup_model', $backup); + if (!$passwordValid) { + $response->setError(Mage::helper('backup')->__('Invalid Password.')); + $backupManager->setErrorMessage(Mage::helper('backup')->__('Invalid Password.')); + return $this->getResponse()->setBody($response->toJson()); + } - $this->_getSession()->addSuccess(Mage::helper('adminhtml')->__('Backup record was deleted.')); + if ($this->getRequest()->getParam('maintenance_mode')) { + $turnedOn = $helper->turnOnMaintenanceMode(); + + if (!$turnedOn) { + $response->setError( + Mage::helper('backup')->__('You do not have sufficient permissions to enable Maintenance Mode during this operation.') + . ' ' . Mage::helper('backup')->__('Please either unselect the "Put store on the maintenance mode" checkbox or update your permissions to proceed with the rollback."') + ); + $backupManager->setErrorMessage(Mage::helper('backup')->__("System couldn't put store on the maintenance mode")); + return $this->getResponse()->setBody($response->toJson()); + } + } + + if ($type != Mage_Backup_Helper_Data::TYPE_DB) { + + $backupManager->setRootDir(Mage::getBaseDir()) + ->addIgnorePaths($helper->getRollbackIgnorePaths()); + + if ($this->getRequest()->getParam('use_ftp', false)) { + $backupManager->setUseFtp( + $this->getRequest()->getParam('ftp_host', ''), + $this->getRequest()->getParam('ftp_user', ''), + $this->getRequest()->getParam('ftp_pass', ''), + $this->getRequest()->getParam('ftp_path', '') + ); + } + } + + $backupManager->rollback(); + + $helper->invalidateCache()->invalidateIndexer(); + + $adminSession = $this->_getSession(); + $adminSession->unsetAll(); + $adminSession->getCookie()->delete($adminSession->getSessionName()); + + $response->setRedirectUrl($this->getUrl('*')); + } catch (Mage_Backup_Exception_CantLoadSnapshot $e) { + $errorMsg = Mage::helper('backup')->__('Backup file not found'); + } catch (Mage_Backup_Exception_FtpConnectionFailed $e) { + $errorMsg = Mage::helper('backup')->__('Failed to connect to FTP'); + } catch (Mage_Backup_Exception_FtpValidationFailed $e) { + $errorMsg = Mage::helper('backup')->__('Failed to validate FTP'); + } catch (Mage_Backup_Exception_NotEnoughPermissions $e) { + Mage::log($e->getMessage()); + $errorMsg = Mage::helper('backup')->__('Not enough permissions to perform rollback'); + } catch (Exception $e) { + Mage::log($e->getMessage()); + $errorMsg = Mage::helper('backup')->__('Failed to rollback'); } - catch (Exception $e) { - // Nothing + + if (!empty($errorMsg)) { + $response->setError($errorMsg); + $backupManager->setErrorMessage($errorMsg); } - $this->_redirect('*/*/'); + if ($this->getRequest()->getParam('maintenance_mode')) { + $helper->turnOffMaintenanceMode(); + } + $this->getResponse()->setBody($response->toJson()); } + /** + * Delete backups mass action + * + * @return Mage_Adminhtml_Controller_Action + */ + public function massDeleteAction() + { + $backupIds = $this->getRequest()->getParam('ids', array()); + + if (!is_array($backupIds) || !count($backupIds)) { + return $this->_redirect('*/*/index'); + } + + /** @var $backupModel Mage_Backup_Model_Backup */ + $backupModel = Mage::getModel('backup/backup'); + $resultData = new Varien_Object(); + $resultData->setIsSuccess(false); + $resultData->setDeleteResult(array()); + Mage::register('backup_manager', $resultData); + + $deleteFailMessage = Mage::helper('backup')->__('Failed to delete one or several backups.'); + + try { + $allBackupsDeleted = true; + + foreach ($backupIds as $id) { + list($time, $type) = explode('_', $id); + $backupModel + ->loadByTimeAndType($time, $type) + ->deleteFile(); + + if ($backupModel->exists()) { + $allBackupsDeleted = false; + $result = Mage::helper('adminhtml')->__('failed'); + } else { + $result = Mage::helper('adminhtml')->__('successful'); + } + + $resultData->setDeleteResult( + array_merge($resultData->getDeleteResult(), array($backupModel->getFileName() . ' ' . $result)) + ); + } + + $resultData->setIsSuccess(true); + if ($allBackupsDeleted) { + $this->_getSession()->addSuccess( + Mage::helper('backup')->__('The selected backup(s) has been deleted.') + ); + } + else { + throw new Exception($deleteFailMessage); + } + } catch (Exception $e) { + $resultData->setIsSuccess(false); + $this->_getSession()->addError($deleteFailMessage); + } + + return $this->_redirect('*/*/index'); + } + + /** + * Check Permissions for all actions + * + * @return bool + */ protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('system/tools/backup'); + return Mage::getSingleton('admin/session')->isAllowed('system/tools/backup' ); } /** diff --git a/app/code/core/Mage/Adminhtml/controllers/System/CacheController.php b/app/code/core/Mage/Adminhtml/controllers/System/CacheController.php index 5a9ade09..85bd35c5 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/CacheController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/CacheController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/System/Config/System/StorageController.php b/app/code/core/Mage/Adminhtml/controllers/System/Config/System/StorageController.php new file mode 100644 index 00000000..12c10720 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/controllers/System/Config/System/StorageController.php @@ -0,0 +1,183 @@ + + */ +class Mage_Adminhtml_System_Config_System_StorageController extends Mage_Adminhtml_Controller_Action +{ + /** + * Return file storage singleton + * + * @return Mage_Core_Model_File_Storage + */ + protected function _getSyncSingleton() + { + return Mage::getSingleton('core/file_storage'); + } + + /** + * Return synchronize process status flag + * + * @return Mage_Core_Model_File_Storage_Flag + */ + protected function _getSyncFlag() + { + return $this->_getSyncSingleton()->getSyncFlag(); + } + + /** + * Synchronize action between storages + * + * @return void + */ + public function synchronizeAction() + { + session_write_close(); + + if (!isset($_REQUEST['storage'])) { + return; + } + + $flag = $this->_getSyncFlag(); + if ($flag && $flag->getState() == Mage_Core_Model_File_Storage_Flag::STATE_RUNNING + && $flag->getLastUpdate() + && time() <= (strtotime($flag->getLastUpdate()) + Mage_Core_Model_File_Storage_Flag::FLAG_TTL) + ) { + return; + } + + $flag->setState(Mage_Core_Model_File_Storage_Flag::STATE_RUNNING)->save(); + Mage::getSingleton('admin/session')->setSyncProcessStopWatch(false); + + $storage = array('type' => (int) $_REQUEST['storage']); + if (isset($_REQUEST['connection']) && !empty($_REQUEST['connection'])) { + $storage['connection'] = $_REQUEST['connection']; + } + + try { + $this->_getSyncSingleton()->synchronize($storage); + } catch (Exception $e) { + Mage::logException($e); + $flag->passError($e); + } + + $flag->setState(Mage_Core_Model_File_Storage_Flag::STATE_FINISHED)->save(); + } + + /** + * Retrieve synchronize process state and it's parameters in json format + * + * @return void + */ + public function statusAction() + { + $result = array(); + $flag = $this->_getSyncFlag(); + + if ($flag) { + $state = $flag->getState(); + + switch ($state) { + case Mage_Core_Model_File_Storage_Flag::STATE_INACTIVE: + $flagData = $flag->getFlagData(); + if (is_array($flagData)) { + if (isset($flagData['destination']) && !empty($flagData['destination'])) { + $result['destination'] = $flagData['destination']; + } + } + + $state = Mage_Core_Model_File_Storage_Flag::STATE_INACTIVE; + break; + case Mage_Core_Model_File_Storage_Flag::STATE_RUNNING: + if (!$flag->getLastUpdate() + || time() <= (strtotime($flag->getLastUpdate()) + Mage_Core_Model_File_Storage_Flag::FLAG_TTL) + ) { + $flagData = $flag->getFlagData(); + if (is_array($flagData) + && isset($flagData['source']) && !empty($flagData['source']) + && isset($flagData['destination']) && !empty($flagData['destination']) + ) { + $result['message'] = Mage::helper('adminhtml')->__('Synchronizing %s to %s', $flagData['source'], $flagData['destination']); + } else { + $result['message'] = Mage::helper('adminhtml')->__('Synchronizing...'); + } + + break; + } else { + $flagData = $flag->getFlagData(); + if (is_array($flagData) + && !(isset($flagData['timeout_reached']) && $flagData['timeout_reached']) + ) { + Mage::logException(new Mage_Exception( + Mage::helper('adminhtml')->__('Timeout limit for response from synchronize process was reached.') + )); + + $state = Mage_Core_Model_File_Storage_Flag::STATE_FINISHED; + + $flagData['has_errors'] = true; + $flagData['timeout_reached'] = true; + + $flag->setState($state) + ->setFlagData($flagData) + ->save(); + } + } + case Mage_Core_Model_File_Storage_Flag::STATE_FINISHED: + Mage::dispatchEvent('add_synchronize_message'); + + $state = Mage_Core_Model_File_Storage_Flag::STATE_NOTIFIED; + case Mage_Core_Model_File_Storage_Flag::STATE_NOTIFIED: + $block = Mage::getSingleton('core/layout') + ->createBlock('adminhtml/notification_toolbar') + ->setTemplate('notification/toolbar.phtml'); + $result['html'] = $block->toHtml(); + + $flagData = $flag->getFlagData(); + if (is_array($flagData)) { + if (isset($flagData['has_errors']) && $flagData['has_errors']) { + $result['has_errors'] = true; + } + } + + break; + default: + $state = Mage_Core_Model_File_Storage_Flag::STATE_INACTIVE; + break; + } + } else { + $state = Mage_Core_Model_File_Storage_Flag::STATE_INACTIVE; + } + $result['state'] = $state; + + $result = Mage::helper('core')->jsonEncode($result); + Mage::app()->getResponse()->setBody($result); + } +} diff --git a/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php b/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php index 28e30a7b..414ffd02 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/ConfigController.php @@ -20,13 +20,12 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** - * config controller + * Configuration controller * * @category Mage * @package Mage_Adminhtml @@ -35,7 +34,31 @@ class Mage_Adminhtml_System_ConfigController extends Mage_Adminhtml_Controller_Action { /** - * Enter description here... + * Whether current section is allowed + * + * @var bool + */ + protected $_isSectionAllowedFlag = true; + + /** + * Controller predispatch method + * Check if current section is found and is allowed + * + * @return Mage_Adminhtml_System_ConfigController + */ + public function preDispatch() + { + parent::preDispatch(); + + if ($this->getRequest()->getParam('section')) { + $this->_isSectionAllowedFlag = $this->_isSectionAllowed($this->getRequest()->getParam('section')); + } + + return $this; + } + + /** + * Index action * */ public function indexAction() @@ -44,7 +67,7 @@ public function indexAction() } /** - * Enter description here... + * Edit configuration section * */ public function editAction() @@ -67,25 +90,33 @@ public function editAction() $this->loadLayout(); $this->_setActiveMenu('system/config'); + $this->getLayout()->getBlock('menu')->setAdditionalCacheKeyInfo(array($current)); - $this->_addBreadcrumb(Mage::helper('adminhtml')->__('System'), Mage::helper('adminhtml')->__('System'), $this->getUrl('*/system')); + $this->_addBreadcrumb(Mage::helper('adminhtml')->__('System'), Mage::helper('adminhtml')->__('System'), + $this->getUrl('*/system')); $this->getLayout()->getBlock('left') ->append($this->getLayout()->createBlock('adminhtml/system_config_tabs')->initTabs()); - if ($this->_isSectionAllowed($this->getRequest()->getParam('section'))) { + if ($this->_isSectionAllowedFlag) { $this->_addContent($this->getLayout()->createBlock('adminhtml/system_config_edit')->initForm()); - $this->_addJs($this->getLayout()->createBlock('adminhtml/template')->setTemplate('system/shipping/ups.phtml')); - $this->_addJs($this->getLayout()->createBlock('adminhtml/template')->setTemplate('system/config/js.phtml')); - $this->_addJs($this->getLayout()->createBlock('adminhtml/template')->setTemplate('system/shipping/applicable_country.phtml')); + $this->_addJs($this->getLayout() + ->createBlock('adminhtml/template') + ->setTemplate('system/shipping/ups.phtml')); + $this->_addJs($this->getLayout() + ->createBlock('adminhtml/template') + ->setTemplate('system/config/js.phtml')); + $this->_addJs($this->getLayout() + ->createBlock('adminhtml/template') + ->setTemplate('system/shipping/applicable_country.phtml')); $this->renderLayout(); } } /** - * Enter description here... + * Save configuration * */ public function saveAction() @@ -130,6 +161,11 @@ public function saveAction() // reinit configuration Mage::getConfig()->reinit(); + Mage::dispatchEvent('admin_system_config_section_save_after', array( + 'website' => $website, + 'store' => $store, + 'section' => $section + )); Mage::app()->reinitStores(); // website and store codes can be used in event implementation, so set them as well @@ -144,7 +180,9 @@ public function saveAction() } } catch (Exception $e) { - $session->addException($e, Mage::helper('adminhtml')->__('An error occurred while saving this configuration:').' '.$e->getMessage()); + $session->addException($e, + Mage::helper('adminhtml')->__('An error occurred while saving this configuration:') . ' ' + . $e->getMessage()); } $this->_saveState($this->getRequest()->getPost('config_state')); @@ -164,20 +202,19 @@ protected function _saveSection () } /** - * Description goes here... + * Advanced save procedure */ - protected function _saveAdvanced () + protected function _saveAdvanced() { Mage::app()->cleanCache( array( 'layout', Mage_Core_Model_Layout_Update::LAYOUT_GENERAL_CACHE_TAG - ) - ); + )); } /** - * action for ajax saving of fieldset state + * Save fieldset state through AJAX * */ public function stateAction() @@ -195,68 +232,29 @@ public function stateAction() } /** - * Enter description here... + * Export shipping table rates in csv format * */ public function exportTableratesAction() { - $websiteModel = Mage::app()->getWebsite($this->getRequest()->getParam('website')); - + $fileName = 'tablerates.csv'; + /** @var $gridBlock Mage_Adminhtml_Block_Shipping_Carrier_Tablerate_Grid */ + $gridBlock = $this->getLayout()->createBlock('adminhtml/shipping_carrier_tablerate_grid'); + $website = Mage::app()->getWebsite($this->getRequest()->getParam('website')); if ($this->getRequest()->getParam('conditionName')) { $conditionName = $this->getRequest()->getParam('conditionName'); } else { - $conditionName = $websiteModel->getConfig('carriers/tablerate/condition_name'); - } - - $tableratesCollection = Mage::getResourceModel('shipping/carrier_tablerate_collection'); - /* @var $tableratesCollection Mage_Shipping_Model_Mysql4_Carrier_Tablerate_Collection */ - $tableratesCollection->setConditionFilter($conditionName); - $tableratesCollection->setWebsiteFilter($websiteModel->getId()); - $tableratesCollection->load(); - - $csv = ''; - - $conditionName = Mage::getModel('shipping/carrier_tablerate')->getCode('condition_name_short', $conditionName); - - $csvHeader = array('"'.Mage::helper('adminhtml')->__('Country').'"', '"'.Mage::helper('adminhtml')->__('Region/State').'"', '"'.Mage::helper('adminhtml')->__('Zip/Postal Code').'"', '"'.$conditionName.'"', '"'.Mage::helper('adminhtml')->__('Shipping Price').'"'); - $csv .= implode(',', $csvHeader)."\n"; - - foreach ($tableratesCollection->getItems() as $item) { - if ($item->getData('dest_country') == '') { - $country = '*'; - } else { - $country = $item->getData('dest_country'); - } - if ($item->getData('dest_region') == '') { - $region = '*'; - } else { - $region = $item->getData('dest_region'); - } - if ($item->getData('dest_zip') == '') { - $zip = '*'; - } else { - $zip = $item->getData('dest_zip'); - } - - $csvData = array($country, $region, $zip, $item->getData('condition_value'), $item->getData('price')); - foreach ($csvData as $cell) { - $cell = '"'.str_replace('"', '""', $cell).'"'; - } - $csv .= implode(',', $csvData)."\n"; + $conditionName = $website->getConfig('carriers/tablerate/condition_name'); } - - header('Pragma: public'); - header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - - header("Content-type: application/octet-stream"); - header("Content-disposition: attachment; filename=tablerates.csv"); - echo $csv; - exit; + $gridBlock->setWebsiteId($website->getId())->setConditionName($conditionName); + $content = $gridBlock->getCsvFile(); + $this->_prepareDownloadResponse($fileName, $content); } /** - * Enter description here... + * Check is allow modify system configuration * + * @return bool */ protected function _isAllowed() { @@ -276,20 +274,28 @@ protected function _isSectionAllowed($section) try { $session = Mage::getSingleton('admin/session'); $resourceLookup = "admin/system/config/{$section}"; - $resourceId = $session->getData('acl')->get($resourceLookup)->getResourceId(); - if (!$session->isAllowed($resourceId)) { - throw new Exception(''); + if ($session->getData('acl') instanceof Mage_Admin_Model_Acl) { + $resourceId = $session->getData('acl')->get($resourceLookup)->getResourceId(); + if (!$session->isAllowed($resourceId)) { + throw new Exception(''); + } + return true; } - return true; + } + catch (Zend_Acl_Exception $e) { + $this->norouteAction(); + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + return false; } catch (Exception $e) { - $this->_forward('denied'); + $this->deniedAction(); + $this->setFlag('', self::FLAG_NO_DISPATCH, true); return false; } } /** - * saving state of config field sets + * Save state of configuration field sets * * @param array $configState * @return bool diff --git a/app/code/core/Mage/Adminhtml/controllers/System/Convert/GuiController.php b/app/code/core/Mage/Adminhtml/controllers/System/Convert/GuiController.php index 3f7b9636..967c9866 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/Convert/GuiController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/Convert/GuiController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -130,7 +130,6 @@ public function downloadAction() } $this->_initProfile(); $profile = Mage::registry('current_convert_profile'); - } protected function _isAllowed() diff --git a/app/code/core/Mage/Adminhtml/controllers/System/Convert/ProfileController.php b/app/code/core/Mage/Adminhtml/controllers/System/Convert/ProfileController.php index b5917128..84d852ad 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/Convert/ProfileController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/Convert/ProfileController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,7 +33,6 @@ */ class Mage_Adminhtml_System_Convert_ProfileController extends Mage_Adminhtml_Controller_Action { - protected function _initProfile($idFieldName = 'id') { $this->_title($this->__('System')) @@ -46,7 +45,8 @@ protected function _initProfile($idFieldName = 'id') if ($profileId) { $profile->load($profileId); if (!$profile->getId()) { - Mage::getSingleton('adminhtml/session')->addError('The profile you are trying to save no longer exists'); + Mage::getSingleton('adminhtml/session')->addError( + $this->__('The profile you are trying to save no longer exists')); $this->_redirect('*/*'); return false; } @@ -87,15 +87,21 @@ public function indexAction() /** * Add breadcrumb item */ - $this->_addBreadcrumb(Mage::helper('adminhtml')->__('Import/Export'), Mage::helper('adminhtml')->__('Import/Export Advanced')); - $this->_addBreadcrumb(Mage::helper('adminhtml')->__('Advanced Profiles'), Mage::helper('adminhtml')->__('Advanced Profiles')); + $this->_addBreadcrumb( + Mage::helper('adminhtml')->__('Import/Export'), + Mage::helper('adminhtml')->__('Import/Export Advanced')); + $this->_addBreadcrumb( + Mage::helper('adminhtml')->__('Advanced Profiles'), + Mage::helper('adminhtml')->__('Advanced Profiles')); $this->renderLayout(); } public function gridAction() { - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/system_convert_profile_grid')->toHtml()); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/system_convert_profile_grid')->toHtml() + ); } /** @@ -149,9 +155,9 @@ public function deleteAction() if ($profile->getId()) { try { $profile->delete(); - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('The profile has been deleted.')); - } - catch (Exception $e){ + Mage::getSingleton('adminhtml/session')->addSuccess( + Mage::helper('adminhtml')->__('The profile has been deleted.')); + } catch (Exception $e){ Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); } } @@ -165,7 +171,7 @@ public function saveAction() { if ($data = $this->getRequest()->getPost()) { if (!$this->_initProfile('profile_id')) { - return ; + return; } $profile = Mage::registry('current_convert_profile'); @@ -177,22 +183,23 @@ public function saveAction() try { $profile->save(); - Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('adminhtml')->__('The profile has been saved.')); - } - catch (Exception $e){ + Mage::getSingleton('adminhtml/session')->addSuccess( + Mage::helper('adminhtml')->__('The profile has been saved.')); + } catch (Exception $e){ Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); Mage::getSingleton('adminhtml/session')->setConvertProfileData($data); - $this->getResponse()->setRedirect($this->getUrl('*/*/edit', array('id'=>$profile->getId()))); + $this->getResponse()->setRedirect($this->getUrl('*/*/edit', array('id' => $profile->getId()))); return; } if ($this->getRequest()->getParam('continue')) { - $this->_redirect('*/*/edit', array('id'=>$profile->getId())); + $this->_redirect('*/*/edit', array('id' => $profile->getId())); } else { $this->_redirect('*/*'); } - } - else { - Mage::getSingleton('adminhtml/session')->addError($this->__('Invalid POST data (please check post_max_size and upload_max_filesize settings in your php.ini file).')); + } else { + Mage::getSingleton('adminhtml/session')->addError( + $this->__('Invalid POST data (please check post_max_size and upload_max_filesize settings in your php.ini file).') + ); $this->_redirect('*/*'); } } @@ -200,39 +207,27 @@ public function saveAction() public function runAction() { $this->_initProfile(); - #$this->loadLayout(); - - #$this->_setActiveMenu('system/convert'); - - #$this->_addContent( - # $this->getLayout()->createBlock('adminhtml/system_convert_profile_run') - #); - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/system_convert_profile_run')->toHtml()); - $this->getResponse()->sendResponse(); - - #$this->renderLayout(); + $this->loadLayout(); + $this->renderLayout(); } public function batchRunAction() { if ($this->getRequest()->isPost()) { - $batchId = $this->getRequest()->getPost('batch_id',0); + $batchId = $this->getRequest()->getPost('batch_id', 0); $rowIds = $this->getRequest()->getPost('rows'); - $batchModel = Mage::getModel('dataflow/batch')->load($batchId); /* @var $batchModel Mage_Dataflow_Model_Batch */ + $batchModel = Mage::getModel('dataflow/batch')->load($batchId); if (!$batchModel->getId()) { - //exit - return ; + return; } if (!is_array($rowIds) || count($rowIds) < 1) { - //exit - return ; + return; } if (!$batchModel->getAdapter()) { - //exit - return ; + return; } $batchImportModel = $batchModel->getBatchImportModel(); @@ -243,7 +238,6 @@ public function batchRunAction() $errors = array(); $saved = 0; - foreach ($rowIds as $importId) { $batchImportModel->load($importId); if (!$batchImportModel->getId()) { @@ -254,14 +248,27 @@ public function batchRunAction() try { $importData = $batchImportModel->getBatchData(); $adapter->saveRow($importData); - } - catch (Exception $e) { + } catch (Exception $e) { $errors[] = $e->getMessage(); continue; } $saved ++; } + if (method_exists($adapter, 'getEventPrefix')) { + /** + * Event for process rules relations after products import + */ + Mage::dispatchEvent($adapter->getEventPrefix() . '_finish_before', array( + 'adapter' => $adapter + )); + + /** + * Clear affected ids for adapter possible reuse + */ + $adapter->clearAffectedEntityIds(); + } + $result = array( 'savedRows' => $saved, 'errors' => $errors @@ -272,7 +279,8 @@ public function batchRunAction() public function batchFinishAction() { - if ($batchId = $this->getRequest()->getParam('id')) { + $batchId = $this->getRequest()->getParam('id'); + if ($batchId) { $batchModel = Mage::getModel('dataflow/batch')->load($batchId); /* @var $batchModel Mage_Dataflow_Model_Batch */ @@ -280,11 +288,9 @@ public function batchFinishAction() $result = array(); try { $batchModel->beforeFinish(); - } - catch (Mage_Core_Exception $e) { + } catch (Mage_Core_Exception $e) { $result['error'] = $e->getMessage(); - } - catch (Exception $e) { + } catch (Exception $e) { $result['error'] = Mage::helper('adminhtml')->__('An error occurred while finishing process. Please refresh the cache'); } $batchModel->delete(); @@ -299,26 +305,13 @@ public function batchFinishAction() */ public function historyAction() { $this->_initProfile(); - $this->getResponse()->setBody($this->getLayout()->createBlock('adminhtml/system_convert_profile_edit_tab_history')->toHtml()); + $this->getResponse()->setBody( + $this->getLayout()->createBlock('adminhtml/system_convert_profile_edit_tab_history')->toHtml() + ); } protected function _isAllowed() { -// switch ($this->getRequest()->getActionName()) { -// case 'index': -// $aclResource = 'admin/system/convert/profiles'; -// break; -// case 'grid': -// $aclResource = 'admin/system/convert/profiles'; -// break; -// case 'run': -// $aclResource = 'admin/system/convert/profiles/run'; -// break; -// default: -// $aclResource = 'admin/system/convert/profiles/edit'; -// break; -// } - return Mage::getSingleton('admin/session')->isAllowed('admin/system/convert/profiles'); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/System/CurrencyController.php b/app/code/core/Mage/Adminhtml/controllers/System/CurrencyController.php index 12ed01ac..8d65bc13 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/CurrencyController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/CurrencyController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -70,7 +70,9 @@ public function fetchRatesAction() throw new Exception(Mage::helper('adminhtml')->__('Invalid Import Service Specified')); } try { - $importModel = Mage::getModel(Mage::getConfig()->getNode('global/currency/import/services/' . $service . '/model')->asArray()); + $importModel = Mage::getModel( + Mage::getConfig()->getNode('global/currency/import/services/' . $service . '/model')->asArray() + ); } catch (Exception $e) { Mage::throwException(Mage::helper('adminhtml')->__('Unable to initialize import model')); } @@ -120,6 +122,6 @@ public function saveRatesAction() protected function _isAllowed() { - return Mage::getSingleton('admin/session')->isAllowed('system/currency'); + return Mage::getSingleton('admin/session')->isAllowed('system/currency/rates'); } } diff --git a/app/code/core/Mage/Adminhtml/controllers/System/DesignController.php b/app/code/core/Mage/Adminhtml/controllers/System/DesignController.php index 3dca58ec..41e2e8fc 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/DesignController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/DesignController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php b/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php index eaae2e75..1310399f 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/Email/TemplateController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/System/StoreController.php b/app/code/core/Mage/Adminhtml/controllers/System/StoreController.php index 5f896a90..e00fcd89 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/StoreController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/StoreController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -172,6 +172,7 @@ public function saveAction() try { switch ($postData['store_type']) { case 'website': + $postData['website']['name'] = $this->_getHelper()->removeTags($postData['website']['name']); $websiteModel = Mage::getModel('core/website'); if ($postData['website']['website_id']) { $websiteModel->load($postData['website']['website_id']); @@ -186,6 +187,7 @@ public function saveAction() break; case 'group': + $postData['group']['name'] = $this->_getHelper()->removeTags($postData['group']['name']); $groupModel = Mage::getModel('core/store_group'); if ($postData['group']['group_id']) { $groupModel->load($postData['group']['group_id']); @@ -205,6 +207,7 @@ public function saveAction() case 'store': $eventName = 'store_edit'; $storeModel = Mage::getModel('core/store'); + $postData['store']['name'] = $this->_getHelper()->removeTags($postData['store']['name']); if ($postData['store']['store_id']) { $storeModel->load($postData['store']['store_id']); } diff --git a/app/code/core/Mage/Adminhtml/controllers/System/VariableController.php b/app/code/core/Mage/Adminhtml/controllers/System/VariableController.php index 499fbf29..dde9268d 100644 --- a/app/code/core/Mage/Adminhtml/controllers/System/VariableController.php +++ b/app/code/core/Mage/Adminhtml/controllers/System/VariableController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -56,7 +56,7 @@ protected function _initVariable() $this->_title($this->__('System'))->_title($this->__('Custom Variables')); $variableId = $this->getRequest()->getParam('variable_id', null); - $storeId = $this->getRequest()->getParam('store', 0); + $storeId = (int)$this->getRequest()->getParam('store', 0); /* @var $emailVariable Mage_Core_Model_Variable */ $variable = Mage::getModel('core/variable'); if ($variableId) { @@ -101,7 +101,9 @@ public function editAction() $this->_initLayout() ->_addContent($this->getLayout()->createBlock('adminhtml/system_variable_edit')) - ->_addJs($this->getLayout()->createBlock('core/template', '', array('template' => 'system/variable/js.phtml'))) + ->_addJs($this->getLayout()->createBlock('core/template', '', array( + 'template' => 'system/variable/js.phtml' + ))) ->renderLayout(); } diff --git a/app/code/core/Mage/Adminhtml/controllers/SystemController.php b/app/code/core/Mage/Adminhtml/controllers/SystemController.php index 50af1d39..7a4e11f0 100644 --- a/app/code/core/Mage/Adminhtml/controllers/SystemController.php +++ b/app/code/core/Mage/Adminhtml/controllers/SystemController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Adminhtml/controllers/UrlrewriteController.php b/app/code/core/Mage/Adminhtml/controllers/UrlrewriteController.php index 32e5e1e2..7e711d38 100644 --- a/app/code/core/Mage/Adminhtml/controllers/UrlrewriteController.php +++ b/app/code/core/Mage/Adminhtml/controllers/UrlrewriteController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -121,15 +121,21 @@ public function saveAction() $this->_initRegistry(); if ($data = $this->getRequest()->getPost()) { + $session = Mage::getSingleton('adminhtml/session'); try { // set basic urlrewrite data $model = Mage::registry('current_urlrewrite'); + // Validate request path + $requestPath = $this->getRequest()->getParam('request_path'); + Mage::helper('core/url_rewrite')->validateRequestPath($requestPath); + + // Proceed and save request $model->setIdPath($this->getRequest()->getParam('id_path')) ->setTargetPath($this->getRequest()->getParam('target_path')) ->setOptions($this->getRequest()->getParam('options')) ->setDescription($this->getRequest()->getParam('description')) - ->setRequestPath($this->getRequest()->getParam('request_path')); + ->setRequestPath($requestPath); if (!$model->getId()) { $model->setIsSystem(0); @@ -156,6 +162,10 @@ public function saveAction() if (in_array($model->getOptions(), array('R', 'RP'))) { $rewrite = Mage::getResourceModel('catalog/url') ->getRewriteByIdPath($idPath, $model->getStoreId()); + if (!$rewrite) { + $exceptionTxt = 'Chosen product does not associated with the chosen store or category.'; + Mage::throwException($exceptionTxt); + } if($rewrite->getId() && $rewrite->getId() != $model->getId()) { $model->setIdPath($idPath); $model->setTargetPath($rewrite->getRequestPath()); @@ -171,17 +181,15 @@ public function saveAction() // save and redirect $model->save(); - Mage::getSingleton('adminhtml/session')->addSuccess( - Mage::helper('adminhtml')->__('The URL Rewrite has been saved.') - ); + $session->addSuccess(Mage::helper('adminhtml')->__('The URL Rewrite has been saved.')); $this->_redirect('*/*/'); return; - } - catch (Exception $e) { - Mage::getSingleton('adminhtml/session') - ->addError($e->getMessage()) - ->setUrlrewriteData($data) - ; + } catch (Mage_Core_Exception $e) { + $session->addError($e->getMessage()) + ->setUrlrewriteData($data); + } catch (Exception $e) { + $session->addException($e, Mage::helper('adminhtml')->__('An error occurred while saving URL Rewrite.')) + ->setUrlrewriteData($data); // return intentionally omitted } } @@ -202,10 +210,10 @@ public function deleteAction() Mage::getSingleton('adminhtml/session')->addSuccess( Mage::helper('adminhtml')->__('The URL Rewrite has been deleted.') ); - } - catch (Exception $e) { - Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); - $this->_redirectReferer(); + } catch (Exception $e) { + Mage::getSingleton('adminhtml/session') + ->addException($e, Mage::helper('adminhtml')->__('An error occurred while deleting URL Rewrite.')); + $this->_redirect('*/*/edit/', array('id'=>Mage::registry('current_urlrewrite')->getId())); return; } } diff --git a/app/code/core/Mage/Adminhtml/etc/adminhtml.xml b/app/code/core/Mage/Adminhtml/etc/adminhtml.xml index c700705b..8bb04402 100644 --- a/app/code/core/Mage/Adminhtml/etc/adminhtml.xml +++ b/app/code/core/Mage/Adminhtml/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -56,12 +56,14 @@ 40 - Profiles + Dataflow - Profiles adminhtml/system_convert_gui + 30 - Advanced Profiles + Dataflow - Advanced Profiles adminhtml/system_convert_profile + 40 @@ -94,20 +96,6 @@ - - Magento Connect - 80 - - - Magento Connect Manager - adminhtml/extensions_local - - - Package Extensions - adminhtml/extensions_custom - - - Cache Management adminhtml/cache @@ -233,10 +221,10 @@ Import/Export - Profiles + Dataflow - Profiles - Advanced Profiles + Dataflow - Advanced Profiles diff --git a/app/code/core/Mage/Adminhtml/etc/config.xml b/app/code/core/Mage/Adminhtml/etc/config.xml index f523b875..8b8af78a 100644 --- a/app/code/core/Mage/Adminhtml/etc/config.xml +++ b/app/code/core/Mage/Adminhtml/etc/config.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -31,16 +31,16 @@ 0.7.1 - Mage_Adminhtml_Model - adminhtml_mysql4 + adminhtml_resource - - Mage_Adminhtml_Model_Mysql4 - + + Mage_Adminhtml_Model_Resource + adminhtml_mysql4 + @@ -50,13 +50,12 @@ - @@ -70,9 +69,16 @@ + + + + adminhtml/observer + clearCacheConfigurationFilesAccessLevelVerification + + + - @@ -84,7 +90,6 @@ - @@ -103,9 +108,16 @@ + + + + adminhtml/system_config_backend_admin_observer + afterCustomUrlChanged + + + - @@ -154,6 +165,8 @@ + + @@ -161,15 +174,45 @@ + + + email + + system_emails_forgot_email_template general + + 1 + + + + + + + /*/sitemap.xml + + + + + + price + media_image + gallery + + + + + + + 1 + + - default @@ -184,7 +227,9 @@ - true + + true + admin/index/noRoute diff --git a/app/code/core/Mage/Adminhtml/etc/jstranslator.xml b/app/code/core/Mage/Adminhtml/etc/jstranslator.xml new file mode 100644 index 00000000..5b5c3b35 --- /dev/null +++ b/app/code/core/Mage/Adminhtml/etc/jstranslator.xml @@ -0,0 +1,65 @@ + + + + + + Complete + + + Upload Security Error + + + Upload HTTP Error + + + Upload I/O Error + + + SSL Error: Invalid or self-signed certificate + + + Tb + + + Gb + + + Mb + + + Kb + + + b + + + + Add Products + + + diff --git a/app/code/core/Mage/Api/Controller/Action.php b/app/code/core/Mage/Api/Controller/Action.php index 7779c1c8..08d6f772 100644 --- a/app/code/core/Mage/Api/Controller/Action.php +++ b/app/code/core/Mage/Api/Controller/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Exception.php b/app/code/core/Mage/Api/Exception.php index fccafd8e..ebd3fd8d 100644 --- a/app/code/core/Mage/Api/Exception.php +++ b/app/code/core/Mage/Api/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Helper/Data.php b/app/code/core/Mage/Api/Helper/Data.php index f9bef604..2307dcc3 100644 --- a/app/code/core/Mage/Api/Helper/Data.php +++ b/app/code/core/Mage/Api/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,4 +33,221 @@ */ class Mage_Api_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_PATH_API_WSI = 'api/config/compliance_wsi'; + + /** + * Method to find adapter code depending on WS-I compatibility setting + * + * @return string + */ + public function getV2AdapterCode() + { + return $this->isComplianceWSI() ? 'soap_wsi' : 'soap_v2'; + } + + /** + * @return boolean + */ + public function isComplianceWSI() + { + return Mage::getStoreConfig(self::XML_PATH_API_WSI); + } + + /** + * Go thru a WSI args array and turns it to correct state. + * + * @param Object $obj - Link to Object + * @return Object + */ + public function wsiArrayUnpacker(&$obj) + { + if (is_object($obj)) { + + $modifiedKeys = $this->clearWsiFootprints($obj); + + foreach ($obj as $key => $value) { + if (is_object($value)) { + $this->wsiArrayUnpacker($value); + } + if (is_array($value)) { + foreach ($value as &$val) { + if (is_object($val)) { + $this->wsiArrayUnpacker($val); + } + } + } + } + + foreach ($modifiedKeys as $arrKey) { + $this->associativeArrayUnpack($obj->$arrKey); + } + } + } + + /** + * Go thru an object parameters and unpak associative object to array. + * + * @param Object $obj - Link to Object + * @return Object + */ + public function v2AssociativeArrayUnpacker(&$obj) + { + if (is_object($obj) + && property_exists($obj, 'key') + && property_exists($obj, 'value') + ) { + if (count(array_keys(get_object_vars($obj))) == 2) { + $obj = array($obj->key => $obj->value); + return true; + } + } elseif (is_array($obj)) { + $arr = array(); + $needReplacement = true; + foreach ($obj as $key => &$value) { + $isAssoc = $this->v2AssociativeArrayUnpacker($value); + if ($isAssoc) { + foreach ($value as $aKey => $aVal) { + $arr[$aKey] = $aVal; + } + } else { + $needReplacement = false; + } + } + if ($needReplacement) { + $obj = $arr; + } + } elseif (is_object($obj)) { + $objectKeys = array_keys(get_object_vars($obj)); + + foreach ($objectKeys as $key) { + $this->v2AssociativeArrayUnpacker($obj->$key); + } + } + return false; + } + + /** + * Go thru mixed and turns it to a correct look. + * + * @param Mixed $mixed A link to variable that may contain associative array. + */ + public function associativeArrayUnpack(&$mixed) + { + if (is_array($mixed)) { + $tmpArr = array(); + foreach ($mixed as $key => $value) { + if (is_object($value)) { + $value = get_object_vars($value); + if (count($value) == 2 && isset($value['key']) && isset($value['value'])) { + $tmpArr[$value['key']] = $value['value']; + } + } + } + if (count($tmpArr)) { + $mixed = $tmpArr; + } + } + + if (is_object($mixed)) { + $numOfVals = count(get_object_vars($mixed)); + if ($numOfVals == 2 && isset($mixed->key) && isset($mixed->value)) { + $mixed = get_object_vars($mixed); + /* + * Processing an associative arrays. + * $mixed->key = '2'; $mixed->value = '3'; turns to array(2 => '3'); + */ + $mixed = array($mixed['key'] => $mixed['value']); + } + } + } + + /** + * Corrects data representation. + * + * @param Object $obj - Link to Object + * @return Object + */ + public function clearWsiFootprints(&$obj) + { + $modifiedKeys = array(); + + $objectKeys = array_keys(get_object_vars($obj)); + + foreach ($objectKeys as $key) { + if (is_object($obj->$key) && isset($obj->$key->complexObjectArray)) { + if (is_array($obj->$key->complexObjectArray)) { + $obj->$key = $obj->$key->complexObjectArray; + } else { // for one element array + $obj->$key = array($obj->$key->complexObjectArray); + } + $modifiedKeys[] = $key; + } + } + return $modifiedKeys; + } + + /** + * For the WSI, generates an response object. + * + * @param mixed $mixed - Link to Object + * @return mixed + */ + public function wsiArrayPacker($mixed) + { + if (is_array($mixed)) { + $arrKeys = array_keys($mixed); + $isDigit = false; + $isString = false; + foreach ($arrKeys as $key) { + if (is_int($key)) { + $isDigit = true; + break; + } + } + if ($isDigit) { + $mixed = $this->packArrayToObjec($mixed); + } else { + $mixed = (object) $mixed; + } + } + if (is_object($mixed) && isset($mixed->complexObjectArray)) { + foreach ($mixed->complexObjectArray as $k => $v) { + $mixed->complexObjectArray[$k] = $this->wsiArrayPacker($v); + } + } + return $mixed; + } + + /** + * For response to the WSI, generates an object from array. + * + * @param Array $arr - Link to Object + * @return Object + */ + public function packArrayToObjec(Array $arr) + { + $obj = new stdClass(); + $obj->complexObjectArray = $arr; + return $obj; + } + + /** + * Convert objects and arrays to array recursively + * + * @param array|object $data + * @return void + */ + public function toArray(&$data) + { + if (is_object($data)) { + $data = get_object_vars($data); + } + if (is_array($data)) { + foreach ($data as &$value) { + if (is_array($value) or is_object($value)) { + $this->toArray($value); + } + } + } + } } // Class Mage_Api_Helper_Data End diff --git a/app/code/core/Mage/Api/Model/Acl.php b/app/code/core/Mage/Api/Model/Acl.php index 30973bc7..3b1553f3 100644 --- a/app/code/core/Mage/Api/Model/Acl.php +++ b/app/code/core/Mage/Api/Model/Acl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -46,6 +46,17 @@ class Mage_Api_Model_Acl extends Zend_Acl */ const ROLE_TYPE_USER = 'U'; + /** + * User types for store access + * G - Guest customer (anonymous) + * C - Authenticated customer + * A - Authenticated admin user + * + */ + const USER_TYPE_GUEST = 'G'; + const USER_TYPE_CUSTOMER = 'C'; + const USER_TYPE_ADMIN = 'A'; + /** * Permission level to deny access * diff --git a/app/code/core/Mage/Api/Model/Acl/Assert/Ip.php b/app/code/core/Mage/Api/Model/Acl/Assert/Ip.php index 11dbba3d..9fdd3591 100644 --- a/app/code/core/Mage/Api/Model/Acl/Assert/Ip.php +++ b/app/code/core/Mage/Api/Model/Acl/Assert/Ip.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Assert/Time.php b/app/code/core/Mage/Api/Model/Acl/Assert/Time.php index 7da10942..b72f281d 100644 --- a/app/code/core/Mage/Api/Model/Acl/Assert/Time.php +++ b/app/code/core/Mage/Api/Model/Acl/Assert/Time.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Resource.php b/app/code/core/Mage/Api/Model/Acl/Resource.php index 9b276849..1157e6da 100644 --- a/app/code/core/Mage/Api/Model/Acl/Resource.php +++ b/app/code/core/Mage/Api/Model/Acl/Resource.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Role.php b/app/code/core/Mage/Api/Model/Acl/Role.php index e87eebd1..e2e98de9 100644 --- a/app/code/core/Mage/Api/Model/Acl/Role.php +++ b/app/code/core/Mage/Api/Model/Acl/Role.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,23 @@ /** * User acl role * - * @category Mage - * @package Mage_Api + * @method Mage_Api_Model_Resource_Role _getResource() + * @method Mage_Api_Model_Resource_Role getResource() + * @method int getParentId() + * @method Mage_Api_Model_Acl_Role setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Api_Model_Acl_Role setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Api_Model_Acl_Role setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Api_Model_Acl_Role setRoleType(string $value) + * @method int getUserId() + * @method Mage_Api_Model_Acl_Role setUserId(int $value) + * @method string getRoleName() + * @method Mage_Api_Model_Acl_Role setRoleName(string $value) + * + * @category Mage + * @package Mage_Api * @author Magento Core Team */ class Mage_Api_Model_Acl_Role extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Api/Model/Acl/Role/Generic.php b/app/code/core/Mage/Api/Model/Acl/Role/Generic.php index 1a94a402..4323e48c 100644 --- a/app/code/core/Mage/Api/Model/Acl/Role/Generic.php +++ b/app/code/core/Mage/Api/Model/Acl/Role/Generic.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Role/Group.php b/app/code/core/Mage/Api/Model/Acl/Role/Group.php index 268f3846..fc6ca50b 100644 --- a/app/code/core/Mage/Api/Model/Acl/Role/Group.php +++ b/app/code/core/Mage/Api/Model/Acl/Role/Group.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Role/Registry.php b/app/code/core/Mage/Api/Model/Acl/Role/Registry.php index e492fd60..0a42cc0e 100644 --- a/app/code/core/Mage/Api/Model/Acl/Role/Registry.php +++ b/app/code/core/Mage/Api/Model/Acl/Role/Registry.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Acl/Role/User.php b/app/code/core/Mage/Api/Model/Acl/Role/User.php index b8376f47..bade58da 100644 --- a/app/code/core/Mage/Api/Model/Acl/Role/User.php +++ b/app/code/core/Mage/Api/Model/Acl/Role/User.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Config.php b/app/code/core/Mage/Api/Model/Config.php index 06cd4fe8..31ff058b 100644 --- a/app/code/core/Mage/Api/Model/Config.php +++ b/app/code/core/Mage/Api/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -71,6 +71,24 @@ protected function _construct() return $this; } + /** + * Retrieve adapter aliases from config. + * + * @return array + */ + public function getAdapterAliases() + { + $aliases = array(); + + foreach ($this->getNode('adapter_aliases')->children() as $alias => $adapter) { + $aliases[$alias] = array( + (string) $adapter->suggest_class, // model class name + (string) $adapter->suggest_method // model method name + ); + } + return $aliases; + } + /** * Retrieve all adapters * diff --git a/app/code/core/Mage/Api/Model/Mysql4/Acl.php b/app/code/core/Mage/Api/Model/Mysql4/Acl.php index d4564cc9..1d8d0229 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Acl.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Acl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,133 +28,10 @@ /** * Resource model for admin ACL * - * @category Mage - * @package Mage_Api - * @author Magento Core Team + * @category Mage + * @package Mage_Api + * @author Magento Core Team */ -class Mage_Api_Model_Mysql4_Acl extends Mage_Core_Model_Mysql4_Abstract +class Mage_Api_Model_Mysql4_Acl extends Mage_Api_Model_Resource_Acl { - - /** - * Initialize resource connections - * - */ - protected function _construct() - { - $this->_init('api/role', 'role_id'); - } - - /** - * Load ACL for the user - * - * @param integer $userId - * @return Mage_Api_Model_Acl - */ - function loadAcl() - { - $acl = Mage::getModel('api/acl'); - - Mage::getSingleton('api/config')->loadAclResources($acl); - - $roleTable = Mage::getSingleton('core/resource')->getTableName('api/role'); - $rolesArr = $this->_getReadAdapter()->fetchAll( - $this->_getReadAdapter()->select() - ->from($this->getTable('role')) - ->order('tree_level') - ); - $this->loadRoles($acl, $rolesArr); - - $rulesArr = $this->_getReadAdapter()->fetchAll( - $this->_getReadAdapter()->select() - ->from(array('r'=>$this->getTable('rule'))) - ->joinLeft( - array('a'=>$this->getTable('assert')), - 'a.assert_id=r.assert_id', - array('assert_type', 'assert_data') - )); - $this->loadRules($acl, $rulesArr); - return $acl; - } - - /** - * Load roles - * - * @param Mage_Api_Model_Acl $acl - * @param array $rolesArr - * @return Mage_Api_Model_Mysql4_Acl - */ - function loadRoles(Mage_Api_Model_Acl $acl, array $rolesArr) - { - foreach ($rolesArr as $role) { - $parent = $role['parent_id']>0 ? Mage_Api_Model_Acl::ROLE_TYPE_GROUP.$role['parent_id'] : null; - switch ($role['role_type']) { - case Mage_Api_Model_Acl::ROLE_TYPE_GROUP: - $roleId = $role['role_type'].$role['role_id']; - $acl->addRole(Mage::getModel('api/acl_role_group', $roleId), $parent); - break; - - case Mage_Api_Model_Acl::ROLE_TYPE_USER: - $roleId = $role['role_type'].$role['user_id']; - if (!$acl->hasRole($roleId)) { - $acl->addRole(Mage::getModel('api/acl_role_user', $roleId), $parent); - } else { - $acl->addRoleParent($roleId, $parent); - } - break; - } - } - - return $this; - } - - /** - * Load rules - * - * @param Mage_Api_Model_Acl $acl - * @param array $rulesArr - * @return Mage_Api_Model_Mysql4_Acl - */ - function loadRules(Mage_Api_Model_Acl $acl, array $rulesArr) - { - foreach ($rulesArr as $rule) { - $role = $rule['role_type'].$rule['role_id']; - $resource = $rule['resource_id']; - $privileges = !empty($rule['privileges']) ? explode(',', $rule['privileges']) : null; - - $assert = null; - if (0!=$rule['assert_id']) { - $assertClass = Mage::getSingleton('api/config')->getAclAssert($rule['assert_type'])->getClassName(); - $assert = new $assertClass(unserialize($rule['assert_data'])); - } - try { - if ( $rule['permission'] == 'allow' ) { - $acl->allow($role, $resource, $privileges, $assert); - } else if ( $rule['permission'] == 'deny' ) { - $acl->deny($role, $resource, $privileges, $assert); - } - } catch (Exception $e) { - //$m = $e->getMessage(); - //if ( eregi("^Resource '(.*)' not found", $m) ) { - // Deleting non existent resource rule from rules table - //$cond = $this->_write->quoteInto('resource_id = ?', $resource); - //$this->_write->delete(Mage::getSingleton('core/resource')->getTableName('admin/rule'), $cond); - //} else { - //TODO: We need to log such exceptions to somewhere like a system/errors.log - //} - } - /* - switch ($rule['permission']) { - case Mage_Api_Model_Acl::RULE_PERM_ALLOW: - $acl->allow($role, $resource, $privileges, $assert); - break; - - case Mage_Api_Model_Acl::RULE_PERM_DENY: - $acl->deny($role, $resource, $privileges, $assert); - break; - } - */ - } - return $this; - } - } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Acl/Role.php b/app/code/core/Mage/Api/Model/Mysql4/Acl/Role.php index a0e410e6..50aa5a37 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Acl/Role.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Acl/Role.php @@ -20,29 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * ACL role resource * - * @category Mage - * @package Mage_Api + * @category Mage + * @package Mage_Api * @author Magento Core Team */ -class Mage_Api_Model_Mysql4_Acl_Role extends Mage_Core_Model_Mysql4_Abstract +class Mage_Api_Model_Mysql4_Acl_Role extends Mage_Api_Model_Resource_Acl_Role { - protected function _construct() - { - $this->_init('api/role', 'role_id'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (!$role->getId()) { - $this->setCreated(Mage::getSingleton('core/date')->gmtDate()); - } - return $this; - } } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Acl/Role/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Acl/Role/Collection.php index bc21a2e6..d5262cb2 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Acl/Role/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Acl/Role/Collection.php @@ -20,21 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Roles collection * - * @category Mage - * @package Mage_Api + * @category Mage + * @package Mage_Api * @author Magento Core Team */ -class Mage_Api_Model_Mysql4_Acl_Role_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Api_Model_Mysql4_Acl_Role_Collection extends Mage_Api_Model_Resource_Acl_Role_Collection { - protected function _construct() - { - $this->_init('api/role'); - } } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Permissions/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Permissions/Collection.php index db7fbdbc..05144cb4 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Permissions/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Permissions/Collection.php @@ -20,14 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Permissions_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract + +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Permissions_Collection extends Mage_Api_Model_Resource_Permissions_Collection { - protected function _construct() - { - $this->_init('api/rules'); - } } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Role.php b/app/code/core/Mage/Api/Model/Mysql4/Role.php index f582cf11..c707b68e 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Role.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Role.php @@ -20,31 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Role extends Mage_Core_Model_Mysql4_Abstract -{ - protected function _construct() - { - $this->_init('api/role', 'role_id'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if ( !$object->getId() ) { - $object->setCreated(now()); - } - $object->setModified(now()); - return $this; - } - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - if (!intval($value) && is_string($value)) { - $field = 'role_id'; - } - return parent::load($object, $value, $field); - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Role extends Mage_Api_Model_Resource_Role +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Role/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Role/Collection.php index 3a608bc9..f59a5d82 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Role/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Role/Collection.php @@ -20,33 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Role_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('api/role'); - } - - /** - * Enter description here... - * - * @param int $userId - * @return Mage_Api_Model_Mysql4_Role_Collection - */ - public function setUserFilter($userId) - { - $this->addFieldToFilter('user_id', $userId); - $this->addFieldToFilter('role_type', 'G'); - return $this; - } - public function setRolesFilter() - { - $this->addFieldToFilter('role_type', 'G'); - return $this; - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Role_Collection extends Mage_Api_Model_Resource_Role_Collection +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Roles.php b/app/code/core/Mage/Api/Model/Mysql4/Roles.php index 9160d96b..bffa0477 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Roles.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Roles.php @@ -20,76 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Roles extends Mage_Core_Model_Mysql4_Abstract -{ - protected $_usersTable; - protected $_ruleTable; - - protected function _construct() { - $this->_init('api/role', 'role_id'); - - $this->_usersTable = $this->getTable('api/user'); - $this->_ruleTable = $this->getTable('api/rule'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $role) - { - if ($role->getId() == '') { - if ($role->getIdFieldName()) { - $role->unsetData($role->getIdFieldName()); - } else { - $role->unsetData('id'); - } - } - - if ($role->getPid() > 0) { - $row = $this->load($role->getPid()); - } else { - $row = array('tree_level' => 0); - } - $role->setTreeLevel($row['tree_level'] + 1); - $role->setRoleName($role->getName()); - return $this; - } - - protected function _afterSave(Mage_Core_Model_Abstract $role) - { - $this->_updateRoleUsersAcl($role); - Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG); - return $this; - } - - protected function _afterDelete(Mage_Core_Model_Abstract $role) - { - $this->_getWriteAdapter()->delete($this->getMainTable(), "parent_id={$role->getId()}"); - $this->_getWriteAdapter()->delete($this->_ruleTable, "role_id={$role->getId()}"); - return $this; - } - public function getRoleUsers(Mage_Api_Model_Roles $role) - { - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getMainTable(), array('user_id'))->where("(parent_id = '{$role->getId()}' AND role_type = 'U') AND user_id > 0"); - return $read->fetchCol($select); - } - - private function _updateRoleUsersAcl(Mage_Api_Model_Roles $role) - { - $write = $this->_getWriteAdapter(); - $users = $this->getRoleUsers($role); - $rowsCount = 0; - if ( sizeof($users) > 0 ) { - $inStatement = implode(", ", $users); - $rowsCount = $write->update($this->_usersTable, array('reload_acl_flag' => 1), "user_id IN({$inStatement})"); - } - if ($rowsCount > 0) { - return true; - } else { - return false; - } - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Roles extends Mage_Api_Model_Resource_Roles +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Roles/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Roles/Collection.php index be1c5eec..f8e5affa 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Roles/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Roles/Collection.php @@ -20,24 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Roles_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('api/role'); - } - - protected function _initSelect(){ - parent::_initSelect(); - $this->getSelect()->where("main_table.role_type='G'"); - } - public function toOptionArray() - { - return $this->_toOptionArray('role_id', 'role_name'); - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Roles_Collection extends Mage_Api_Model_Resource_Roles_Collection +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Roles/User/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Roles/User/Collection.php index 037577d7..4d243aea 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Roles/User/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Roles/User/Collection.php @@ -20,21 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Roles_User_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('api/user'); - } - - protected function _initSelect() - { - parent::_initSelect(); - $this->getSelect()->where("user_id > 0"); - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Roles_User_Collection extends Mage_Api_Model_Resource_Roles_User_Collection +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Rules.php b/app/code/core/Mage/Api/Model/Mysql4/Rules.php index 23d18cb5..6246f8b9 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Rules.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Rules.php @@ -20,48 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Rules extends Mage_Core_Model_Mysql4_Abstract -{ - protected function _construct() { - $this->_init('api/rule', 'rule_id'); - } - - public function saveRel(Mage_Api_Model_Rules $rule) { - $this->_getWriteAdapter()->beginTransaction(); - - try { - $roleId = $rule->getRoleId(); - $this->_getWriteAdapter()->delete($this->getMainTable(), "role_id = {$roleId}"); - $masterResources = Mage::getModel('api/roles')->getResourcesList2D(); - $masterAdmin = false; - if ( $postedResources = $rule->getResources() ) { - foreach ($masterResources as $index => $resName) { - if ( !$masterAdmin ) { - $permission = ( in_array($resName, $postedResources) )? 'allow' : 'deny'; - $this->_getWriteAdapter()->insert($this->getMainTable(), array( - 'role_type' => 'G', - 'resource_id' => trim($resName, '/'), - 'privileges' => '', # FIXME !!! - 'assert_id' => 0, - 'role_id' => $roleId, - 'permission' => $permission - )); - } - if ( $resName == 'all' && $permission == 'allow' ) { - $masterAdmin = true; - } - } - } - $this->_getWriteAdapter()->commit(); - } catch (Mage_Core_Exception $e) { - throw $e; - } catch (Exception $e){ - $this->_getWriteAdapter()->rollBack(); - } - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Rules extends Mage_Api_Model_Resource_Rules +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/Rules/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/Rules/Collection.php index 02256621..88acda2f 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/Rules/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/Rules/Collection.php @@ -20,27 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_Rules_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - protected function _construct() - { - $this->_init('api/rules'); - } - - public function getByRoles($id) - { - $this->getSelect()->where("role_id = ?", (int)$id); - return $this; - } - public function addSortByLength() - { - $this->getSelect()->columns(array('length' => 'LENGTH(resource_id)')) - ->order('length desc'); - return $this; - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_Rules_Collection extends Mage_Api_Model_Resource_Rules_Collection +{ } diff --git a/app/code/core/Mage/Api/Model/Mysql4/User.php b/app/code/core/Mage/Api/Model/Mysql4/User.php index 6ef94458..6e57f86c 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/User.php +++ b/app/code/core/Mage/Api/Model/Mysql4/User.php @@ -20,300 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * ACL user resource * - * @category Mage - * @package Mage_Api + * @category Mage + * @package Mage_Api * @author Magento Core Team */ -class Mage_Api_Model_Mysql4_User extends Mage_Core_Model_Mysql4_Abstract +class Mage_Api_Model_Mysql4_User extends Mage_Api_Model_Resource_User { - - protected function _construct() - { - $this->_init('api/user', 'user_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array( - array( - 'field' => 'email', - 'title' => Mage::helper('api')->__('Email') - ), - array( - 'field' => 'username', - 'title' => Mage::helper('api')->__('User Name') - ), - ); - return $this; - } - - /** - * Authenticate user by $username and $password - * - * @param string $username - * @param string $password - * @return boolean|Object - */ - public function recordLogin(Mage_Api_Model_User $user) - { - $data = array( - 'lognum' => $user->getLognum()+1, - ); - $condition = $this->_getWriteAdapter()->quoteInto('user_id=?', $user->getUserId()); - $this->_getWriteAdapter()->update($this->getTable('api/user'), $data, $condition); - return $this; - } - - public function recordSession(Mage_Api_Model_User $user) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('api/session'), 'user_id') - ->where('user_id = ?', $user->getId()) - ->where('sessid = ?', $user->getSessid()); - $logdate = now(); - if ($this->_getReadAdapter()->fetchRow($select)) { - $this->_getWriteAdapter()->update( - $this->getTable('api/session'), - array ('logdate' => $logdate), - $this->_getReadAdapter()->quoteInto('user_id = ?', $user->getId()) . ' AND ' - . $this->_getReadAdapter()->quoteInto('sessid = ?', $user->getSessid()) - ); - } else { - $this->_getWriteAdapter()->insert( - $this->getTable('api/session'), - array( - 'user_id' => $user->getId(), - 'logdate' => $logdate, - 'sessid' => $user->getSessid() - ) - ); - } - $user->setLogdate($logdate); - return $this; - } - - public function cleanOldSessions(Mage_Api_Model_User $user) - { - $timeout = Mage::getStoreConfig('api/config/session_timeout'); - $this->_getWriteAdapter()->delete( - $this->getTable('api/session'), - $this->_getReadAdapter()->quoteInto('user_id = ?', $user->getId()) . ' AND ' - . new Zend_Db_Expr('(UNIX_TIMESTAMP(\'' . now() . '\') - UNIX_TIMESTAMP(logdate)) > ' . $timeout) - ); - return $this; - } - - public function loadByUsername($username) - { - $select = $this->_getReadAdapter()->select()->from($this->getTable('api/user')) - ->where('username=:username'); - return $this->_getReadAdapter()->fetchRow($select, array('username'=>$username)); - } - - public function loadBySessId ($sessId) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('api/session')) - ->where('sessid = ?', $sessId); - if ($apiSession = $this->_getReadAdapter()->fetchRow($select)) { - $selectUser = $this->_getReadAdapter()->select() - ->from($this->getTable('api/user')) - ->where('user_id = ?', $apiSession['user_id']); - if ($user = $this->_getReadAdapter()->fetchRow($selectUser)) { - return array_merge($user, $apiSession); - } - } - return array(); - } - - public function clearBySessId($sessid) - { - $this->_getWriteAdapter()->delete( - $this->getTable('api/session'), - $this->_getReadAdapter()->quoteInto('sessid = ?', $sessid) - ); - return $this; - } - - public function hasAssigned2Role($user) - { - if (is_numeric($user)) { - $userId = $user; - } else if ($user instanceof Mage_Core_Model_Abstract) { - $userId = $user->getUserId(); - } else { - return null; - } - - if ( $userId > 0 ) { - $dbh = $this->_getReadAdapter(); - $select = $dbh->select(); - $select->from($this->getTable('api/role')) - ->where("parent_id > 0 AND user_id = {$userId}"); - return $dbh->fetchAll($select); - } else { - return null; - } - } - - protected function _beforeSave(Mage_Core_Model_Abstract $user) - { - if (!$user->getId()) { - $user->setCreated(now()); - } - $user->setModified(now()); - return $this; - } - - public function load(Mage_Core_Model_Abstract $user, $value, $field=null) - { - return parent::load($user, $value, $field); - } - - public function delete(Mage_Core_Model_Abstract $user) - { - $dbh = $this->_getWriteAdapter(); - $uid = (int) $user->getId(); - $dbh->beginTransaction(); - try { - $dbh->delete($this->getTable('api/user'), "user_id=$uid"); - $dbh->delete($this->getTable('api/role'), "user_id=$uid"); - } catch (Mage_Core_Exception $e) { - throw $e; - return false; - } catch (Exception $e){ - $dbh->rollBack(); - return false; - } - $dbh->commit(); - return true; - } - - public function _saveRelations(Mage_Core_Model_Abstract $user) - { - $rolesIds = $user->getRoleIds(); - - if( !is_array($rolesIds) || count($rolesIds) == 0 ) { - return $user; - } - - $this->_getWriteAdapter()->beginTransaction(); - - try { - $this->_getWriteAdapter()->delete($this->getTable('api/role'), "user_id = {$user->getId()}"); - foreach ($rolesIds as $rid) { - $rid = intval($rid); - if ($rid > 0) { - //$row = $this->load($user, $rid); - } else { - $row = array('tree_level' => 0); - } - $row = array('tree_level' => 0); - - $data = array( - 'parent_id' => $rid, - 'tree_level' => $row['tree_level'] + 1, - 'sort_order' => 0, - 'role_type' => 'U', - 'user_id' => $user->getId(), - 'role_name' => $user->getFirstname() - ); - $this->_getWriteAdapter()->insert($this->getTable('api/role'), $data); - } - $this->_getWriteAdapter()->commit(); - } catch (Mage_Core_Exception $e) { - throw $e; - } catch (Exception $e){ - $this->_getWriteAdapter()->rollBack(); - } - } - - public function _getRoles(Mage_Core_Model_Abstract $user) - { - if ( !$user->getId() ) { - return array(); - } - $table = $this->getTable('api/role'); - $read = $this->_getReadAdapter(); - $select = $read->select()->from($table, array()) - ->joinLeft(array('ar' => $table), "(ar.role_id = `{$table}`.parent_id and ar.role_type = 'G')", array('role_id')) - ->where("`{$table}`.user_id = {$user->getId()}"); - - return (($roles = $read->fetchCol($select)) ? $roles : array()); - } - - public function add(Mage_Core_Model_Abstract $user) { - - $dbh = $this->_getWriteAdapter(); - - $aRoles = $this->hasAssigned2Role($user); - if ( sizeof($aRoles) > 0 ) { - foreach($aRoles as $idx => $data){ - $dbh->delete($this->getTable('api/role'), "role_id = {$data['role_id']}"); - } - } - - if ($user->getId() > 0) { - $role = Mage::getModel('api/role')->load($user->getRoleId()); - } else { - $role = array('tree_level' => 0); - } - $dbh->insert($this->getTable('api/role'), array( - 'parent_id' => $user->getRoleId(), - 'tree_level'=> ($role->getTreeLevel() + 1), - 'sort_order'=> 0, - 'role_type' => 'U', - 'user_id' => $user->getUserId(), - 'role_name' => $user->getFirstname() - )); - - return $this; - } - - public function deleteFromRole(Mage_Core_Model_Abstract $user) { - if ( $user->getUserId() <= 0 ) { - return $this; - } - if ( $user->getRoleId() <= 0 ) { - return $this; - } - $dbh = $this->_getWriteAdapter(); - $condition = "`{$this->getTable('api/role')}`.user_id = ".$dbh->quote($user->getUserId())." AND `{$this->getTable('api/role')}`.parent_id = ".$dbh->quote($user->getRoleId()); - $dbh->delete($this->getTable('api/role'), $condition); - return $this; - } - - public function roleUserExists(Mage_Core_Model_Abstract $user) - { - if ( $user->getUserId() > 0 ) { - $roleTable = $this->getTable('api/role'); - $dbh = $this->_getReadAdapter(); - $select = $dbh->select()->from($roleTable) - ->where("parent_id = {$user->getRoleId()} AND user_id = {$user->getUserId()}"); - return $dbh->fetchCol($select); - } else { - return array(); - } - } - - public function userExists(Mage_Core_Model_Abstract $user) - { - $usersTable = $this->getTable('api/user'); - $select = $this->_getReadAdapter()->select(); - $select->from($usersTable); - $select->where("({$usersTable}.username = '{$user->getUsername()}' OR {$usersTable}.email = '{$user->getEmail()}') AND {$usersTable}.user_id != '{$user->getId()}'"); - return $this->_getReadAdapter()->fetchRow($select); - } } diff --git a/app/code/core/Mage/Api/Model/Mysql4/User/Collection.php b/app/code/core/Mage/Api/Model/Mysql4/User/Collection.php index 04485f85..f7e1abe4 100644 --- a/app/code/core/Mage/Api/Model/Mysql4/User/Collection.php +++ b/app/code/core/Mage/Api/Model/Mysql4/User/Collection.php @@ -20,14 +20,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Api_Model_Mysql4_User_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract + +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ +class Mage_Api_Model_Mysql4_User_Collection extends Mage_Api_Model_Resource_User_Collection { - protected function _construct() - { - $this->_init('api/user'); - } } diff --git a/app/code/core/Mage/Api/Model/Resource/Abstract.php b/app/code/core/Mage/Api/Model/Resource/Abstract.php index 208beda0..f5be48b1 100644 --- a/app/code/core/Mage/Api/Model/Resource/Abstract.php +++ b/app/code/core/Mage/Api/Model/Resource/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Resource/Acl.php b/app/code/core/Mage/Api/Model/Resource/Acl.php new file mode 100755 index 00000000..7dd755de --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Acl.php @@ -0,0 +1,157 @@ + + */ +class Mage_Api_Model_Resource_Acl extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource connections + * + */ + protected function _construct() + { + $this->_init('api/role', 'role_id'); + } + + /** + * Load ACL for the user + * + * @return Mage_Api_Model_Acl + */ + public function loadAcl() + { + $acl = Mage::getModel('api/acl'); + $adapter = $this->_getReadAdapter(); + + Mage::getSingleton('api/config')->loadAclResources($acl); + + $rolesArr = $adapter->fetchAll( + $adapter->select() + ->from($this->getTable('api/role')) + ->order(array('tree_level', 'role_type')) + ); + $this->loadRoles($acl, $rolesArr); + + $rulesArr = $adapter->fetchAll( + $adapter->select() + ->from(array('r'=>$this->getTable('api/rule'))) + ->joinLeft( + array('a'=>$this->getTable('api/assert')), + 'a.assert_id=r.assert_id', + array('assert_type', 'assert_data') + )); + $this->loadRules($acl, $rulesArr); + return $acl; + } + + /** + * Load roles + * + * @param Mage_Api_Model_Acl $acl + * @param array $rolesArr + * @return Mage_Api_Model_Resource_Acl + */ + public function loadRoles(Mage_Api_Model_Acl $acl, array $rolesArr) + { + foreach ($rolesArr as $role) { + $parent = $role['parent_id']>0 ? Mage_Api_Model_Acl::ROLE_TYPE_GROUP.$role['parent_id'] : null; + switch ($role['role_type']) { + case Mage_Api_Model_Acl::ROLE_TYPE_GROUP: + $roleId = $role['role_type'].$role['role_id']; + $acl->addRole(Mage::getModel('api/acl_role_group', $roleId), $parent); + break; + + case Mage_Api_Model_Acl::ROLE_TYPE_USER: + $roleId = $role['role_type'].$role['user_id']; + if (!$acl->hasRole($roleId)) { + $acl->addRole(Mage::getModel('api/acl_role_user', $roleId), $parent); + } else { + $acl->addRoleParent($roleId, $parent); + } + break; + } + } + + return $this; + } + + /** + * Load rules + * + * @param Mage_Api_Model_Acl $acl + * @param array $rulesArr + * @return Mage_Api_Model_Resource_Acl + */ + public function loadRules(Mage_Api_Model_Acl $acl, array $rulesArr) + { + foreach ($rulesArr as $rule) { + $role = $rule['role_type'].$rule['role_id']; + $resource = $rule['resource_id']; + $privileges = !empty($rule['api_privileges']) ? explode(',', $rule['api_privileges']) : null; + + $assert = null; + if (0!=$rule['assert_id']) { + $assertClass = Mage::getSingleton('api/config')->getAclAssert($rule['assert_type'])->getClassName(); + $assert = new $assertClass(unserialize($rule['assert_data'])); + } + try { + if ($rule['api_permission'] == 'allow') { + $acl->allow($role, $resource, $privileges, $assert); + } else if ($rule['api_permission'] == 'deny') { + $acl->deny($role, $resource, $privileges, $assert); + } + } catch (Exception $e) { + //$m = $e->getMessage(); + //if ( eregi("^Resource '(.*)' not found", $m) ) { + // Deleting non existent resource rule from rules table + //$cond = $this->_write->quoteInto('resource_id = ?', $resource); + //$this->_write->delete(Mage::getSingleton('core/resource')->getTableName('admin/rule'), $cond); + //} else { + //TODO: We need to log such exceptions to somewhere like a system/errors.log + //} + } + /* + switch ($rule['api_permission']) { + case Mage_Api_Model_Acl::RULE_PERM_ALLOW: + $acl->allow($role, $resource, $privileges, $assert); + break; + + case Mage_Api_Model_Acl::RULE_PERM_DENY: + $acl->deny($role, $resource, $privileges, $assert); + break; + } + */ + } + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Acl/Role.php b/app/code/core/Mage/Api/Model/Resource/Acl/Role.php new file mode 100755 index 00000000..cc357cc4 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Acl/Role.php @@ -0,0 +1,59 @@ + + */ +class Mage_Api_Model_Resource_Acl_Role extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('api/role', 'role_id'); + } + + /** + * Action before save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Api_Model_Resource_Acl_Role + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getId()) { + $this->setCreated(Mage::getSingleton('core/date')->gmtDate()); + } + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Acl/Role/Collection.php b/app/code/core/Mage/Api/Model/Resource/Acl/Role/Collection.php new file mode 100755 index 00000000..2cf5fed2 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Acl/Role/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Api_Model_Resource_Acl_Role_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/role'); + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Permissions/Collection.php b/app/code/core/Mage/Api/Model/Resource/Permissions/Collection.php new file mode 100755 index 00000000..a2a410b0 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Permissions/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Api_Model_Resource_Permissions_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/rules'); + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Role.php b/app/code/core/Mage/Api/Model/Resource/Role.php new file mode 100755 index 00000000..49fa7949 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Role.php @@ -0,0 +1,76 @@ + + */ +class Mage_Api_Model_Resource_Role extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('api/role', 'role_id'); + } + + /** + * Action before save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Api_Model_Resource_Role + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getId()) { + $object->setCreated(now()); + } + $object->setModified(now()); + return $this; + } + + /** + * Load an object + * + * @param Mage_Core_Model_Abstract $object + * @param mixed $value + * @param string $field field to load by (defaults to model id) + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (!intval($value) && is_string($value)) { + $field = 'role_id'; + } + return parent::load($object, $value, $field); + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Role/Collection.php b/app/code/core/Mage/Api/Model/Resource/Role/Collection.php new file mode 100755 index 00000000..48a44d83 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Role/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Api_Model_Resource_Role_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/role'); + } + + /** + * Aet user filter + * + * @param int $userId + * @return Mage_Api_Model_Resource_Role_Collection + */ + public function setUserFilter($userId) + { + $this->addFieldToFilter('user_id', $userId); + $this->addFieldToFilter('role_type', Mage_Api_Model_Acl::ROLE_TYPE_GROUP); + return $this; + } + + /** + * Set roles filter + * + * @return Mage_Api_Model_Resource_Role_Collection + */ + public function setRolesFilter() + { + $this->addFieldToFilter('role_type', Mage_Api_Model_Acl::ROLE_TYPE_GROUP); + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Roles.php b/app/code/core/Mage/Api/Model/Resource/Roles.php new file mode 100755 index 00000000..61578c7c --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Roles.php @@ -0,0 +1,151 @@ + + */ +class Mage_Api_Model_Resource_Roles extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * User table name + * + * @var unknown + */ + protected $_usersTable; + + /** + * Rule table name + * + * @var unknown + */ + protected $_ruleTable; + + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('api/role', 'role_id'); + + $this->_usersTable = $this->getTable('api/user'); + $this->_ruleTable = $this->getTable('api/rule'); + } + + /** + * Action before save + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Api_Model_Resource_Roles + */ + protected function _beforeSave(Mage_Core_Model_Abstract $role) + { + if ($role->getId() == '') { + if ($role->getIdFieldName()) { + $role->unsetData($role->getIdFieldName()); + } else { + $role->unsetData('id'); + } + } + + if ($role->getPid() > 0) { + $row = $this->load($role->getPid()); + } else { + $row = array('tree_level' => 0); + } + $role->setTreeLevel($row['tree_level'] + 1); + $role->setRoleName($role->getName()); + return $this; + } + + /** + * Action after save + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Api_Model_Resource_Roles + */ + protected function _afterSave(Mage_Core_Model_Abstract $role) + { + $this->_updateRoleUsersAcl($role); + Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG); + return $this; + } + + /** + * Action after delete + * + * @param Mage_Core_Model_Abstract $role + * @return Mage_Api_Model_Resource_Roles + */ + protected function _afterDelete(Mage_Core_Model_Abstract $role) + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable(), array('parent_id=?'=>$role->getId())); + $adapter->delete($this->_ruleTable, array('role_id=?'=>$role->getId())); + return $this; + } + + /** + * Get role users + * + * @param Mage_Api_Model_Roles $role + * @return unknown + */ + public function getRoleUsers(Mage_Api_Model_Roles $role) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), array('user_id')) + ->where('parent_id = ?', $role->getId()) + ->where('role_type = ?', Mage_Api_Model_Acl::ROLE_TYPE_USER) + ->where('user_id > 0'); + return $adapter->fetchCol($select); + } + + /** + * Update role users + * + * @param Mage_Api_Model_Roles $role + * @return boolean + */ + private function _updateRoleUsersAcl(Mage_Api_Model_Roles $role) + { + $users = $this->getRoleUsers($role); + $rowsCount = 0; + if (sizeof($users) > 0) { + $rowsCount = $this->_getWriteAdapter()->update( + $this->_usersTable, + array('reload_acl_flag' => 1), + array('user_id IN(?)' => $users)); + } + return ($rowsCount > 0) ? true : false; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Roles/Collection.php b/app/code/core/Mage/Api/Model/Resource/Roles/Collection.php new file mode 100755 index 00000000..c9f28635 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Roles/Collection.php @@ -0,0 +1,67 @@ + + */ +class Mage_Api_Model_Resource_Roles_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/role'); + } + + /** + * Init collection select + * + * @return Mage_Api_Model_Resource_Roles_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + $this->getSelect()->where('main_table.role_type = ?', Mage_Api_Model_Acl::ROLE_TYPE_GROUP); + return $this; + } + + /** + * Convert items array to array for select options + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('role_id', 'role_name'); + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Roles/User/Collection.php b/app/code/core/Mage/Api/Model/Resource/Roles/User/Collection.php new file mode 100755 index 00000000..4f86e353 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Roles/User/Collection.php @@ -0,0 +1,59 @@ + + */ +class Mage_Api_Model_Resource_Roles_User_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/user'); + } + + /** + * Init collection select + * + * @return Mage_Api_Model_Resource_Roles_User_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + + $this->getSelect()->where("user_id > 0"); + + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Rules.php b/app/code/core/Mage/Api/Model/Resource/Rules.php new file mode 100755 index 00000000..16311f1d --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Rules.php @@ -0,0 +1,87 @@ + + */ +class Mage_Api_Model_Resource_Rules extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('api/rule', 'rule_id'); + } + + /** + * Save rule + * + * @param Mage_Api_Model_Rules $rule + */ + public function saveRel(Mage_Api_Model_Rules $rule) + { + $adapter = $this->_getWriteAdapter(); + $adapter->beginTransaction(); + + try { + $roleId = $rule->getRoleId(); + $adapter->delete($this->getMainTable(), array('role_id = ?' => $roleId)); + $masterResources = Mage::getModel('api/roles')->getResourcesList2D(); + $masterAdmin = false; + if ($postedResources = $rule->getResources()) { + foreach ($masterResources as $index => $resName) { + if (!$masterAdmin) { + $permission = (in_array($resName, $postedResources))? 'allow' : 'deny'; + $adapter->insert($this->getMainTable(), array( + 'role_type' => 'G', + 'resource_id' => trim($resName, '/'), + 'api_privileges' => null, + 'assert_id' => 0, + 'role_id' => $roleId, + 'api_permission' => $permission + )); + } + if ($resName == 'all' && $permission == 'allow') { + $masterAdmin = true; + } + } + } + + $adapter->commit(); + } catch (Mage_Core_Exception $e) { + throw $e; + } catch (Exception $e) { + $adapter->rollBack(); + } + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/Rules/Collection.php b/app/code/core/Mage/Api/Model/Resource/Rules/Collection.php new file mode 100755 index 00000000..96359c3d --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/Rules/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Api_Model_Resource_Rules_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/rules'); + } + + /** + * Retrieve rules by role + * + * @param int $id + * @return Mage_Api_Model_Resource_Rules_Collection + */ + public function getByRoles($id) + { + $this->getSelect()->where("role_id = ?", (int)$id); + return $this; + } + + /** + * Add sort by length + * + * @return Mage_Api_Model_Resource_Rules_Collection + */ + public function addSortByLength() + { + $this->getSelect()->columns(array('length' => $this->getConnection()->getLengthSql('resource_id'))) + ->order('length DESC'); + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/User.php b/app/code/core/Mage/Api/Model/Resource/User.php new file mode 100755 index 00000000..b137f8a9 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/User.php @@ -0,0 +1,435 @@ + + */ +class Mage_Api_Model_Resource_User extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('api/user', 'user_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Api_Model_Resource_User + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array( + array( + 'field' => 'email', + 'title' => Mage::helper('api')->__('Email') + ), + array( + 'field' => 'username', + 'title' => Mage::helper('api')->__('User Name') + ), + ); + return $this; + } + + /** + * Authenticate user by $username and $password + * + * @param Mage_Api_Model_User $user + * @return Mage_Api_Model_Resource_User + */ + public function recordLogin(Mage_Api_Model_User $user) + { + $data = array( + 'lognum' => $user->getLognum()+1, + ); + $condition = $this->_getReadAdapter()->quoteInto('user_id=?', $user->getUserId()); + $this->_getWriteAdapter()->update($this->getTable('api/user'), $data, $condition); + return $this; + } + + /** + * Record api user session + * + * @param Mage_Api_Model_User $user + * @return Mage_Api_Model_Resource_User + */ + public function recordSession(Mage_Api_Model_User $user) + { + $readAdapter = $this->_getReadAdapter(); + $writeAdapter = $this->_getWriteAdapter(); + $select = $readAdapter->select() + ->from($this->getTable('api/session'), 'user_id') + ->where('user_id = ?', $user->getId()) + ->where('sessid = ?', $user->getSessid()); + $loginDate = now(); + if ($readAdapter->fetchRow($select)) { + $writeAdapter->update( + $this->getTable('api/session'), + array ('logdate' => $loginDate), + $readAdapter->quoteInto('user_id = ?', $user->getId()) . ' AND ' + . $readAdapter->quoteInto('sessid = ?', $user->getSessid()) + ); + } else { + $writeAdapter->insert( + $this->getTable('api/session'), + array( + 'user_id' => $user->getId(), + 'logdate' => $loginDate, + 'sessid' => $user->getSessid() + ) + ); + } + $user->setLogdate($loginDate); + return $this; + } + + /** + * Clean old session + * + * @param Mage_Api_Model_User $user + * @return Mage_Api_Model_Resource_User + */ + public function cleanOldSessions(Mage_Api_Model_User $user) + { + $readAdapter = $this->_getReadAdapter(); + $writeAdapter = $this->_getWriteAdapter(); + $timeout = Mage::getStoreConfig('api/config/session_timeout'); + $timeSubtract = $readAdapter->getDateAddSql( + 'logdate', + $timeout, + Varien_Db_Adapter_Interface::INTERVAL_SECOND); + $writeAdapter->delete( + $this->getTable('api/session'), + array('user_id = ?' => $user->getId(), $readAdapter->quote(now()) . ' > '.$timeSubtract) + ); + return $this; + } + + /** + * Load data by username + * + * @param string $username + * @return array + */ + public function loadByUsername($username) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select()->from($this->getTable('api/user')) + ->where('username=:username'); + return $adapter->fetchRow($select, array('username'=>$username)); + } + + /** + * load by session id + * + * @param string $sessId + * @return array + */ + public function loadBySessId($sessId) + { + $result = array(); + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getTable('api/session')) + ->where('sessid = ?', $sessId); + if ($apiSession = $adapter->fetchRow($select)) { + $selectUser = $adapter->select() + ->from($this->getTable('api/user')) + ->where('user_id = ?', $apiSession['user_id']); + if ($user = $adapter->fetchRow($selectUser)) { + $result = array_merge($user, $apiSession); + } + } + return $result; + } + + /** + * Clear by session + * + * @param string $sessid + * @return Mage_Api_Model_Resource_User + */ + public function clearBySessId($sessid) + { + $this->_getWriteAdapter()->delete( + $this->getTable('api/session'), + array('sessid = ?' => $sessid) + ); + return $this; + } + + /** + * Retrieve api user role data if it was assigned to role + * + * @param int | Mage_Api_Model_User $user + * @return null | array + */ + public function hasAssigned2Role($user) + { + $userId = null; + $result = null; + if (is_numeric($user)) { + $userId = $user; + } else if ($user instanceof Mage_Core_Model_Abstract) { + $userId = $user->getUserId(); + } + + if ($userId) { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select(); + $select->from($this->getTable('api/role')) + ->where('parent_id > 0 AND user_id = ?', $userId); + $result = $adapter->fetchAll($select); + } + return $result; + } + + /** + * Action before save + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Api_Model_Resource_User + */ + protected function _beforeSave(Mage_Core_Model_Abstract $user) + { + if (!$user->getId()) { + $user->setCreated(now()); + } + $user->setModified(now()); + return $this; + } + + /** + * Delete the object + * + * @param Mage_Core_Model_Abstract $user + * @return boolean + */ + public function delete(Mage_Core_Model_Abstract $user) + { + $dbh = $this->_getWriteAdapter(); + $uid = (int) $user->getId(); + $dbh->beginTransaction(); + try { + $dbh->delete($this->getTable('api/user'), array('user_id = ?' => $uid)); + $dbh->delete($this->getTable('api/role'), array('user_id = ?' => $uid)); + } catch (Mage_Core_Exception $e) { + throw $e; + return false; + } catch (Exception $e) { + $dbh->rollBack(); + return false; + } + $dbh->commit(); + return true; + } + + /** + * Save user roles + * + * @param Mage_Core_Model_Abstract $user + * @return unknown + */ + public function _saveRelations(Mage_Core_Model_Abstract $user) + { + $rolesIds = $user->getRoleIds(); + if (!is_array($rolesIds) || count($rolesIds) == 0) { + return $user; + } + + $adapter = $this->_getWriteAdapter(); + + $adapter->beginTransaction(); + + try { + $adapter->delete( + $this->getTable('api/role'), + array('user_id = ?' => (int) $user->getId())); + foreach ($rolesIds as $rid) { + $rid = intval($rid); + if ($rid > 0) { + //$row = $this->load($user, $rid); + } else { + $row = array('tree_level' => 0); + } + $row = array('tree_level' => 0); + + $data = array( + 'parent_id' => $rid, + 'tree_level' => $row['tree_level'] + 1, + 'sort_order' => 0, + 'role_type' => Mage_Api_Model_Acl::ROLE_TYPE_USER, + 'user_id' => $user->getId(), + 'role_name' => $user->getFirstname() + ); + $adapter->insert($this->getTable('api/role'), $data); + } + $adapter->commit(); + } catch (Mage_Core_Exception $e) { + throw $e; + } catch (Exception $e) { + $adapter->rollBack(); + } + return $this; + } + + /** + * Retrieve roles data + * + * @param Mage_Core_Model_Abstract $user + * @return array + */ + public function _getRoles(Mage_Core_Model_Abstract $user) + { + if (!$user->getId()) { + return array(); + } + $table = $this->getTable('api/role'); + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($table, array()) + ->joinLeft( + array('ar' => $table), + $adapter->quoteInto( + "ar.role_id = {$table}.parent_id AND ar.role_type = ?", + Mage_Api_Model_Acl::ROLE_TYPE_GROUP), + array('role_id')) + ->where("{$table}.user_id = ?", $user->getId()); + + return (($roles = $adapter->fetchCol($select)) ? $roles : array()); + } + + /** + * Add Role + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Api_Model_Resource_User + */ + public function add(Mage_Core_Model_Abstract $user) + { + $adapter = $this->_getWriteAdapter(); + $aRoles = $this->hasAssigned2Role($user); + if (sizeof($aRoles) > 0) { + foreach ($aRoles as $idx => $data) { + $adapter->delete( + $this->getTable('api/role'), + array('role_id = ?' => $data['role_id']) + ); + } + } + + if ($user->getId() > 0) { + $role = Mage::getModel('api/role')->load($user->getRoleId()); + } else { + $role = new Varien_Object(array('tree_level' => 0)); + } + $adapter->insert($this->getTable('api/role'), array( + 'parent_id' => $user->getRoleId(), + 'tree_level'=> ($role->getTreeLevel() + 1), + 'sort_order'=> 0, + 'role_type' => Mage_Api_Model_Acl::ROLE_TYPE_USER, + 'user_id' => $user->getUserId(), + 'role_name' => $user->getFirstname() + )); + + return $this; + } + + /** + * Delete from role + * + * @param Mage_Core_Model_Abstract $user + * @return Mage_Api_Model_Resource_User + */ + public function deleteFromRole(Mage_Core_Model_Abstract $user) + { + if ($user->getUserId() <= 0) { + return $this; + } + if ($user->getRoleId() <= 0) { + return $this; + }; + + $adapter = $this->_getWriteAdapter(); + $table = $this->getTable('api/role'); + + $condition = array( + "{$table}.user_id = ?" => $user->getUserId(), + "{$table}.parent_id = ?"=> $user->getRoleId() + ); + $adapter->delete($table, $condition); + return $this; + } + + /** + * Retrieve roles which exists for user + * + * @param Mage_Core_Model_Abstract $user + * @return array + */ + public function roleUserExists(Mage_Core_Model_Abstract $user) + { + $result = array(); + if ($user->getUserId() > 0) { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select()->from($this->getTable('api/role')) + ->where('parent_id = ?', $user->getRoleId()) + ->where('user_id = ?', $user->getUserId()); + $result = $adapter->fetchCol($select); + } + return $result; + } + + /** + * Check if user not unique + * + * @param Mage_Core_Model_Abstract $user + * @return array + */ + public function userExists(Mage_Core_Model_Abstract $user) + { + $usersTable = $this->getTable('api/user'); + $adapter = $this->_getReadAdapter(); + $condition = array( + $adapter->quoteInto("{$usersTable}.username = ?", $user->getUsername()), + $adapter->quoteInto("{$usersTable}.email = ?", $user->getEmail()), + ); + $select = $adapter->select() + ->from($usersTable) + ->where(implode(' OR ', $condition)) + ->where($usersTable.'.user_id != ?', (int) $user->getId()); + return $adapter->fetchRow($select); + } +} diff --git a/app/code/core/Mage/Api/Model/Resource/User/Collection.php b/app/code/core/Mage/Api/Model/Resource/User/Collection.php new file mode 100755 index 00000000..daccae70 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Resource/User/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Api_Model_Resource_User_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource collection initialization + * + */ + protected function _construct() + { + $this->_init('api/user'); + } +} diff --git a/app/code/core/Mage/Api/Model/Role.php b/app/code/core/Mage/Api/Model/Role.php index a1b8b4bd..1376281b 100644 --- a/app/code/core/Mage/Api/Model/Role.php +++ b/app/code/core/Mage/Api/Model/Role.php @@ -20,12 +20,37 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Role item model + * + * @method Mage_Api_Model_Resource_Role _getResource() + * @method Mage_Api_Model_Resource_Role getResource() + * @method int getParentId() + * @method Mage_Api_Model_Role setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Api_Model_Role setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Api_Model_Role setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Api_Model_Role setRoleType(string $value) + * @method int getUserId() + * @method Mage_Api_Model_Role setUserId(int $value) + * @method string getRoleName() + * @method Mage_Api_Model_Role setRoleName(string $value) + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ class Mage_Api_Model_Role extends Mage_Core_Model_Abstract { + /** + * Initialize resource + */ protected function _construct() { $this->_init('api/role'); diff --git a/app/code/core/Mage/Api/Model/Roles.php b/app/code/core/Mage/Api/Model/Roles.php index 4ce7adfa..553fd3ea 100644 --- a/app/code/core/Mage/Api/Model/Roles.php +++ b/app/code/core/Mage/Api/Model/Roles.php @@ -20,12 +20,44 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Api_Model_Resource_Roles _getResource() + * @method Mage_Api_Model_Resource_Roles getResource() + * @method int getParentId() + * @method Mage_Api_Model_Roles setParentId(int $value) + * @method int getTreeLevel() + * @method Mage_Api_Model_Roles setTreeLevel(int $value) + * @method int getSortOrder() + * @method Mage_Api_Model_Roles setSortOrder(int $value) + * @method string getRoleType() + * @method Mage_Api_Model_Roles setRoleType(string $value) + * @method int getUserId() + * @method Mage_Api_Model_Roles setUserId(int $value) + * @method string getRoleName() + * @method Mage_Api_Model_Roles setRoleName(string $value) + * @method string getName() + * @method Mage_Api_Model_Role setName() setName(string $name) + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ class Mage_Api_Model_Roles extends Mage_Core_Model_Abstract { + /** + * Filters + * + * @var array + */ + protected $_filters; + + protected function _construct() { $this->_init('api/roles'); @@ -62,8 +94,10 @@ public function getRoleUsers() return $this->getResource()->getRoleUsers($this); } - protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $parentName=null, $level=0, $represent2Darray=null, $rawNodes = false, $module = 'adminhtml') - { + protected function _buildResourcesArray( + Varien_Simplexml_Element $resource = null, $parentName = null, $level = 0, $represent2Darray = null, + $rawNodes = false, $module = 'adminhtml' + ) { static $result; if (is_null($resource)) { @@ -72,7 +106,9 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, $level = -1; } else { $resourceName = $parentName; - if ($resource->getName()!='title' && $resource->getName()!='sort_order' && $resource->getName() != 'children') { + if ($resource->getName()!='title' && $resource->getName()!='sort_order' + && $resource->getName() != 'children' + ) { $resourceName = (is_null($parentName) ? '' : $parentName.'/').$resource->getName(); //assigning module for its' children nodes @@ -113,4 +149,33 @@ protected function _buildResourcesArray(Varien_Simplexml_Element $resource=null, } } + /** + * Filter data before save + * + * @return Mage_Api_Model_Roles + */ + protected function _beforeSave() + { + $this->filter(); + parent::_beforeSave(); + return $this; + } + + /** + * Filter set data + * + * @return Mage_Api_Model_Roles + */ + public function filter() + { + $data = $this->getData(); + if (!$this->_filters || !$data) { + return $this; + } + /** @var $filter Mage_Core_Model_Input_Filter */ + $filter = Mage::getModel('core/input_filter'); + $filter->setFilters($this->_filters); + $this->setData($filter->filter($data)); + return $this; + } } diff --git a/app/code/core/Mage/Api/Model/Rules.php b/app/code/core/Mage/Api/Model/Rules.php index 79f8e1f1..38d2649c 100644 --- a/app/code/core/Mage/Api/Model/Rules.php +++ b/app/code/core/Mage/Api/Model/Rules.php @@ -20,10 +20,32 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Api_Model_Resource_Rules _getResource() + * @method Mage_Api_Model_Resource_Rules getResource() + * @method int getRoleId() + * @method Mage_Api_Model_Rules setRoleId(int $value) + * @method string getResourceId() + * @method Mage_Api_Model_Rules setResourceId(string $value) + * @method string getPrivileges() + * @method Mage_Api_Model_Rules setPrivileges(string $value) + * @method int getAssertId() + * @method Mage_Api_Model_Rules setAssertId(int $value) + * @method string getRoleType() + * @method Mage_Api_Model_Rules setRoleType(string $value) + * @method string getPermission() + * @method Mage_Api_Model_Rules setPermission(string $value) + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ class Mage_Api_Model_Rules extends Mage_Core_Model_Abstract { protected function _construct() diff --git a/app/code/core/Mage/Api/Model/Server.php b/app/code/core/Mage/Api/Model/Server.php index bbf8a9a7..a9589c58 100644 --- a/app/code/core/Mage/Api/Model/Server.php +++ b/app/code/core/Mage/Api/Model/Server.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,37 +33,99 @@ */ class Mage_Api_Model_Server { + + /** + * Api Name by Adapter + * @var string + */ + protected $_api = ""; + /** * Web service adapter * - * @var Mage_Api_Model_Server_Adaper_Interface + * @var Mage_Api_Model_Server_Adapter_Interface */ protected $_adapter; - public function init(Mage_Api_Controller_Action $controller, $adapter='default', $handler='default') + /** + * Complex retrieve adapter code by calling auxiliary model method + * + * @param string $alias Alias name + * @return string|null Returns NULL if no alias found + */ + public function getAdapterCodeByAlias($alias) + { + /** @var $config Mage_Api_Model_Config */ + $config = Mage::getSingleton('api/config'); + $aliases = $config->getAdapterAliases(); + + if (!isset($aliases[$alias])) { + return null; + } + $object = Mage::getModel($aliases[$alias][0]); + $method = $aliases[$alias][1]; + + if (!method_exists($object, $method)) { + Mage::throwException(Mage::helper('api')->__('Can not find webservice adapter.')); + } + return $object->$method(); + } + + /** + * Initialize server components + * + * @param Mage_Api_Controller_Action $controller + * @param string $adapter Adapter name + * @param string $handler Handler name + * @return Mage_Api_Model_Server + */ + public function init(Mage_Api_Controller_Action $controller, $adapter = 'default', $handler = 'default') { - $adapters = Mage::getSingleton('api/config')->getActiveAdapters(); - $handlers = Mage::getSingleton('api/config')->getHandlers(); - if (isset($adapters[$adapter])) { - $adapterModel = Mage::getModel((string) $adapters[$adapter]->model); - /* @var $adapterModel Mage_Api_Model_Server_Adapter_Interface */ + $this->initialize($adapter, $handler); + + $this->_adapter->setController($controller); + + return $this; + } + + /** + * Initialize server components. Lightweight implementation of init() method + * + * @param string $adapterCode Adapter code + * @param string $handler OPTIONAL Handler name (if not specified, it will be found from config) + * @return Mage_Api_Model_Server + */ + public function initialize($adapterCode, $handler = null) + { + /** @var $helper Mage_Api_Model_Config */ + $helper = Mage::getSingleton('api/config'); + $adapters = $helper->getActiveAdapters(); + + if (isset($adapters[$adapterCode])) { + /** @var $adapterModel Mage_Api_Model_Server_Adapter_Interface */ + $adapterModel = Mage::getModel((string) $adapters[$adapterCode]->model); + if (!($adapterModel instanceof Mage_Api_Model_Server_Adapter_Interface)) { Mage::throwException(Mage::helper('api')->__('Invalid webservice adapter specified.')); } - $this->_adapter = $adapterModel; - $this->_adapter->setController($controller); + $this->_api = $adapterCode; + + // get handler code from config if no handler passed as argument + if (null === $handler && !empty($adapters[$adapterCode]->handler)) { + $handler = (string) $adapters[$adapterCode]->handler; + } + $handlers = $helper->getHandlers(); if (!isset($handlers->$handler)) { Mage::throwException(Mage::helper('api')->__('Invalid webservice handler specified.')); } - $handlerClassName = Mage::getConfig()->getModelClassName((string) $handlers->$handler->model); + $this->_adapter->setHandler($handlerClassName); } else { Mage::throwException(Mage::helper('api')->__('Invalid webservice adapter specified.')); } - return $this; } @@ -76,10 +138,19 @@ public function run() $this->getAdapter()->run(); } + /** + * Get Api name by Adapter + * @return string + */ + public function getApiName() + { + return $this->_api; + } + /** * Retrieve web service adapter * - * @return Mage_Api_Model_Server_Adaper_Interface + * @return Mage_Api_Model_Server_Adapter_Interface */ public function getAdapter() { diff --git a/app/code/core/Mage/Api/Model/Server/Adapter/Interface.php b/app/code/core/Mage/Api/Model/Server/Adapter/Interface.php index 0bcf1d51..e48430c4 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Interface.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php b/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php index ab8af1a9..627bdd8f 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Soap.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -64,7 +64,7 @@ public function getHandler() return $this->getData('handler'); } - /** + /** * Set webservice api controller * * @param Mage_Api_Controller_Action $controller @@ -77,13 +77,22 @@ public function setController(Mage_Api_Controller_Action $controller) } /** - * Retrive webservice api controller + * Retrive webservice api controller. If no controller have been set - emulate it by the use of Varien_Object * - * @return Mage_Api_Controller_Action + * @return Mage_Api_Controller_Action|Varien_Object */ public function getController() { - return $this->getData('controller'); + $controller = $this->getData('controller'); + + if (null === $controller) { + $controller = new Varien_Object( + array('request' => Mage::app()->getRequest(), 'response' => Mage::app()->getResponse()) + ); + + $this->setData('controller', $controller); + } + return $controller; } /** @@ -94,11 +103,11 @@ public function getController() */ public function run() { - $urlModel = Mage::getModel('core/url') - ->setUseSession(false); + $apiConfigCharset = Mage::getStoreConfig("api/config/charset"); + if ($this->getController()->getRequest()->getParam('wsdl') !== null) { // Generating wsdl content from template - $io = new Varien_Io_File(); + $io = new Varien_Io_File(); $io->open(array('path'=>Mage::getModuleDir('etc', 'Mage_Api'))); $wsdlContent = $io->read('wsdl.xml'); @@ -111,28 +120,43 @@ public function run() unset($queryParams['wsdl']); } - $wsdlConfig->setUrl( - htmlspecialchars($urlModel->getUrl('*/*/*', array('_query'=>$queryParams))) - ); + $wsdlConfig->setUrl(htmlspecialchars(Mage::getUrl('*/*/*', array('_query'=>$queryParams)))); $wsdlConfig->setName('Magento'); $wsdlConfig->setHandler($this->getHandler()); - $template->setVariables(array('wsdl'=>$wsdlConfig)); + $template->setVariables(array('wsdl' => $wsdlConfig)); $this->getController()->getResponse() - ->setHeader('Content-Type','text/xml') - ->setBody($template->filter($wsdlContent)); - } elseif ($this->_extensionLoaded()) { - $this->_soap = new SoapServer($urlModel->getUrl('*/*/*', array('wsdl'=>1))); - use_soap_error_handler(false); - $this->_soap->setClass($this->getHandler()); - $this->getController()->getResponse() - ->setHeader('Content-Type', 'text/xml') - ->setBody($this->_soap->handle()); - + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $template->filter($wsdlContent) + ) + ); } else { - $this->fault('0', 'Unable to load Soap extension on the server'); + try { + $this->_instantiateServer(); + + $this->getController()->getResponse() + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $this->_soap->handle() + ) + ); + } catch( Zend_Soap_Server_Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } catch( Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } } + return $this; } @@ -160,13 +184,80 @@ public function fault($code, $message) } /** - * Check whether Soap extension is loaded + * Check whether Soap extension is loaded * - * @return boolean + * @return boolean */ protected function _extensionLoaded() { return class_exists('SoapServer', false); } -} // Class Mage_Api_Model_Server_Adapter_Soap End + /** + * Transform wsdl url if $_SERVER["PHP_AUTH_USER"] is set + * + * @param array + * @return String + */ + protected function getWsdlUrl($params = null, $withAuth = true) + { + $urlModel = Mage::getModel('core/url') + ->setUseSession(false); + + $wsdlUrl = $params !== null + ? $urlModel->getUrl('*/*/*', array('_current' => true, '_query' => $params)) + : $urlModel->getUrl('*/*/*'); + + if( $withAuth ) { + $phpAuthUser = $this->getController()->getRequest()->getServer('PHP_AUTH_USER', false); + $phpAuthPw = $this->getController()->getRequest()->getServer('PHP_AUTH_PW', false); + + if ($phpAuthUser && $phpAuthPw) { + $wsdlUrl = sprintf("http://%s:%s@%s", $phpAuthUser, $phpAuthPw, str_replace('http://', '', $wsdlUrl )); + } + } + + return $wsdlUrl; + } + + /** + * Try to instantiate Zend_Soap_Server + * If schema import error is caught, it will retry in 1 second. + * + * @throws Zend_Soap_Server_Exception + */ + protected function _instantiateServer() + { + $apiConfigCharset = Mage::getStoreConfig('api/config/charset'); + $wsdlCacheEnabled = (bool) Mage::getStoreConfig('api/config/wsdl_cache_enabled'); + + if ($wsdlCacheEnabled) { + ini_set('soap.wsdl_cache_enabled', '1'); + } else { + ini_set('soap.wsdl_cache_enabled', '0'); + } + + $tries = 0; + do { + $retry = false; + try { + $this->_soap = new Zend_Soap_Server($this->getWsdlUrl(array("wsdl" => 1)), + array('encoding' => $apiConfigCharset)); + } catch (SoapFault $e) { + if (false !== strpos($e->getMessage(), + "can't import schema from 'http://schemas.xmlsoap.org/soap/encoding/'") + ) { + $retry = true; + sleep(1); + } else { + throw $e; + } + $tries++; + } + } while ($retry && $tries < 5); + use_soap_error_handler(false); + $this->_soap + ->setReturnResponse(true) + ->setClass($this->getHandler()); + } +} diff --git a/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php b/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php index e1788387..02412844 100644 --- a/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php +++ b/app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -77,27 +77,39 @@ public function setController(Mage_Api_Controller_Action $controller) } /** - * Retrive webservice api controller + * Retrive webservice api controller. If no controller have been set - emulate it by the use of Varien_Object * - * @return Mage_Api_Controller_Action + * @return Mage_Api_Controller_Action|Varien_Object */ public function getController() { - return $this->getData('controller'); + $controller = $this->getData('controller'); + + if (null === $controller) { + $controller = new Varien_Object( + array('request' => Mage::app()->getRequest(), 'response' => Mage::app()->getResponse()) + ); + + $this->setData('controller', $controller); + } + return $controller; } /** * Run webservice * - * @param Mage_Api_Controller_Action $controller * @return Mage_Api_Model_Server_Adapter_Xmlrpc */ public function run() { + $apiConfigCharset = Mage::getStoreConfig("api/config/charset"); + $this->_xmlRpc = new Zend_XmlRpc_Server(); - $this->_xmlRpc->setClass($this->getHandler()); + $this->_xmlRpc->setEncoding($apiConfigCharset) + ->setClass($this->getHandler()); $this->getController()->getResponse() - ->setHeader('Content-Type', 'text/xml') + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) ->setBody($this->_xmlRpc->handle()); return $this; } diff --git a/app/code/core/Mage/Api/Model/Server/Handler.php b/app/code/core/Mage/Api/Model/Server/Handler.php index 201018f2..4c4181b7 100644 --- a/app/code/core/Mage/Api/Model/Server/Handler.php +++ b/app/code/core/Mage/Api/Model/Server/Handler.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php b/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php index ba1bc1c5..2661d131 100644 --- a/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php +++ b/app/code/core/Mage/Api/Model/Server/Handler/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,6 +38,7 @@ abstract class Mage_Api_Model_Server_Handler_Abstract public function __construct() { set_error_handler(array($this, 'handlePhpError'), E_ALL); + Mage::app()->loadAreaPart(Mage_Core_Model_App_Area::AREA_ADMINHTML, Mage_Core_Model_App_Area::PART_EVENTS); } public function handlePhpError($errorCode, $errorMessage, $errorFile) @@ -109,7 +110,7 @@ protected function _isAllowed($resource, $privilege=null) /** * Check session expiration * - * @return boolean + * @return boolean */ protected function _isSessionExpired () { @@ -209,10 +210,14 @@ protected function _prepareResourceModelName($resource) * @param string $apiKey * @return string */ - public function login($username, $apiKey) + public function login($username, $apiKey = null) { - $this->_startSession(); + if (empty($username) || empty($apiKey)) { + return $this->_fault('invalid_request_param'); + } + try { + $this->_startSession(); $this->_getSession()->login($username, $apiKey); } catch (Exception $e) { return $this->_fault('access_denied'); @@ -224,7 +229,7 @@ public function login($username, $apiKey) * Call resource functionality * * @param string $sessionId - * @param string $resourcePath + * @param string $apiPath * @param array $args * @return mixed */ @@ -436,6 +441,11 @@ public function multiCall($sessionId, array $calls = array(), $options = array() public function resources($sessionId) { $this->_startSession($sessionId); + + if (!$this->_getSession()->isLoggedIn($sessionId)) { + return $this->_fault('session_expired'); + } + $resources = array(); $resourcesAlias = array(); @@ -497,6 +507,10 @@ public function resourceFaults($sessionId, $resourceName) { $this->_startSession($sessionId); + if (!$this->_getSession()->isLoggedIn($sessionId)) { + return $this->_fault('session_expired'); + } + $resourcesAlias = $this->_getConfig()->getResourcesAlias(); $resources = $this->_getConfig()->getResources(); diff --git a/app/code/core/Mage/Api/Model/Server/V2/Adapter/Soap.php b/app/code/core/Mage/Api/Model/Server/V2/Adapter/Soap.php index 0504552b..b14c14db 100644 --- a/app/code/core/Mage/Api/Model/Server/V2/Adapter/Soap.php +++ b/app/code/core/Mage/Api/Model/Server/V2/Adapter/Soap.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -41,28 +41,43 @@ class Mage_Api_Model_Server_V2_Adapter_Soap extends Mage_Api_Model_Server_Adapte */ public function run() { - $urlModel = Mage::getModel('core/url') - ->setUseSession(false); - if ($this->getController()->getRequest()->getParam('wsdl')) { + $apiConfigCharset = Mage::getStoreConfig("api/config/charset"); + + if ($this->getController()->getRequest()->getParam('wsdl') !== null) { $wsdlConfig = Mage::getModel('api/wsdl_config'); $wsdlConfig->setHandler($this->getHandler()) ->init(); $this->getController()->getResponse() - ->setHeader('Content-Type','text/xml') - ->setBody($wsdlConfig->getWsdlContent()); - } elseif ($this->_extensionLoaded()) { - $this->_soap = new SoapServer($urlModel->getUrl('*/*/*', array('wsdl'=>1))); - use_soap_error_handler(false); - $this->_soap->setClass($this->getHandler()); - $this->getController()->getResponse() - ->setHeader('Content-Type', 'text/xml') - ->setBody($this->_soap->handle()); - + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $wsdlConfig->getWsdlContent() + ) + ); } else { - $this->fault('0', 'Unable to load Soap extension on the server'); + try { + $this->_instantiateServer(); + + $this->getController()->getResponse() + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $this->_soap->handle() + ) + ); + } catch( Zend_Soap_Server_Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } catch( Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } } + return $this; } - - } diff --git a/app/code/core/Mage/Api/Model/Server/V2/Handler.php b/app/code/core/Mage/Api/Model/Server/V2/Handler.php index d392c1e6..a593dcf5 100644 --- a/app/code/core/Mage/Api/Model/Server/V2/Handler.php +++ b/app/code/core/Mage/Api/Model/Server/V2/Handler.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php b/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php new file mode 100644 index 00000000..327c4ad3 --- /dev/null +++ b/app/code/core/Mage/Api/Model/Server/Wsi/Adapter/Soap.php @@ -0,0 +1,103 @@ + + */ +class Mage_Api_Model_Server_WSI_Adapter_Soap extends Mage_Api_Model_Server_Adapter_Soap +{ + /** + * Run webservice + * + * @param Mage_Api_Controller_Action $controller + * @return Mage_Api_Model_Server_Adapter_Soap + */ + public function run() + { + $apiConfigCharset = Mage::getStoreConfig("api/config/charset"); + + if ($this->getController()->getRequest()->getParam('wsdl') !== null) { + $wsdlConfig = Mage::getModel('api/wsdl_config'); + $wsdlConfig->setHandler($this->getHandler()) + ->init(); + $this->getController()->getResponse() + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + preg_replace( + '/(\>\<)/i', + ">\n<", + str_replace( + '', + "\n", + str_replace( + '', + "\n", + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $wsdlConfig->getWsdlContent() + ) + ) + ) + ) + ); + } else { + try { + $this->_instantiateServer(); + + $this->getController()->getResponse() + ->clearHeaders() + ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset) + ->setBody( + str_replace( + '', + "\n", + str_replace( + '', + "\n", + preg_replace( + '/<\?xml version="([^\"]+)"([^\>]+)>/i', + '', + $this->_soap->handle() + ) + ) + ) + ); + } catch( Zend_Soap_Server_Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } catch( Exception $e ) { + $this->fault( $e->getCode(), $e->getMessage() ); + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php b/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php new file mode 100644 index 00000000..b1b6711b --- /dev/null +++ b/app/code/core/Mage/Api/Model/Server/Wsi/Handler.php @@ -0,0 +1,179 @@ + + */ +class Mage_Api_Model_Server_WSI_Handler extends Mage_Api_Model_Server_Handler_Abstract +{ + protected $_resourceSuffix = '_v2'; + + /** + * Interceptor for all interfaces + * + * @param string $function + * @param array $args + */ + + public function __call ($function, $args) + { + $args = $args[0]; + + /** @var Mage_Api_Helper_Data */ + $helper = Mage::helper('api/data'); + + $helper->wsiArrayUnpacker($args); + $args = get_object_vars($args); + + if(isset($args['sessionId'])){ + $sessionId = $args['sessionId']; + unset($args['sessionId']); + } else { + // Was left for backward compatibility. + $sessionId = array_shift( $args ); + } + + $apiKey = ''; + $nodes = Mage::getSingleton('api/config')->getNode('v2/resources_function_prefix')->children(); + foreach ($nodes as $resource => $prefix) { + $prefix = $prefix->asArray(); + if (false !== strpos($function, $prefix)) { + $method = substr($function, strlen($prefix)); + $apiKey = $resource . '.' . strtolower($method[0]).substr($method, 1); + } + } + + list($modelName, $methodName) = $this->_getResourceName($apiKey); + $methodParams = $this->getMethodParams($modelName, $methodName); + + $args = $this->prepareArgs($methodParams, $args); + + $res = $this->call($sessionId, $apiKey, $args); + + $obj = $helper->wsiArrayPacker($res); + $stdObj = new stdClass(); + $stdObj->result = $obj; + + return $stdObj; + } + + /** + * Login user and Retrieve session id + * + * @param string $username + * @param string $apiKey + * @return string + */ + public function login($username, $apiKey = null) + { + if (is_object($username)) { + $apiKey = $username->apiKey; + $username = $username->username; + } + + $stdObject = new stdClass(); + $stdObject->result = parent::login($username, $apiKey); + return $stdObject; + } + + /** + * Return called class and method names + * + * @param String $apiPath + * @return Array + */ + protected function _getResourceName($apiPath){ + + list($resourceName, $methodName) = explode('.', $apiPath); + + if (empty($resourceName) || empty($methodName)) { + return $this->_fault('resource_path_invalid'); + } + + $resourcesAlias = $this->_getConfig()->getResourcesAlias(); + $resources = $this->_getConfig()->getResources(); + if (isset($resourcesAlias->$resourceName)) { + $resourceName = (string) $resourcesAlias->$resourceName; + } + + $methodInfo = $resources->$resourceName->methods->$methodName; + + $modelName = $this->_prepareResourceModelName((string) $resources->$resourceName->model); + + $modelClass = Mage::getConfig()->getModelClassName($modelName); + + $method = (isset($methodInfo->method) ? (string) $methodInfo->method : $methodName); + + return array($modelClass, $method); + } + + /** + * Return an array of parameters for the callable method. + * + * @param String $modelName + * @param String $methodName + * @return Array of ReflectionParameter + */ + public function getMethodParams($modelName, $methodName) { + + $method = new ReflectionMethod($modelName, $methodName); + + return $method->getParameters(); + } + + /** + * Prepares arguments for the method calling. Sort in correct order, set default values for omitted parameters. + * + * @param Array $params + * @param Array $args + * @return Array + */ + public function prepareArgs($params, $args) { + + $callArgs = array(); + + /** @var $parameter ReflectionParameter */ + foreach($params AS $parameter){ + $pName = $parameter->getName(); + if( isset( $args[$pName] ) ){ + $callArgs[$pName] = $args[$pName]; + } else { + if($parameter->isOptional()){ + $callArgs[$pName] = $parameter->getDefaultValue(); + } else { + Mage::logException(new Exception("Required parameter \"$pName\" is missing.", 0)); + $this->_fault('invalid_request_param'); + } + } + } + return $callArgs; + } + +} diff --git a/app/code/core/Mage/Api/Model/Session.php b/app/code/core/Mage/Api/Model/Session.php index f0673db2..57f0c51d 100644 --- a/app/code/core/Mage/Api/Model/Session.php +++ b/app/code/core/Mage/Api/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -39,7 +39,7 @@ class Mage_Api_Model_Session extends Mage_Core_Model_Session_Abstract public function start($sessionName=null) { // parent::start($sessionName=null); - $this->_currentSessId = md5(time() . $sessionName); + $this->_currentSessId = md5(time() . uniqid('', true) . $sessionName); $this->sessionIds[] = $this->getSessionId(); return $this; } @@ -83,10 +83,6 @@ public function clear() { public function login($username, $apiKey) { - if (empty($username) || empty($apiKey)) { - return; - } - $user = Mage::getModel('api/user') ->setSessid($this->getSessionId()) ->login($username, $apiKey); @@ -157,7 +153,7 @@ public function isAllowed($resource, $privilege=null) /** * Check session expiration * - * @return boolean + * @return boolean */ public function isSessionExpired ($user) { @@ -187,7 +183,7 @@ public function isLoggedIn($sessId = false) * Renew user by session ID if session not expired * * @param string $sessId - * @return boolean + * @return boolean */ protected function _renewBySessId ($sessId) { diff --git a/app/code/core/Mage/Api/Model/User.php b/app/code/core/Mage/Api/Model/User.php index bf332a54..acd296e4 100644 --- a/app/code/core/Mage/Api/Model/User.php +++ b/app/code/core/Mage/Api/Model/User.php @@ -20,12 +20,49 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Api_Model_Resource_User _getResource() + * @method Mage_Api_Model_Resource_User getResource() + * @method string getFirstname() + * @method Mage_Api_Model_User setFirstname(string $value) + * @method string getLastname() + * @method Mage_Api_Model_User setLastname(string $value) + * @method string getEmail() + * @method Mage_Api_Model_User setEmail(string $value) + * @method string getUsername() + * @method Mage_Api_Model_User setUsername(string $value) + * @method string getApiKey() + * @method Mage_Api_Model_User setApiKey(string $value) + * @method string getCreated() + * @method Mage_Api_Model_User setCreated(string $value) + * @method string getModified() + * @method Mage_Api_Model_User setModified(string $value) + * @method int getLognum() + * @method Mage_Api_Model_User setLognum(int $value) + * @method int getReloadAclFlag() + * @method Mage_Api_Model_User setReloadAclFlag(int $value) + * @method int getIsActive() + * @method Mage_Api_Model_User setIsActive(int $value) + * + * @category Mage + * @package Mage_Api + * @author Magento Core Team + */ class Mage_Api_Model_User extends Mage_Core_Model_Abstract { + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'api_user'; + protected function _construct() { $this->_init('api/user'); @@ -111,7 +148,7 @@ public function userExists() } public function getCollection() { - return Mage::getResourceModel('admin/user_collection'); + return Mage::getResourceModel('api/user_collection'); } public function getName($separator=' ') diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config.php b/app/code/core/Mage/Api/Model/Wsdl/Config.php index cc3b3c78..3db7bbf3 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -103,15 +103,27 @@ public function init() $mergeWsdl = new Mage_Api_Model_Wsdl_Config_Base(); $mergeWsdl->setHandler($this->getHandler()); + if(Mage::helper('api/data')->isComplianceWSI()){ /** * Exclude Mage_Api wsdl xml file because it used for previous version * of API wsdl declaration */ - $mergeWsdl->addLoadedFile(Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsdl.xml'); - - $baseWsdlFile = Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsdl2.xml'; - $this->loadFile($baseWsdlFile); - Mage::getConfig()->loadModulesConfiguration('wsdl.xml', $this, $mergeWsdl); + $mergeWsdl->addLoadedFile(Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsi.xml'); + + $baseWsdlFile = Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsi.xml'; + $this->loadFile($baseWsdlFile); + Mage::getConfig()->loadModulesConfiguration('wsi.xml', $this, $mergeWsdl); + } else { + /** + * Exclude Mage_Api wsdl xml file because it used for previous version + * of API wsdl declaration + */ + $mergeWsdl->addLoadedFile(Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsdl.xml'); + + $baseWsdlFile = Mage::getConfig()->getModuleDir('etc', "Mage_Api").DS.'wsdl2.xml'; + $this->loadFile($baseWsdlFile); + Mage::getConfig()->loadModulesConfiguration('wsdl.xml', $this, $mergeWsdl); + } if (Mage::app()->useCache('config')) { $this->saveCache(array('config')); diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php b/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php index b4650ac9..6e6540f1 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config/Base.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,6 +45,18 @@ class Mage_Api_Model_Wsdl_Config_Base extends Varien_Simplexml_Config public function __construct($sourceData=null) { $this->_elementClass = 'Mage_Api_Model_Wsdl_Config_Element'; + + // remove wsdl parameter from query + $queryParams = Mage::app()->getRequest()->getQuery(); + unset($queryParams['wsdl']); + + // set up default WSDL template variables + $this->_wsdlVariables = new Varien_Object( + array( + 'name' => 'Magento', + 'url' => htmlspecialchars(Mage::getUrl('*/*/*', array('_query' => $queryParams))) + ) + ); parent::__construct($sourceData); } @@ -78,20 +90,14 @@ public function getHandler() */ public function processFileData($text) { + /** @var $template Mage_Core_Model_Email_Template_Filter */ $template = Mage::getModel('core/email_template_filter'); - if (null === $this->_wsdlVariables) { - $this->_wsdlVariables = new Varien_Object(); - $this->_wsdlVariables->setUrl(Mage::getUrl('*/*/*')); - $this->_wsdlVariables->setName('Magento'); - $this->_wsdlVariables->setHandler($this->getHandler()); - } + $this->_wsdlVariables->setHandler($this->getHandler()); $template->setVariables(array('wsdl'=>$this->_wsdlVariables)); - $text = $template->filter($text); - - return $text; + return $template->filter($text); } public function addLoadedFile($file) @@ -113,4 +119,18 @@ public function loadFile($file) } return $this; } + + /** + * Set variable to be used in WSDL template processing + * + * @param string $key Varible key + * @param string $value Variable value + * @return Mage_Api_Model_Wsdl_Config_Base + */ + public function setWsdlVariable($key, $value) + { + $this->_wsdlVariables->setData($key, $value); + + return $this; + } } diff --git a/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php b/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php index 3643eb75..3db75353 100644 --- a/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php +++ b/app/code/core/Mage/Api/Model/Wsdl/Config/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -173,8 +173,11 @@ public function getChildren($source) { $children = array(); $namespaces = $source->getNamespaces(true); + + $isWsi = Mage::helper('api/data')->isComplianceWSI(); + foreach ($namespaces as $key => $value) { - if ($key == '' || $key == 'wsdl') { + if ($key == '' || (!$isWsi && $key == 'wsdl')) { continue; } $children[$value] = $source->children($value); diff --git a/app/code/core/Mage/Api/controllers/IndexController.php b/app/code/core/Mage/Api/controllers/IndexController.php index f67be513..39753e59 100644 --- a/app/code/core/Mage/Api/controllers/IndexController.php +++ b/app/code/core/Mage/Api/controllers/IndexController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,10 +35,8 @@ class Mage_Api_IndexController extends Mage_Api_Controller_Action { public function indexAction() { - $server = Mage::getSingleton('api/server'); - /* @var $server Mage_Api_Model_Server */ - $this->_getServer()->init($this, 'soap') + $this->_getServer()->init($this) ->run(); } } // Class Mage_Api_IndexController End diff --git a/app/code/core/Mage/Api/controllers/SoapController.php b/app/code/core/Mage/Api/controllers/SoapController.php index 75a7d0c2..91f4ec7b 100644 --- a/app/code/core/Mage/Api/controllers/SoapController.php +++ b/app/code/core/Mage/Api/controllers/SoapController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,10 +35,8 @@ class Mage_Api_SoapController extends Mage_Api_Controller_Action { public function indexAction() { - $server = Mage::getSingleton('api/server'); - /* @var $server Mage_Api_Model_Server */ - $this->_getServer()->init($this) + $this->_getServer()->init($this, 'soap') ->run(); } } // Class Mage_Api_IndexController End diff --git a/app/code/core/Mage/Api/controllers/V2/SoapController.php b/app/code/core/Mage/Api/controllers/V2/SoapController.php index d7c99a67..e75b8fa6 100644 --- a/app/code/core/Mage/Api/controllers/V2/SoapController.php +++ b/app/code/core/Mage/Api/controllers/V2/SoapController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,10 +35,14 @@ class Mage_Api_V2_SoapController extends Mage_Api_Controller_Action { public function indexAction() { - $server = Mage::getSingleton('api/server'); + if(Mage::helper('api/data')->isComplianceWSI()){ + $handler_name = 'soap_wsi'; + } else { + $handler_name = 'soap_v2'; + } /* @var $server Mage_Api_Model_Server */ - $this->_getServer()->init($this, 'soap_v2', 'soap_v2') + $this->_getServer()->init($this, $handler_name, $handler_name) ->run(); } } // Class Mage_Api_IndexController End diff --git a/app/code/core/Mage/Api/controllers/XmlrpcController.php b/app/code/core/Mage/Api/controllers/XmlrpcController.php index fbc9668a..1573ed59 100644 --- a/app/code/core/Mage/Api/controllers/XmlrpcController.php +++ b/app/code/core/Mage/Api/controllers/XmlrpcController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/etc/adminhtml.xml b/app/code/core/Mage/Api/etc/adminhtml.xml index ce1b3e6f..e2a4909b 100644 --- a/app/code/core/Mage/Api/etc/adminhtml.xml +++ b/app/code/core/Mage/Api/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -37,11 +37,11 @@ 0 - Users + SOAP/XML-RPC - Users 10 - Roles + SOAP/XML-RPC - Roles 20 @@ -49,7 +49,7 @@ - Magento Core Api Section + Magento Core API Section @@ -67,12 +67,14 @@ 25 - Users + SOAP/XML-RPC - Users adminhtml/api_user + 10 - Roles + SOAP/XML-RPC - Roles adminhtml/api_role + 20 diff --git a/app/code/core/Mage/Api/etc/api.xml b/app/code/core/Mage/Api/etc/api.xml index 30d691a4..84aa2616 100644 --- a/app/code/core/Mage/Api/etc/api.xml +++ b/app/code/core/Mage/Api/etc/api.xml @@ -21,12 +21,18 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> + + + Mage_Api_Helper_Data + getV2AdapterCode + + api/server_adapter_soap @@ -48,6 +54,16 @@ + + api/server_wsi_adapter_soap + soap_wsi + 1 + + + + + + api/server_adapter_xmlrpc default @@ -64,6 +80,9 @@ api/server_v2_handler + + api/server_wsi_handler + @@ -101,6 +120,10 @@ 5 Session expired. Try to relogin. + + 6 + Required parameter is missing, for more details see "exception.log". + @@ -122,5 +145,148 @@ + + + + + + + Request is executed. + + + + Request is executed. Created new resource. + + + + Request is carried out. + + + + Parameter "%s" is not valid. + + + + + API version "%s" not found. + + + + You must provide an authenticated user for this method. + Token in request is not valid. + + + + Invalid API key + + + + Requested item %s not found. + Requested resource %s not found. + + + + Method "%s" is not allowed. + + + + Api version is required. + + + + + API version "%s" is deprecated. + + Resource "%s" is deprecated. + + + + + + There was unknown error while processing your request. + + There was internal error while processing your request. + + Server has internal error. %s: %s + + + + This resource is not implemented so far. + This method is not implemented so far. + + + + + + + + + + 200 + notification + + + + 201 + notification + + + + 202 + notification + + + + + 400 + error + + + + 400 + error + + + + 401 + error + + + + 403 + error + + + + 404 + error + + + + 405 + error + + + + 406 + error + + + + 410 + error + + + + 500 + error + + + + 501 + error + + + diff --git a/app/code/core/Mage/Api/etc/config.xml b/app/code/core/Mage/Api/etc/config.xml index a8226412..eceac462 100644 --- a/app/code/core/Mage/Api/etc/config.xml +++ b/app/code/core/Mage/Api/etc/config.xml @@ -21,20 +21,20 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.8.1 + 1.6.0.0 - + @@ -49,10 +49,11 @@ Mage_Api_Model - api_mysql4 + api_resource - - Mage_Api_Model_Mysql4 + + Mage_Api_Model_Resource + api_mysql4
    '.$element->getLabelHtml().''; + + $html .= 'serialize($htmlAttributes).'/>'; + $html .= 'serialize($htmlAttributes) . "/>" . "\n"; + $html .= ''; + $html .= '
    api_assert
    @@ -70,7 +71,7 @@ api_session
    - + @@ -119,7 +120,10 @@ + UTF-8 3600 + 0 + 0 diff --git a/app/code/core/Mage/Api/etc/system.xml b/app/code/core/Mage/Api/etc/system.xml index c85f89a1..0e1a2e80 100644 --- a/app/code/core/Mage/Api/etc/system.xml +++ b/app/code/core/Mage/Api/etc/system.xml @@ -21,14 +21,14 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - + service text 101 @@ -44,14 +44,42 @@ 1 1 + + + text + 10 + 1 + 1 + 1 + text - 1 + 20 1 1 1 + + + select + adminhtml/system_config_source_yesno + adminhtml/system_config_backend_store + 30 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + adminhtml/system_config_backend_store + 40 + 1 + 1 + 1 + diff --git a/app/code/core/Mage/Api/etc/wsdl.xml b/app/code/core/Mage/Api/etc/wsdl.xml index 99229a33..6b24d0d9 100644 --- a/app/code/core/Mage/Api/etc/wsdl.xml +++ b/app/code/core/Mage/Api/etc/wsdl.xml @@ -4,7 +4,7 @@ name="{{var wsdl.name}}" targetNamespace="urn:{{var wsdl.name}}"> - + diff --git a/app/code/core/Mage/Api/etc/wsdl2.xml b/app/code/core/Mage/Api/etc/wsdl2.xml index 8d2db7c8..117cbcae 100644 --- a/app/code/core/Mage/Api/etc/wsdl2.xml +++ b/app/code/core/Mage/Api/etc/wsdl2.xml @@ -18,6 +18,25 @@ + + + + + + + + + + + + + + + + + + + @@ -34,7 +53,7 @@ - + diff --git a/app/code/core/Mage/Api/etc/wsi.xml b/app/code/core/Mage/Api/etc/wsi.xml new file mode 100644 index 00000000..9cac1358 --- /dev/null +++ b/app/code/core/Mage/Api/etc/wsi.xml @@ -0,0 +1,381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Call api functionality + + + + + Multiple calls of resource functionality + + + + + End web service session + + + + + Login user and retrive session id + + + + + Start web service session + + + + + List of available resources + + + + + List of global faults + + + + + List of resource faults + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/core/Mage/Api/sql/api_setup/install-1.6.0.0.php b/app/code/core/Mage/Api/sql/api_setup/install-1.6.0.0.php new file mode 100644 index 00000000..eed4483e --- /dev/null +++ b/app/code/core/Mage/Api/sql/api_setup/install-1.6.0.0.php @@ -0,0 +1,208 @@ + + */ +$installer = $this; +/* @var $installer Mage_Core_Model_Resource_Setup */ + +$installer->startSetup(); + +/** + * Create table 'api/assert' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('api/assert')) + ->addColumn('assert_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Assert id') + ->addColumn('assert_type', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + ), 'Assert type') + ->addColumn('assert_data', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Assert additional data') + ->setComment('Api ACL Asserts'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'api/role' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('api/role')) + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Role id') + ->addColumn('parent_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Parent role id') + ->addColumn('tree_level', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Role level in tree') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Sort order to display on admin area') + ->addColumn('role_type', Varien_Db_Ddl_Table::TYPE_TEXT, 1, array( + 'nullable' => false, + 'default' => '0', + ), 'Role type') + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'User id') + ->addColumn('role_name', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + ), 'Role name') + ->addIndex($installer->getIdxName('api/role', array('parent_id', 'sort_order')), + array('parent_id', 'sort_order')) + ->addIndex($installer->getIdxName('api/role', array('tree_level')), + array('tree_level')) + ->setComment('Api ACL Roles'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'api/rule' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('api/rule')) + ->addColumn('rule_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Api rule Id') + ->addColumn('role_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Api role Id') + ->addColumn('resource_id', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Module code') + ->addColumn('api_privileges', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + ), 'Privileges') + ->addColumn('assert_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Assert id') + ->addColumn('role_type', Varien_Db_Ddl_Table::TYPE_TEXT, 1, array( + ), 'Role type') + ->addColumn('api_permission', Varien_Db_Ddl_Table::TYPE_TEXT, 10, array( + ), 'Permission') + ->addIndex($installer->getIdxName('api/rule', array('resource_id', 'role_id')), + array('resource_id', 'role_id')) + ->addIndex($installer->getIdxName('api/rule', array('role_id', 'resource_id')), + array('role_id', 'resource_id')) + ->addForeignKey($installer->getFkName('api/rule', 'role_id', 'api/role', 'role_id'), + 'role_id', $installer->getTable('api/role'), 'role_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Api ACL Rules'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'api/user' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('api/user')) + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'User id') + ->addColumn('firstname', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'First name') + ->addColumn('lastname', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'Last name') + ->addColumn('email', Varien_Db_Ddl_Table::TYPE_TEXT, 128, array( + ), 'Email') + ->addColumn('username', Varien_Db_Ddl_Table::TYPE_TEXT, 40, array( + ), 'Nickname') + ->addColumn('api_key', Varien_Db_Ddl_Table::TYPE_TEXT, 40, array( + ), 'Api key') + ->addColumn('created', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'User record create date') + ->addColumn('modified', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'User record modify date') + ->addColumn('lognum', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Quantity of log ins') + ->addColumn('reload_acl_flag', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Refresh ACL flag') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '1', + ), 'Account status') + ->setComment('Api Users'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'api/session' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('api/session')) + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'User id') + ->addColumn('logdate', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Login date') + ->addColumn('sessid', Varien_Db_Ddl_Table::TYPE_TEXT, 40, array( + ), 'Sessioin id') + ->addIndex($installer->getIdxName('api/session', array('user_id')), + array('user_id')) + ->addIndex($installer->getIdxName('api/session', array('sessid')), + array('sessid')) + ->addForeignKey($installer->getFkName('api/session', 'user_id', 'api/user', 'user_id'), + 'user_id', $installer->getTable('api/user'), 'user_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Api Sessions'); +$installer->getConnection()->createTable($table); + + + +$installer->endSetup(); diff --git a/app/code/core/Mage/Api/sql/api_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Api/sql/api_setup/mysql4-install-0.7.0.php index ced3b85f..95c14b21 100644 --- a/app/code/core/Mage/Api/sql/api_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Api/sql/api_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.7.0-0.7.1.php index 978bf2bc..08eb72fc 100644 --- a/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.8.0-0.8.1.php b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.8.0-0.8.1.php index 0d92d18a..e83ccbb6 100644 --- a/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.8.0-0.8.1.php +++ b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-0.8.0-0.8.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Api - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..1081ffb9 --- /dev/null +++ b/app/code/core/Mage/Api/sql/api_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,375 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('api/rule'), + 'FK_API_RULE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('api/session'), + 'FK_API_SESSION_USER' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('api/role'), + 'PARENT_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('api/role'), + 'TREE_LEVEL' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('api/rule'), + 'RESOURCE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('api/rule'), + 'ROLE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('api/session'), + 'API_SESSION_USER' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('api/session'), + 'API_SESSION_SESSID' +); + + +/* + * Change columns + */ +$tables = array( + $installer->getTable('api/assert') => array( + 'columns' => array( + 'assert_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Assert id' + ), + 'assert_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'comment' => 'Assert type' + ), + 'assert_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Assert additional data' + ) + ), + 'comment' => 'Api ACL Asserts' + ), + $installer->getTable('api/role') => array( + 'columns' => array( + 'role_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Role id' + ), + 'parent_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Parent role id' + ), + 'tree_level' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role level in tree' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort order to display on admin area' + ), + 'role_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 1, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Role type' + ), + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'User id' + ), + 'role_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'comment' => 'Role name' + ) + ), + 'comment' => 'Api ACL Roles' + ), + $installer->getTable('api/rule') => array( + 'columns' => array( + 'rule_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Api rule Id' + ), + 'role_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Api role Id' + ), + 'resource_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Module code' + ), + 'assert_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Assert id' + ), + 'role_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 1, + 'comment' => 'Role type' + ) + ), + 'comment' => 'Api ACL Rules' + ), + $installer->getTable('api/user') => array( + 'columns' => array( + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'User id' + ), + 'firstname' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'First name' + ), + 'lastname' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'Last name' + ), + 'email' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 128, + 'comment' => 'Email' + ), + 'username' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 40, + 'comment' => 'Nickname' + ), + 'api_key' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 40, + 'comment' => 'Api key' + ), + 'created' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'User record create date' + ), + 'modified' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'User record modify date' + ), + 'lognum' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Quantity of log ins' + ), + 'reload_acl_flag' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Refresh ACL flag' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '1', + 'comment' => 'Account status' + ) + ), + 'comment' => 'Api Users' + ), + $installer->getTable('api/session') => array( + 'columns' => array( + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'User id' + ), + 'logdate' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Login date' + ), + 'sessid' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 40, + 'comment' => 'Sessioin id' + ) + ), + 'comment' => 'Api Sessions' + ) +); + +$installer->getConnection()->modifyTables($tables); + +$installer->getConnection()->changeColumn( + $installer->getTable('api/rule'), + 'privileges', + 'api_privileges', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'comment' => 'Privileges' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('api/rule'), + 'permission', + 'api_permission', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 10, + 'comment' => 'Permission' + ) +); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('api/rule'), + $installer->getIdxName('api/rule', array('resource_id', 'role_id')), + array('resource_id', 'role_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +$installer->getConnection()->addIndex( + $installer->getTable('api/rule'), + $installer->getIdxName('api/rule', array('role_id', 'resource_id')), + array('role_id', 'resource_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +$installer->getConnection()->addIndex( + $installer->getTable('api/session'), + $installer->getIdxName('api/session', array('user_id')), + array('user_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +$installer->getConnection()->addIndex( + $installer->getTable('api/session'), + $installer->getIdxName('api/session', array('sessid')), + array('sessid'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +$installer->getConnection()->addIndex( + $installer->getTable('api/role'), + $installer->getIdxName('api/role', array('parent_id', 'sort_order')), + array('parent_id', 'sort_order'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +$installer->getConnection()->addIndex( + $installer->getTable('api/role'), + $installer->getIdxName('api/role', array('tree_level')), + array('tree_level'), + Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX +); + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('api/rule', 'role_id', 'api/role', 'role_id'), + $installer->getTable('api/rule'), + 'role_id', + $installer->getTable('api/role'), + 'role_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('api/session', 'user_id', 'api/user', 'user_id'), + $installer->getTable('api/session'), + 'user_id', + $installer->getTable('api/user'), + 'user_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Backup/Exception.php b/app/code/core/Mage/Backup/Exception.php index eeb6c262..cbdfdc8a 100644 --- a/app/code/core/Mage/Backup/Exception.php +++ b/app/code/core/Mage/Backup/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Backup/Helper/Data.php b/app/code/core/Mage/Backup/Helper/Data.php index 6d00629c..41c96cae 100644 --- a/app/code/core/Mage/Backup/Helper/Data.php +++ b/app/code/core/Mage/Backup/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,5 +29,295 @@ */ class Mage_Backup_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Backup type constant for database backup + */ + const TYPE_DB = 'db'; + /** + * Backup type constant for filesystem backup + */ + const TYPE_FILESYSTEM = 'filesystem'; + + /** + * Backup type constant for full system backup(database + filesystem) + */ + const TYPE_SYSTEM_SNAPSHOT = 'snapshot'; + + /** + * Backup type constant for media and database backup + */ + const TYPE_MEDIA = 'media'; + + /** + * Backup type constant for full system backup excluding media folder + */ + const TYPE_SNAPSHOT_WITHOUT_MEDIA = 'nomedia'; + + /** + * Get all possible backup type values with descriptive title + * + * @return array + */ + public function getBackupTypes() + { + return array( + self::TYPE_DB => self::__('Database'), + self::TYPE_MEDIA => self::__('Database and Media'), + self::TYPE_SYSTEM_SNAPSHOT => self::__('System'), + self::TYPE_SNAPSHOT_WITHOUT_MEDIA => self::__('System (excluding Media)') + ); + } + + /** + * Get all possible backup type values + * + * @return array + */ + public function getBackupTypesList() + { + return array( + self::TYPE_DB, + self::TYPE_SYSTEM_SNAPSHOT, + self::TYPE_SNAPSHOT_WITHOUT_MEDIA, + self::TYPE_MEDIA + ); + } + + /** + * Get default backup type value + * + * @return string + */ + public function getDefaultBackupType() + { + return self::TYPE_DB; + } + + /** + * Get directory path where backups stored + * + * @return string + */ + public function getBackupsDir() + { + return Mage::getBaseDir('var') . DS . 'backups'; + } + + /** + * Get backup file extension by backup type + * + * @param string $type + * @return string + */ + public function getExtensionByType($type) + { + $extensions = $this->getExtensions(); + return isset($extensions[$type]) ? $extensions[$type] : ''; + } + + /** + * Get all types to extensions map + * + * @return array + */ + public function getExtensions() + { + return array( + self::TYPE_SYSTEM_SNAPSHOT => 'tgz', + self::TYPE_SNAPSHOT_WITHOUT_MEDIA => 'tgz', + self::TYPE_MEDIA => 'tgz', + self::TYPE_DB => 'gz' + ); + } + + /** + * Generate backup download name + * + * @param Mage_Backup_Model_Backup $backup + * @return string + */ + public function generateBackupDownloadName(Mage_Backup_Model_Backup $backup) + { + $additionalExtension = $backup->getType() == self::TYPE_DB ? '.sql' : ''; + return $backup->getType() . '-' . date('YmdHis', $backup->getTime()) . $additionalExtension . '.' + . $this->getExtensionByType($backup->getType()); + } + + /** + * Check Permission for Rollback + * + * @return boolean + */ + public function isRollbackAllowed(){ + return Mage::getSingleton('admin/session')->isAllowed('system/tools/backup/rollback' ); + } + + /** + * Get paths that should be ignored when creating system snapshots + * + * @return array + */ + public function getBackupIgnorePaths() + { + return array( + '.svn', + 'maintenance.flag', + Mage::getBaseDir('var') . DS . 'session', + Mage::getBaseDir('var') . DS . 'cache', + Mage::getBaseDir('var') . DS . 'full_page_cache', + Mage::getBaseDir('var') . DS . 'locks', + Mage::getBaseDir('var') . DS . 'log', + Mage::getBaseDir('var') . DS . 'report' + ); + } + + /** + * Get paths that should be ignored when rolling back system snapshots + * + * @return array + */ + public function getRollbackIgnorePaths() + { + return array( + '.svn', + 'maintenance.flag', + Mage::getBaseDir('var') . DS . 'session', + Mage::getBaseDir('var') . DS . 'locks', + Mage::getBaseDir('var') . DS . 'log', + Mage::getBaseDir('var') . DS . 'report', + Mage::getBaseDir('app') . DS . 'Mage.php', + Mage::getBaseDir() . DS . 'errors', + Mage::getBaseDir() . DS . 'index.php' + ); + } + + /** + * Put store into maintenance mode + * + * @return bool + */ + public function turnOnMaintenanceMode() + { + $maintenanceFlagFile = $this->getMaintenanceFlagFilePath(); + $result = file_put_contents($maintenanceFlagFile, 'maintenance'); + + return $result !== false; + } + + /** + * Turn off store maintenance mode + */ + public function turnOffMaintenanceMode() + { + $maintenanceFlagFile = $this->getMaintenanceFlagFilePath(); + @unlink($maintenanceFlagFile); + } + + /** + * Get backup create success message by backup type + * + * @param string $type + * @return string + */ + public function getCreateSuccessMessageByType($type) + { + $messagesMap = array( + self::TYPE_SYSTEM_SNAPSHOT => $this->__('The system backup has been created.'), + self::TYPE_SNAPSHOT_WITHOUT_MEDIA => $this->__('The system (excluding Media) backup has been created.'), + self::TYPE_MEDIA => $this->__('The database and media backup has been created.'), + self::TYPE_DB => $this->__('The database backup has been created.') + ); + + if (!isset($messagesMap[$type])) { + return; + } + + return $messagesMap[$type]; + } + + /** + * Get path to maintenance flag file + * + * @return string + */ + protected function getMaintenanceFlagFilePath() + { + return Mage::getBaseDir() . DS . 'maintenance.flag'; + } + + /** + * Invalidate Cache + * @return Mage_Backup_Helper_Data + */ + public function invalidateCache() + { + if ($cacheTypesNode = Mage::getConfig()->getNode(Mage_Core_Model_Cache::XML_PATH_TYPES)) { + $cacheTypesList = array_keys($cacheTypesNode->asArray()); + Mage::app()->getCacheInstance()->invalidateType($cacheTypesList); + } + return $this; + } + + /** + * Invalidate Indexer + * + * @return Mage_Backup_Helper_Data + */ + public function invalidateIndexer() + { + foreach (Mage::getResourceModel('index/process_collection') as $process){ + $process->changeStatus(Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX); + } + return $this; + } + + /** + * Creates backup's display name from it's name + * + * @param string $name + * @return string + */ + public function nameToDisplayName($name) + { + return str_replace('_', ' ', $name); + } + + /** + * Extracts information from backup's filename + * + * @param string $filename + * @return Varien_Object + */ + public function extractDataFromFilename($filename) + { + $extensions = $this->getExtensions(); + + $filenameWithoutExtension = $filename; + + foreach ($extensions as $extension) { + $filenameWithoutExtension = preg_replace('/' . preg_quote($extension, '/') . '$/', '', + $filenameWithoutExtension + ); + } + + $filenameWithoutExtension = substr($filenameWithoutExtension, 0, strrpos($filenameWithoutExtension, ".")); + + list($time, $type) = explode("_", $filenameWithoutExtension); + + $name = str_replace($time . '_' . $type, '', $filenameWithoutExtension); + + if (!empty($name)) { + $name = substr($name, 1); + } + + $result = new Varien_Object(); + $result->addData(array( + 'name' => $name, + 'type' => $type, + 'time' => $time + )); + + return $result; + } } diff --git a/app/code/core/Mage/Backup/Model/Backup.php b/app/code/core/Mage/Backup/Model/Backup.php index 0d2591e1..17ecaa3a 100644 --- a/app/code/core/Mage/Backup/Model/Backup.php +++ b/app/code/core/Mage/Backup/Model/Backup.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,23 +29,17 @@ * * @category Mage * @package Mage_Backup - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Backup_Model_Backup extends Varien_Object { - /* backup types */ - const BACKUP_DB = 'db'; - const BACKUP_VIEW = 'view'; - const BACKUP_MEDIA = 'media'; - /* internal constants */ - const BACKUP_EXTENSION = 'gz'; const COMPRESS_RATE = 9; /** * Type of backup file * - * @var string db|media|view + * @var string */ private $_type = 'db'; @@ -65,14 +59,19 @@ class Mage_Backup_Model_Backup extends Varien_Object */ public function load($fileName, $filePath) { - list ($time, $type) = explode("_", substr($fileName, 0, strrpos($fileName, "."))); + $backupData = Mage::helper('backup')->extractDataFromFilename($fileName); + $this->addData(array( 'id' => $filePath . DS . $fileName, - 'time' => (int)$time, + 'time' => (int)$backupData->getTime(), 'path' => $filePath, - 'date_object' => new Zend_Date((int)$time) + 'extension' => Mage::helper('backup')->getExtensionByType($backupData->getType()), + 'display_name' => Mage::helper('backup')->nameToDisplayName($backupData->getName()), + 'name' => $backupData->getName(), + 'date_object' => new Zend_Date((int)$backupData->getTime(), Mage::app()->getLocale()->getLocaleCode()) )); - $this->setType($type); + + $this->setType($backupData->getType()); return $this; } @@ -93,19 +92,29 @@ public function exists() */ public function getFileName() { - return $this->getTime() . "_" . $this->getType() - . "." . self::BACKUP_EXTENSION; + $filename = $this->getTime() . "_" . $this->getType(); + $backupName = $this->getName(); + + if (!empty($backupName)) { + $filename .= '_' . $backupName; + } + + $filename .= '.' . Mage::helper('backup')->getExtensionByType($this->getType()); + + return $filename; } /** * Sets type of file * - * @param string $value db|media|view + * @param string $value + * @return Mage_Backup_Model_Backup */ public function setType($value='db') { - if(!in_array($value, array('db','media','view'))) { - $value = 'db'; + $possibleTypes = Mage::helper('backup')->getBackupTypesList(); + if(!in_array($value, $possibleTypes)) { + $value = Mage::helper('backup')->getDefaultBackupType(); } $this->_type = $value; @@ -117,7 +126,7 @@ public function setType($value='db') /** * Returns type of backup file * - * @return string db|media|view + * @return string */ public function getType() { @@ -205,6 +214,7 @@ public function &getFile() * Delete backup file * * @throws Mage_Backup_Exception + * @return Mage_Backup_Model_Backup */ public function deleteFile() { @@ -249,11 +259,12 @@ public function open($write = false) $mode = $write ? 'wb' . self::COMPRESS_RATE : 'rb'; - try { - $this->_handler = gzopen($filePath, $mode); - } - catch (Exception $e) { - Mage::exception('Mage_Backup', Mage::helper('backup')->__('Backup file "%s" cannot be read from or written to.', $this->getFileName())); + $this->_handler = @gzopen($filePath, $mode); + + if (!$this->_handler) { + throw new Mage_Backup_Exception_NotEnoughPermissions( + Mage::helper('backup')->__('Backup file "%s" cannot be read from or written to.', $this->getFileName()) + ); } return $this; @@ -351,4 +362,41 @@ public function getSize() return 0; } + + /** + * Validate user password + * + * @param string $password + * @return bool + */ + public function validateUserPassword($password) + { + $userPasswordHash = Mage::getModel('admin/session')->getUser()->getPassword(); + return Mage::helper('core')->validateHash($password, $userPasswordHash); + } + + /** + * Load backup by it's type and creation timestamp + * + * @param int $timestamp + * @param string $type + * @return Mage_Backup_Model_Backup + */ + public function loadByTimeAndType($timestamp, $type) + { + $backupsCollection = Mage::getSingleton('backup/fs_collection'); + $backupId = $timestamp . '_' . $type; + + foreach ($backupsCollection as $backup) { + if ($backup->getId() == $backupId) { + $this->setType($backup->getType()) + ->setTime($backup->getTime()) + ->setName($backup->getName()) + ->setPath($backup->getPath()); + break; + } + } + + return $this; + } } diff --git a/app/code/core/Mage/Backup/Model/Config/Backend/Cron.php b/app/code/core/Mage/Backup/Model/Config/Backend/Cron.php new file mode 100644 index 00000000..622ffd4c --- /dev/null +++ b/app/code/core/Mage/Backup/Model/Config/Backend/Cron.php @@ -0,0 +1,89 @@ + + */ +class Mage_Backup_Model_Config_Backend_Cron extends Mage_Core_Model_Config_Data +{ + const CRON_STRING_PATH = 'crontab/jobs/system_backup/schedule/cron_expr'; + const CRON_MODEL_PATH = 'crontab/jobs/system_backup/run/model'; + + const XML_PATH_BACKUP_ENABLED = 'groups/backup/fields/enabled/value'; + const XML_PATH_BACKUP_TIME = 'groups/backup/fields/time/value'; + const XML_PATH_BACKUP_FREQUENCY = 'groups/backup/fields/frequency/value'; + + /** + * Cron settings after save + * + * @return Mage_Adminhtml_Model_System_Config_Backend_Log_Cron + */ + protected function _afterSave() + { + $enabled = $this->getData(self::XML_PATH_BACKUP_ENABLED); + $time = $this->getData(self::XML_PATH_BACKUP_TIME); + $frequency = $this->getData(self::XML_PATH_BACKUP_FREQUENCY); + + $frequencyWeekly = Mage_Adminhtml_Model_System_Config_Source_Cron_Frequency::CRON_WEEKLY; + $frequencyMonthly = Mage_Adminhtml_Model_System_Config_Source_Cron_Frequency::CRON_MONTHLY; + + if ($enabled) { + $cronExprArray = array( + intval($time[1]), # Minute + intval($time[0]), # Hour + ($frequency == $frequencyMonthly) ? '1' : '*', # Day of the Month + '*', # Month of the Year + ($frequency == $frequencyWeekly) ? '1' : '*', # Day of the Week + ); + $cronExprString = join(' ', $cronExprArray); + } + else { + $cronExprString = ''; + } + + try { + Mage::getModel('core/config_data') + ->load(self::CRON_STRING_PATH, 'path') + ->setValue($cronExprString) + ->setPath(self::CRON_STRING_PATH) + ->save(); + + Mage::getModel('core/config_data') + ->load(self::CRON_MODEL_PATH, 'path') + ->setValue((string) Mage::getConfig()->getNode(self::CRON_MODEL_PATH)) + ->setPath(self::CRON_MODEL_PATH) + ->save(); + } + catch (Exception $e) { + Mage::throwException(Mage::helper('backup')->__('Unable to save the cron expression.')); + } + } +} diff --git a/app/code/core/Mage/Backup/Model/Config/Source/Type.php b/app/code/core/Mage/Backup/Model/Config/Source/Type.php new file mode 100644 index 00000000..865d9c61 --- /dev/null +++ b/app/code/core/Mage/Backup/Model/Config/Source/Type.php @@ -0,0 +1,52 @@ + + */ +class Mage_Backup_Model_Config_Source_Type +{ + /** + * return possible options + * + * @return array + */ + public function toOptionArray() + { + $backupTypes = array(); + foreach(Mage::helper('backup')->getBackupTypes() as $type => $label) { + $backupTypes[] = array( + 'label' => $label, + 'value' => $type, + ); + } + return $backupTypes; + } +} diff --git a/app/code/core/Mage/Backup/Model/Db.php b/app/code/core/Mage/Backup/Model/Db.php index 60612715..22e11e59 100644 --- a/app/code/core/Mage/Backup/Model/Db.php +++ b/app/code/core/Mage/Backup/Model/Db.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -37,10 +37,19 @@ class Mage_Backup_Model_Db /** * Buffer length for multi rows - * default 512 Kb + * default 100 Kb * */ - const BUFFER_LENGTH = 524288; + const BUFFER_LENGTH = 102400; + + /** + * List of tables which data should not be backed up + * + * @var array + */ + protected $_ignoreDataTablesList = array( + 'importexport/importdata' + ); /** * Retrieve resource model @@ -108,13 +117,16 @@ public function createBackup(Mage_Backup_Model_Backup $backup) $backup->write($this->getResource()->getHeader()); + $ignoreDataTablesList = $this->getIgnoreDataTablesList(); + foreach ($tables as $table) { - $backup->write($this->getResource()->getTableHeader($table) . $this->getResource()->getTableDropSql($table) . "\n"); + $backup->write($this->getResource()->getTableHeader($table) + . $this->getResource()->getTableDropSql($table) . "\n"); $backup->write($this->getResource()->getTableCreateSql($table, false) . "\n"); $tableStatus = $this->getResource()->getTableStatus($table); - if ($tableStatus->getRows()) { + if ($tableStatus->getRows() && !in_array($table, $ignoreDataTablesList)) { $backup->write($this->getResource()->getTableDataBeforeSql($table)); if ($tableStatus->getDataLength() > self::BUFFER_LENGTH) { @@ -149,4 +161,20 @@ public function createBackup(Mage_Backup_Model_Backup $backup) return $this; } + /**. + * Returns the list of tables which data should not be backed up + * + * @return array + */ + public function getIgnoreDataTablesList() + { + $result = array(); + $resource = Mage::getSingleton('core/resource'); + + foreach ($this->_ignoreDataTablesList as $table) { + $result[] = $resource->getTableName($table); + } + + return $result; + } } diff --git a/app/code/core/Mage/Backup/Model/Fs/Collection.php b/app/code/core/Mage/Backup/Model/Fs/Collection.php index a7bc76bd..38e45dc3 100644 --- a/app/code/core/Mage/Backup/Model/Fs/Collection.php +++ b/app/code/core/Mage/Backup/Model/Fs/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -58,10 +58,17 @@ public function __construct() } // set collection specific params + $extensions = Mage::helper('backup')->getExtensions(); + + foreach ($extensions as $key => $value) { + $extensions[] = '(' . preg_quote($value, '/') . ')'; + } + $extensions = implode('|', $extensions); + $this ->setOrder('time', self::SORT_ORDER_DESC) ->addTargetDir($this->_baseDir) - ->setFilesFilter('/^[a-z0-9\-\_]+\.' . preg_quote(Mage_Backup_Model_Backup::BACKUP_EXTENSION, '/') . '$/') + ->setFilesFilter('/^[a-z0-9\-\_]+\.' . $extensions . '$/') ->setCollectRecursively(false) ; } @@ -80,6 +87,7 @@ protected function _generateRow($filename) $row[$key] = $value; } $row['size'] = filesize($filename); + $row['id'] = $row['time'] . '_' . $row['type']; return $row; } } diff --git a/app/code/core/Mage/Backup/Model/Mysql4/Db.php b/app/code/core/Mage/Backup/Model/Mysql4/Db.php index 14d762ad..12266fff 100644 --- a/app/code/core/Mage/Backup/Model/Mysql4/Db.php +++ b/app/code/core/Mage/Backup/Model/Mysql4/Db.php @@ -20,396 +20,18 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Database backup resource model * - * @category Mage - * @package Mage_Backup + * @category Mage + * @package Mage_Backup * @author Magento Core Team */ -class Mage_Backup_Model_Mysql4_Db +class Mage_Backup_Model_Mysql4_Db extends Mage_Backup_Model_Resource_Db { - /** - * Read connection - * - * @var Varien_Db_Adapter_Pdo_Mysql - */ - protected $_read; - - /** - * tables Foreign key data array - * [tbl_name] = array(create foreign key strings) - * - * @var array - */ - protected $_foreignKeys = array(); - - /** - * Initialize Backup DB resource model - * - */ - public function __construct() - { - $this->_read = Mage::getSingleton('core/resource')->getConnection('backup_read'); - } - - /** - * @deprecated after 1.4.0.0-alpha2 - */ - public function crear() - { - $this->clear(); - } - - /** - * Clear data - * - */ - public function clear() - { - $this->_foreignKeys = array(); - } - - /** - * Retrieve table list - * - * @return array - */ - public function getTables() - { - return $this->_read->listTables(); - } - - /** - * Retrieve SQL fragment for drop table - * - * @param string $tableName - * @return string - */ - public function getTableDropSql($tableName) - { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - return 'DROP TABLE IF EXISTS ' . $quotedTableName . ';'; - } - - /** - * Retrieve SQL fragment for create table - * - * @param string $tableName - * @param bool $withForeignKeys - * @return string - */ - public function getTableCreateSql($tableName, $withForeignKeys = false) - { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - $sql = 'SHOW CREATE TABLE ' . $quotedTableName; - $row = $this->_read->fetchRow($sql); - - if (!$row || !isset($row['Table']) || !isset($row['Create Table'])) { - return false; - } - - $regExp = '/,\s+CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) ' - . 'REFERENCES `([^`]*)` \(`([^`]*)`\)' - . '( ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?' - . '( ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?/'; - $matches = array(); - preg_match_all($regExp, $row['Create Table'], $matches, PREG_SET_ORDER); - - foreach ($matches as $match) { - $this->_foreignKeys[$tableName][] = sprintf('ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s', - $this->_read->quoteIdentifier($match[1]), - $this->_read->quoteIdentifier($match[2]), - $this->_read->quoteIdentifier($match[3]), - $this->_read->quoteIdentifier($match[4]), - isset($match[5]) ? $match[5] : '', - isset($match[7]) ? $match[7] : '' - ); - } - - if ($withForeignKeys) { - return $row['Create Table'] . ';'; - } - else { - return preg_replace($regExp, '', $row['Create Table']) . ';'; - } - } - - /** - * Retrieve foreign keys for table(s) - * - * @param string|null $tableName - * @return string - */ - public function getTableForeignKeysSql($tableName = null) - { - if (is_null($tableName)) { - $sql = ''; - foreach ($this->_foreignKeys as $table => $foreignKeys) { - $sql .= sprintf("ALTER TABLE %s\n %s;\n", - $this->_read->quoteIdentifier($table), - join(",\n ", $foreignKeys) - ); - } - return $sql; - } - if (isset($this->_foreignKeys[$tableName]) && ($foreignKeys = $this->_foreignKeys[$tableName])) { - - } - return false; - } - - /** - * Retrieve table status - * - * @param string $tableName - * @return Varien_Object - */ - public function getTableStatus($tableName) - { - $sql = $this->_read->quoteInto('SHOW TABLE STATUS LIKE ?', $tableName); - $row = $this->_read->fetchRow($sql); - - if ($row) { - $statusObject = new Varien_Object(); - $statusObject->setIdFieldName('name'); - foreach ($row as $field => $value) { - $statusObject->setData(strtolower($field), $value); - } - - $cntRow = $this->_read->fetchRow( $this->_read->select()->from($tableName, 'COUNT(*) as rows')); - $statusObject->setRows($cntRow['rows']); - - return $statusObject; - } - - return false; - } - - /** - * Quote Table Row - * - * @param string $tableName - * @param array $row - * @return string - */ - protected function _quoteRow($tableName, array $row) - { - $describe = $this->_read->describeTable($tableName); - $rowData = array(); - foreach ($row as $k => $v) { - if (is_null($v)) { - $value = 'NULL'; - } - elseif (in_array(strtolower($describe[$k]['DATA_TYPE']), array('bigint','mediumint','smallint','tinyint'))) { - $value = $v; - } - else { - $value = $this->_read->quoteInto('?', $v); - } - $rowData[] = $value; - } - return '('.join(',', $rowData).')'; - } - - /** - * Retrive table partical data SQL insert - * - * @param string $tableName - * @param int $count - * @param int $offset - * @return string - */ - public function getTableDataSql($tableName, $count, $offset = 0) - { - $sql = null; - $quotedTableName = $this->_read->quoteIdentifier($tableName); - $select = $this->_read->select() - ->from($tableName) - ->limit($count, $offset); - $query = $this->_read->query($select); - - while ($row = $query->fetch()) { - if (is_null($sql)) { - $sql = 'INSERT INTO ' . $quotedTableName . ' VALUES '; - } - else { - $sql .= ','; - } - - $sql .= $this->_quoteRow($tableName, $row); - } - - if (!is_null($sql)) { - $sql .= ';' . "\n"; - } - - return $sql; - } - - /** - * Enter description here... - * - * @param unknown_type $tableName - * @param unknown_type $addDropIfExists - * @return unknown - */ - public function getTableCreateScript($tableName, $addDropIfExists=false) - { - $script = ''; - if ($this->_read) { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - - if ($addDropIfExists) { - $script .= 'DROP TABLE IF EXISTS ' . $quotedTableName .";\n"; - } - $sql = 'SHOW CREATE TABLE ' . $quotedTableName; - $data = $this->_read->fetchRow($sql); - $script.= isset($data['Create Table']) ? $data['Create Table'].";\n" : ''; - } - - return $script; - } - - /** - * Retrieve table header comment - * - * @return string - */ - public function getTableHeader($tableName) - { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - return "\n--\n" - . "-- Table structure for table {$quotedTableName}\n" - . "--\n\n"; - } - - public function getTableDataDump($tableName, $step=100) - { - $sql = ''; - if ($this->_read) { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - $colunms = $this->_read->fetchRow('SELECT * FROM '.$quotedTableName.' LIMIT 1'); - if ($colunms) { - $arrSql = array(); - - $colunms = array_keys($colunms); - $quote = $this->_read->getQuoteIdentifierSymbol(); - $sql = 'INSERT INTO ' . $quotedTableName . ' (' .$quote . implode($quote.', '.$quote,$colunms).$quote.')'; - $sql.= ' VALUES '; - - $startRow = 0; - $select = $this->_read->select(); - $select->from($tableName) - ->limit($step, $startRow); - while ($data = $this->_read->fetchAll($select)) { - $dataSql = array(); - foreach ($data as $row) { - $dataSql[] = $this->_read->quoteInto('(?)', $row); - } - $arrSql[] = $sql.implode(', ', $dataSql).';'; - $startRow += $step; - $select->limit($step, $startRow); - } - - $sql = implode("\n", $arrSql)."\n"; - } - - } - - return $sql; - } - - /** - * Returns SQL header data - */ - public function getHeader() - { - $dbConfig = $this->_read->getConfig(); - - $versionRow = $this->_read->fetchRow('SHOW VARIABLES LIKE \'version\''); - $hostName = !empty($dbConfig['unix_socket']) ? $dbConfig['unix_socket'] - : (!empty($dbConfig['host']) ? $dbConfig['host'] : 'localhost'); - - $header = "-- Magento DB backup\n" - . "--\n" - . "-- Host: {$hostName} Database: {$dbConfig['dbname']}\n" - . "-- ------------------------------------------------------\n" - . "-- Server version: {$versionRow['Value']}\n\n" - . "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n" - . "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n" - . "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n" - . "/*!40101 SET NAMES utf8 */;\n" - . "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n" - . "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n" - . "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n" - . "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n"; - - return $header; - } - - /** - * Returns SQL footer data - */ - public function getFooter() - { - $footer = "\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n" - . "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; \n" - . "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n" - . "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n" - . "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" - . "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n" - . "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n" - . "\n-- Dump completed on " . Mage::getSingleton('core/date')->gmtDate() . " GMT"; - - return $footer; - } - - /** - * Retrieve before insert data SQL fragment - * - * @param string $tableName - * @return string - */ - public function getTableDataBeforeSql($tableName) - { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - return "\n--\n" - . "-- Dumping data for table {$quotedTableName}\n" - . "--\n\n" - . "LOCK TABLES {$quotedTableName} WRITE;\n" - . "/*!40000 ALTER TABLE {$quotedTableName} DISABLE KEYS */;\n"; - } - - /** - * Retrieve after insert data SQL fragment - * - * @param string $tableName - * @return string - */ - public function getTableDataAfterSql($tableName) - { - $quotedTableName = $this->_read->quoteIdentifier($tableName); - return "/*!40000 ALTER TABLE {$quotedTableName} ENABLE KEYS */;\n" - . "UNLOCK TABLES;\n"; - } - - public function beginTransaction() - { - $this->_read->beginTransaction(); - } - - public function commitTransaction() - { - $this->_read->commit(); - } - - public function rollBackTransaction() - { - $this->_read->rollBack(); - } } diff --git a/app/code/core/Mage/Backup/Model/Observer.php b/app/code/core/Mage/Backup/Model/Observer.php new file mode 100644 index 00000000..c2eb1f21 --- /dev/null +++ b/app/code/core/Mage/Backup/Model/Observer.php @@ -0,0 +1,95 @@ + + */ +class Mage_Backup_Model_Observer +{ + const XML_PATH_BACKUP_ENABLED = 'system/backup/enabled'; + const XML_PATH_BACKUP_TYPE = 'system/backup/type'; + const XML_PATH_BACKUP_MAINTENANCE_MODE = 'system/backup/maintenance'; + + /** + * Error messages + * + * @var array + */ + protected $_errors = array(); + + /** + * Create Backup + * + * @return Mage_Log_Model_Cron + */ + public function scheduledBackup() + { + if (!Mage::getStoreConfigFlag(self::XML_PATH_BACKUP_ENABLED)) { + return $this; + } + + if (Mage::getStoreConfigFlag(self::XML_PATH_BACKUP_MAINTENANCE_MODE)) { + Mage::helper('backup')->turnOnMaintenanceMode(); + } + + $type = Mage::getStoreConfig(self::XML_PATH_BACKUP_TYPE); + + $this->_errors = array(); + try { + $backupManager = Mage_Backup::getBackupInstance($type) + ->setBackupExtension(Mage::helper('backup')->getExtensionByType($type)) + ->setTime(time()) + ->setBackupsDir(Mage::helper('backup')->getBackupsDir()); + + Mage::register('backup_manager', $backupManager); + + if ($type != Mage_Backup_Helper_Data::TYPE_DB) { + $backupManager->setRootDir(Mage::getBaseDir()) + ->addIgnorePaths(Mage::helper('backup')->getBackupIgnorePaths()); + } + + $backupManager->create(); + Mage::log(Mage::helper('backup')->getCreateSuccessMessageByType($type)); + } + catch (Exception $e) { + $this->_errors[] = $e->getMessage(); + $this->_errors[] = $e->getTrace(); + Mage::log($e->getMessage(), Zend_Log::ERR); + Mage::logException($e); + } + + if (Mage::getStoreConfigFlag(self::XML_PATH_BACKUP_MAINTENANCE_MODE)) { + Mage::helper('backup')->turnOffMaintenanceMode(); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Backup/Model/Resource/Db.php b/app/code/core/Mage/Backup/Model/Resource/Db.php new file mode 100755 index 00000000..07d66e92 --- /dev/null +++ b/app/code/core/Mage/Backup/Model/Resource/Db.php @@ -0,0 +1,316 @@ + + */ +class Mage_Backup_Model_Resource_Db +{ + /** + * Database connection adapter + * + * @var Varien_Db_Adapter_Pdo_Mysql + */ + protected $_write; + + /** + * tables Foreign key data array + * [tbl_name] = array(create foreign key strings) + * + * @var array + */ + protected $_foreignKeys = array(); + + /** + * Initialize Backup DB resource model + * + */ + public function __construct() + { + $this->_write = Mage::getSingleton('core/resource')->getConnection('backup_write'); + } + + /** + * Enter description here ... + * + * @deprecated after 1.4.0.0-alpha2 + * + */ + public function crear() + { + $this->clear(); + } + + /** + * Clear data + * + */ + public function clear() + { + $this->_foreignKeys = array(); + } + + /** + * Retrieve table list + * + * @return array + */ + public function getTables() + { + return $this->_write->listTables(); + } + + /** + * Retrieve SQL fragment for drop table + * + * @param string $tableName + * @return string + */ + public function getTableDropSql($tableName) + { + return Mage::getResourceHelper('backup')->getTableDropSql($tableName); + } + + /** + * Retrieve SQL fragment for create table + * + * @param string $tableName + * @param bool $withForeignKeys + * @return string + */ + public function getTableCreateSql($tableName, $withForeignKeys = false) + { + return Mage::getResourceHelper('backup')->getTableCreateSql($tableName, $withForeignKeys = false); + } + + /** + * Retrieve foreign keys for table(s) + * + * @param string|null $tableName + * @return string + */ + public function getTableForeignKeysSql($tableName = null) + { + $fkScript = ''; + if (!$tableName) { + $tables = $this->getTables(); + foreach($tables as $table) { + $tableFkScript = Mage::getResourceHelper('backup')->getTableForeignKeysSql($table); + if (!empty($tableFkScript)) { + $fkScript .= "\n" . $tableFkScript; + } + } + } else { + $fkScript = $this->getTableForeignKeysSql($tableName); + } + return $fkScript; + } + + /** + * Retrieve table status + * + * @param string $tableName + * @return Varien_Object + */ + public function getTableStatus($tableName) + { + $row = $this->_write->showTableStatus($tableName); + + if ($row) { + $statusObject = new Varien_Object(); + $statusObject->setIdFieldName('name'); + foreach ($row as $field => $value) { + $statusObject->setData(strtolower($field), $value); + } + + $cntRow = $this->_write->fetchRow( + $this->_write->select()->from($tableName, 'COUNT(1) as rows')); + $statusObject->setRows($cntRow['rows']); + + return $statusObject; + } + + return false; + } + + /** + * Quote Table Row + * + * @deprecated + * + * @param string $tableName + * @param array $row + * @return string + */ + protected function _quoteRow($tableName, array $row) + { + return $row; + } + + /** + * Retrive table partical data SQL insert + * + * @param string $tableName + * @param int $count + * @param int $offset + * @return string + */ + public function getTableDataSql($tableName, $count = null, $offset = null) + { + return Mage::getResourceHelper('backup')->getPartInsertSql($tableName, $count, $offset); + } + + /** + * Enter description here... + * + * @param unknown_type $tableName + * @param unknown_type $addDropIfExists + * @return unknown + */ + public function getTableCreateScript($tableName, $addDropIfExists = false) + { + return Mage::getResourceHelper('backup')->getTableCreateScript($tableName, $addDropIfExists);; + } + + /** + * Retrieve table header comment + * + * @param unknown_type $tableName + * @return string + */ + public function getTableHeader($tableName) + { + $quotedTableName = $this->_write->quoteIdentifier($tableName); + return "\n--\n" + . "-- Table structure for table {$quotedTableName}\n" + . "--\n\n"; + } + + /** + * Return table data dump + * + * @param string $tableName + * @param bool $step + * @return string + */ + public function getTableDataDump($tableName, $step = false) + { + return $this->getTableDataSql($tableName); + } + + /** + * Returns SQL header data + * + * @return string + */ + public function getHeader() + { + return Mage::getResourceHelper('backup')->getHeader(); + } + + /** + * Returns SQL footer data + * + * @return string + */ + public function getFooter() + { + return Mage::getResourceHelper('backup')->getFooter(); + } + + /** + * Retrieve before insert data SQL fragment + * + * @param string $tableName + * @return string + */ + public function getTableDataBeforeSql($tableName) + { + return Mage::getResourceHelper('backup')->getTableDataBeforeSql($tableName); + } + + /** + * Retrieve after insert data SQL fragment + * + * @param string $tableName + * @return string + */ + public function getTableDataAfterSql($tableName) + { + return Mage::getResourceHelper('backup')->getTableDataAfterSql($tableName); + } + + /** + * Start transaction mode + * + * @return Mage_Backup_Model_Resource_Db + */ + public function beginTransaction() + { + Mage::getResourceHelper('backup')->turnOnSerializableMode(); + $this->_write->beginTransaction(); + return $this; + } + + /** + * Commit transaction + * + * @return Mage_Backup_Model_Resource_Db + */ + public function commitTransaction() + { + $this->_write->commit(); + Mage::getResourceHelper('backup')->turnOnReadCommittedMode(); + return $this; + } + + /** + * Rollback transaction + * + * @return Mage_Backup_Model_Resource_Db + */ + public function rollBackTransaction() + { + $this->_write->rollBack(); + return $this; + } + + /** + * Run sql code + * + * @param $command + * @return Mage_Backup_Model_Resource_Db + */ + public function runCommand($command){ + $this->_write->query($command); + return $this; + } +} diff --git a/app/code/core/Mage/Backup/Model/Resource/Helper/Mysql4.php b/app/code/core/Mage/Backup/Model/Resource/Helper/Mysql4.php new file mode 100644 index 00000000..28a1e63f --- /dev/null +++ b/app/code/core/Mage/Backup/Model/Resource/Helper/Mysql4.php @@ -0,0 +1,322 @@ +_getReadAdapter()->quoteIdentifier($tableName); + return sprintf('DROP TABLE IF EXISTS %s;', $quotedTableName); + } + + /** + * Retrieve foreign keys for table(s) + * + * @param string|null $tableName + * @return string|false + */ + public function getTableForeignKeysSql($tableName = null) + { + $sql = false; + + if ($tableName === null) { + $sql = ''; + foreach ($this->_foreignKeys as $table => $foreignKeys) { + $sql .= $this->_buildForeignKeysAlterTableSql($table, $foreignKeys); + } + } else if (isset($this->_foreignKeys[$tableName])) { + $foreignKeys = $this->_foreignKeys[$tableName]; + $sql = $this->_buildForeignKeysAlterTableSql($tableName, $foreignKeys); + } + + return $sql; + } + + /** + * Build sql that will add foreign keys to it + * + * @param string $tableName + * @param array $foreignKeys + * @return string + */ + protected function _buildForeignKeysAlterTableSql($tableName, $foreignKeys) + { + if (!is_array($foreignKeys) || empty($foreignKeys)) { + return ''; + } + + return sprintf("ALTER TABLE %s\n %s;\n", + $this->_getReadAdapter()->quoteIdentifier($tableName), + join(",\n ", $foreignKeys) + ); + } + + /** + * Get create script for table + * + * @param string $tableName + * @param boolean $addDropIfExists + * @return string + */ + public function getTableCreateScript($tableName, $addDropIfExists = false) + { + $script = ''; + $quotedTableName = $this->_getReadAdapter()->quoteIdentifier($tableName); + + if ($addDropIfExists) { + $script .= 'DROP TABLE IF EXISTS ' . $quotedTableName .";\n"; + } + //TODO fix me + $sql = 'SHOW CREATE TABLE ' . $quotedTableName; + $data = $this->_getReadAdapter()->fetchRow($sql); + $script .= isset($data['Create Table']) ? $data['Create Table'].";\n" : ''; + + return $script; + } + /** + * Retrieve SQL fragment for create table + * + * @param string $tableName + * @param bool $withForeignKeys + * @return string + */ + public function getTableCreateSql($tableName, $withForeignKeys = false) + { + $adapter = $this->_getReadAdapter(); + $quotedTableName = $adapter->quoteIdentifier($tableName); + $query = 'SHOW CREATE TABLE ' . $quotedTableName; + $row = $adapter->fetchRow($query); + + if (!$row || !isset($row['Table']) || !isset($row['Create Table'])) { + return false; + } + + $regExp = '/,\s+CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) ' + . 'REFERENCES `([^`]*)` \(`([^`]*)`\)' + . '( ON DELETE (RESTRICT|CASCADE|SET NULL|NO ACTION))?' + . '( ON UPDATE (RESTRICT|CASCADE|SET NULL|NO ACTION))?/'; + $matches = array(); + preg_match_all($regExp, $row['Create Table'], $matches, PREG_SET_ORDER); + + if (is_array($matches)) { + foreach ($matches as $match) { + $this->_foreignKeys[$tableName][] = sprintf('ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s%s', + $adapter->quoteIdentifier($match[1]), + $adapter->quoteIdentifier($match[2]), + $adapter->quoteIdentifier($match[3]), + $adapter->quoteIdentifier($match[4]), + isset($match[5]) ? $match[5] : '', + isset($match[7]) ? $match[7] : '' + ); + } + } + + if ($withForeignKeys) { + $sql = $row['Create Table']; + } else { + $sql = preg_replace($regExp, '', $row['Create Table']); + } + + return $sql . ';'; + } + /** + * Returns SQL header data, move from original resource model + * + * @return string + */ + public function getHeader() + { + $dbConfig = $this->_getReadAdapter()->getConfig(); + + $versionRow = $this->_getReadAdapter()->fetchRow('SHOW VARIABLES LIKE \'version\''); + $hostName = !empty($dbConfig['unix_socket']) ? $dbConfig['unix_socket'] + : (!empty($dbConfig['host']) ? $dbConfig['host'] : 'localhost'); + + $header = "-- Magento DB backup\n" + . "--\n" + . "-- Host: {$hostName} Database: {$dbConfig['dbname']}\n" + . "-- ------------------------------------------------------\n" + . "-- Server version: {$versionRow['Value']}\n\n" + . "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n" + . "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n" + . "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n" + . "/*!40101 SET NAMES utf8 */;\n" + . "/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n" + . "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n" + . "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;\n" + . "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n"; + + return $header; + } + + /** + * Returns SQL footer data, move from original resource model + * + * @return string + */ + public function getFooter() + { + $footer = "\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n" + . "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; \n" + . "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n" + . "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n" + . "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" + . "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n" + . "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n" + . "\n-- Dump completed on " . Mage::getSingleton('core/date')->gmtDate() . " GMT"; + + return $footer; + } + + /** + * Retrieve before insert data SQL fragment + * + * @param string $tableName + * @return string + */ + public function getTableDataBeforeSql($tableName) + { + $quotedTableName = $this->_getReadAdapter()->quoteIdentifier($tableName); + return "\n--\n" + . "-- Dumping data for table {$quotedTableName}\n" + . "--\n\n" + . "LOCK TABLES {$quotedTableName} WRITE;\n" + . "/*!40000 ALTER TABLE {$quotedTableName} DISABLE KEYS */;\n"; + } + + /** + * Retrieve after insert data SQL fragment + * + * @param string $tableName + * @return string + */ + public function getTableDataAfterSql($tableName) + { + $quotedTableName = $this->_getReadAdapter()->quoteIdentifier($tableName); + return "/*!40000 ALTER TABLE {$quotedTableName} ENABLE KEYS */;\n" + . "UNLOCK TABLES;\n"; + } + + /** + * Return table part data SQL insert + * + * @param string $tableName + * @param int $count + * @param int $offset + * @return string + */ + public function getPartInsertSql($tableName, $count = null, $offset = null) + { + $sql = null; + $adapter = $this->_getWriteAdapter(); + $select = $adapter->select() + ->from($tableName) + ->limit($count, $offset); + $query = $adapter->query($select); + + while ($row = $query->fetch()) { + if ($sql === null) { + $sql = sprintf('INSERT INTO %s VALUES ', $adapter->quoteIdentifier($tableName)); + } else { + $sql .= ','; + } + + $sql .= $this->_quoteRow($tableName, $row); + } + + if ($sql !== null) { + $sql .= ';' . "\n"; + } + + return $sql; + } + /** + * Return table data SQL insert + * + * @param string $tableName + * @return string + */ + public function getInsertSql($tableName) + { + return $this->getPartInsertSql($tableName); + } + /** + * Quote Table Row + * + * @param string $tableName + * @param array $row + * @return string + */ + protected function _quoteRow($tableName, array $row) + { + $adapter = $this->_getReadAdapter(); + $describe = $adapter->describeTable($tableName); + $dataTypes = array('bigint', 'mediumint', 'smallint', 'tinyint'); + $rowData = array(); + foreach ($row as $k => $v) { + if ($v === null) { + $value = 'NULL'; + } elseif (in_array(strtolower($describe[$k]['DATA_TYPE']), $dataTypes)) { + $value = $v; + } else { + $value = $adapter->quoteInto('?', $v); + } + $rowData[] = $value; + } + + return sprintf('(%s)', implode(',', $rowData)); + } + + /** + * Turn on serializable mode + */ + public function turnOnSerializableMode() + { + $this->_getReadAdapter()->query("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + } + + /** + * Turn on read committed mode + */ + public function turnOnReadCommittedMode() + { + $this->_getReadAdapter()->query("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"); + } +} diff --git a/app/code/core/Mage/Backup/etc/adminhtml.xml b/app/code/core/Mage/Backup/etc/adminhtml.xml index dfe811be..7f55c38f 100644 --- a/app/code/core/Mage/Backup/etc/adminhtml.xml +++ b/app/code/core/Mage/Backup/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -50,6 +50,11 @@ Backups + + + Rollback + + diff --git a/app/code/core/Mage/Backup/etc/config.xml b/app/code/core/Mage/Backup/etc/config.xml index c580fb70..ab19ccd9 100644 --- a/app/code/core/Mage/Backup/etc/config.xml +++ b/app/code/core/Mage/Backup/etc/config.xml @@ -21,25 +21,26 @@ * * @category Mage * @package Mage_Backup - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.0 + 1.6.0.0 Mage_Backup_Model - backup_mysql4 + backup_resource - - Mage_Backup_Model_Mysql4 - + + Mage_Backup_Model_Resource + backup_mysql4 + @@ -60,4 +61,13 @@ + + + + + backup/observer::scheduledBackup + + + + diff --git a/app/code/core/Mage/Backup/etc/system.xml b/app/code/core/Mage/Backup/etc/system.xml new file mode 100644 index 00000000..76b98b11 --- /dev/null +++ b/app/code/core/Mage/Backup/etc/system.xml @@ -0,0 +1,95 @@ + + + + + + + + + text + 500 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 10 + 1 + 0 + 0 + + + + select + 1 + backup/config_source_type + 20 + 1 + 0 + 0 + + + + + select + 1 + adminhtml/system_config_source_cron_frequency + backup/config_backend_cron + 40 + 1 + 0 + 0 + + + + Put store on the maintenance mode while backup's creation + select + 1 + adminhtml/system_config_source_yesno + 50 + 1 + 0 + 0 + + + + + + + diff --git a/app/code/core/Mage/Cms/Block/Block.php b/app/code/core/Mage/Cms/Block/Block.php index a7f935de..41585621 100644 --- a/app/code/core/Mage/Cms/Block/Block.php +++ b/app/code/core/Mage/Cms/Block/Block.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Block/Page.php b/app/code/core/Mage/Cms/Block/Page.php index 89ab2059..b1efe4df 100644 --- a/app/code/core/Mage/Cms/Block/Page.php +++ b/app/code/core/Mage/Cms/Block/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -98,7 +98,7 @@ protected function _toHtml() $helper = Mage::helper('cms'); $processor = $helper->getPageTemplateProcessor(); $html = $processor->filter($this->getPage()->getContent()); - $html = $this->getMessagesBlock()->getGroupedHtml() . $html; + $html = $this->getMessagesBlock()->toHtml() . $html; return $html; } } diff --git a/app/code/core/Mage/Cms/Block/Widget/Block.php b/app/code/core/Mage/Cms/Block/Widget/Block.php index 15b8b623..34e20c2d 100644 --- a/app/code/core/Mage/Cms/Block/Widget/Block.php +++ b/app/code/core/Mage/Cms/Block/Widget/Block.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,8 +34,16 @@ */ class Mage_Cms_Block_Widget_Block extends Mage_Core_Block_Template implements Mage_Widget_Block_Interface { + /** + * Storage for used widgets + * + * @var array + */ + static protected $_widgetUsageMap = array(); + /** * Prepare block text and determine whether block output enabled or not + * Prevent blocks recursion if needed * * @return Mage_Cms_Block_Widget_Block */ @@ -43,6 +51,13 @@ protected function _beforeToHtml() { parent::_beforeToHtml(); $blockId = $this->getData('block_id'); + $blockHash = get_class($this) . $blockId; + + if (isset(self::$_widgetUsageMap[$blockHash])) { + return $this; + } + self::$_widgetUsageMap[$blockHash] = true; + if ($blockId) { $block = Mage::getModel('cms/block') ->setStoreId(Mage::app()->getStore()->getId()) @@ -54,6 +69,8 @@ protected function _beforeToHtml() $this->setText($processor->filter($block->getContent())); } } + + unset(self::$_widgetUsageMap[$blockHash]); return $this; } } diff --git a/app/code/core/Mage/Cms/Block/Widget/Page/Link.php b/app/code/core/Mage/Cms/Block/Widget/Page/Link.php index d129ed51..897ebb31 100644 --- a/app/code/core/Mage/Cms/Block/Widget/Page/Link.php +++ b/app/code/core/Mage/Cms/Block/Widget/Page/Link.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Controller/Router.php b/app/code/core/Mage/Cms/Controller/Router.php index 99c8c16a..0fa18baf 100644 --- a/app/code/core/Mage/Cms/Controller/Router.php +++ b/app/code/core/Mage/Cms/Controller/Router.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Helper/Data.php b/app/code/core/Mage/Cms/Helper/Data.php index b1344155..1cc961b9 100644 --- a/app/code/core/Mage/Cms/Helper/Data.php +++ b/app/code/core/Mage/Cms/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Helper/Page.php b/app/code/core/Mage/Cms/Helper/Page.php index ed084907..2cb55f2b 100644 --- a/app/code/core/Mage/Cms/Helper/Page.php +++ b/app/code/core/Mage/Cms/Helper/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -62,6 +62,7 @@ public function renderPage(Mage_Core_Controller_Front_Action $action, $pageId = */ protected function _renderPage(Mage_Core_Controller_Varien_Action $action, $pageId = null, $renderLayout = true) { + $page = Mage::getSingleton('cms/page'); if (!is_null($pageId) && $pageId!==$page->getId()) { $delimeterPosition = strrpos($pageId, '|'); @@ -79,7 +80,8 @@ protected function _renderPage(Mage_Core_Controller_Varien_Action $action, $pag return false; } - $inRange = Mage::app()->getLocale()->isStoreDateInInterval(null, $page->getCustomThemeFrom(), $page->getCustomThemeTo()); + $inRange = Mage::app()->getLocale() + ->isStoreDateInInterval(null, $page->getCustomThemeFrom(), $page->getCustomThemeTo()); if ($page->getCustomTheme()) { if ($inRange) { @@ -105,13 +107,15 @@ protected function _renderPage(Mage_Core_Controller_Varien_Action $action, $pag Mage::dispatchEvent('cms_page_render', array('page' => $page, 'controller_action' => $action)); $action->loadLayoutUpdates(); - $layoutUpdate = ($page->getCustomLayoutUpdateXml() && $inRange) ? $page->getCustomLayoutUpdateXml() : $page->getLayoutUpdateXml(); + $layoutUpdate = ($page->getCustomLayoutUpdateXml() && $inRange) + ? $page->getCustomLayoutUpdateXml() : $page->getLayoutUpdateXml(); $action->getLayout()->getUpdate()->addUpdate($layoutUpdate); $action->generateLayoutXml()->generateLayoutBlocks(); $contentHeadingBlock = $action->getLayout()->getBlock('page_content_heading'); if ($contentHeadingBlock) { - $contentHeadingBlock->setContentHeading($page->getContentHeading()); + $contentHeading = $this->escapeHtml($page->getContentHeading()); + $contentHeadingBlock->setContentHeading($contentHeading); } if ($page->getRootTemplate()) { @@ -119,10 +123,13 @@ protected function _renderPage(Mage_Core_Controller_Varien_Action $action, $pag ->applyTemplate($page->getRootTemplate()); } - foreach (array('catalog/session', 'checkout/session') as $class_name) { - $storage = Mage::getSingleton($class_name); + /* @TODO: Move catalog and checkout storage types to appropriate modules */ + $messageBlock = $action->getLayout()->getMessagesBlock(); + foreach (array('catalog/session', 'checkout/session', 'customer/session') as $storageType) { + $storage = Mage::getSingleton($storageType); if ($storage) { - $action->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); + $messageBlock->addStorageType($storageType); + $messageBlock->addMessages($storage->getMessages(true)); } } diff --git a/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php b/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php index a11a6df0..84b154e4 100644 --- a/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php +++ b/app/code/core/Mage/Cms/Helper/Wysiwyg/Images.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -51,7 +51,7 @@ class Mage_Cms_Helper_Wysiwyg_Images extends Mage_Core_Helper_Abstract /** - * Set a specified store ID value + * Set a specified store ID value * * @param $store */ @@ -68,7 +68,8 @@ public function setStoreId($store) */ public function getStorageRoot() { - return Mage::getConfig()->getOptions()->getMediaDir() . DS; + return Mage::getConfig()->getOptions()->getMediaDir() . DS . Mage_Cms_Model_Wysiwyg_Config::IMAGE_DIRECTORY + . DS; } /** diff --git a/app/code/core/Mage/Cms/Model/Block.php b/app/code/core/Mage/Cms/Model/Block.php index 81ad52f3..0b95c36d 100644 --- a/app/code/core/Mage/Cms/Model/Block.php +++ b/app/code/core/Mage/Cms/Model/Block.php @@ -20,15 +20,30 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * CMS block model * - * @category Mage - * @package Mage_Cms + * @method Mage_Cms_Model_Resource_Block _getResource() + * @method Mage_Cms_Model_Resource_Block getResource() + * @method string getTitle() + * @method Mage_Cms_Model_Block setTitle(string $value) + * @method string getIdentifier() + * @method Mage_Cms_Model_Block setIdentifier(string $value) + * @method string getContent() + * @method Mage_Cms_Model_Block setContent(string $value) + * @method string getCreationTime() + * @method Mage_Cms_Model_Block setCreationTime(string $value) + * @method string getUpdateTime() + * @method Mage_Cms_Model_Block setUpdateTime(string $value) + * @method int getIsActive() + * @method Mage_Cms_Model_Block setIsActive(int $value) + * + * @category Mage + * @package Mage_Cms * @author Magento Core Team */ @@ -41,4 +56,21 @@ protected function _construct() { $this->_init('cms/block'); } + + /** + * Prevent blocks recursion + * + * @throws Mage_Core_Exception + * @return Mage_Core_Model_Abstract + */ + protected function _beforeSave() + { + $needle = 'block_id="' . $this->getBlockId() . '"'; + if (false == strstr($this->getContent(), $needle)) { + return parent::_beforeSave(); + } + Mage::throwException( + Mage::helper('cms')->__('The static block content cannot contain directive with its self.') + ); + } } diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Block.php b/app/code/core/Mage/Cms/Model/Mysql4/Block.php index 4a13955a..9b639b42 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Block.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Block.php @@ -20,149 +20,18 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * CMS block model * - * @category Mage - * @package Mage_Cms + * @category Mage + * @package Mage_Cms * @author Magento Core Team */ - -class Mage_Cms_Model_Mysql4_Block extends Mage_Core_Model_Mysql4_Abstract +class Mage_Cms_Model_Mysql4_Block extends Mage_Cms_Model_Resource_Block { - - protected function _construct() - { - $this->_init('cms/block', 'block_id'); - } - - /** - * - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (!$this->getIsUniqueBlockToStores($object)) { - Mage::throwException(Mage::helper('cms')->__('A block identifier with the same properties already exists in the selected store.')); - } - - if (! $object->getId()) { - $object->setCreationTime(Mage::getSingleton('core/date')->gmtDate()); - } - $object->setUpdateTime(Mage::getSingleton('core/date')->gmtDate()); - return $this; - } - - /** - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $condition = $this->_getWriteAdapter()->quoteInto('block_id = ?', $object->getId()); - $this->_getWriteAdapter()->delete($this->getTable('cms/block_store'), $condition); - - foreach ((array)$object->getData('stores') as $store) { - $storeArray = array(); - $storeArray['block_id'] = $object->getId(); - $storeArray['store_id'] = $store; - $this->_getWriteAdapter()->insert($this->getTable('cms/block_store'), $storeArray); - } - - return parent::_afterSave($object); - } - - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - - if (!intval($value) && is_string($value)) { - $field = 'identifier'; - } - return parent::load($object, $value, $field); - } - - /** - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _afterLoad(Mage_Core_Model_Abstract $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('cms/block_store')) - ->where('block_id = ?', $object->getId()); - - if ($data = $this->_getReadAdapter()->fetchAll($select)) { - $storesArray = array(); - foreach ($data as $row) { - $storesArray[] = $row['store_id']; - } - $object->setData('store_id', $storesArray); - } - - return parent::_afterLoad($object); - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @return Zend_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - - $select = parent::_getLoadSelect($field, $value, $object); - - if ($object->getStoreId()) { - $select->join(array('cbs' => $this->getTable('cms/block_store')), $this->getMainTable().'.block_id = cbs.block_id') - ->where('is_active=1 AND cbs.store_id in (0, ?) ', $object->getStoreId()) - ->order('store_id DESC') - ->limit(1); - } - return $select; - } - - /** - * Check for unique of identifier of block to selected store(s). - * - * @param Mage_Core_Model_Abstract $object - * @return bool - */ - public function getIsUniqueBlockToStores(Mage_Core_Model_Abstract $object) - { - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainTable()) - ->join(array('cbs' => $this->getTable('cms/block_store')), $this->getMainTable().'.block_id = `cbs`.block_id') - ->where($this->getMainTable().'.identifier = ?', $object->getData('identifier')); - if ($object->getId()) { - $select->where($this->getMainTable().'.block_id <> ?',$object->getId()); - } - $select->where('`cbs`.store_id IN (?)', (array)$object->getData('stores')); - - if ($this->_getWriteAdapter()->fetchRow($select)) { - return false; - } - - return true; - } - - /** - * Get store ids to which specified item is assigned - * - * @param int $id - * @return array - */ - public function lookupStoreIds($id) - { - return $this->_getReadAdapter()->fetchCol($this->_getReadAdapter()->select() - ->from($this->getTable('cms/block_store'), 'store_id') - ->where("{$this->getIdFieldName()} = ?", $id) - ); - } } diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Block/Collection.php b/app/code/core/Mage/Cms/Model/Mysql4/Block/Collection.php index 41d89112..79222e9d 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Block/Collection.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Block/Collection.php @@ -20,51 +20,18 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * CMS block model * - * @category Mage - * @package Mage_Cms + * @category Mage + * @package Mage_Cms * @author Magento Core Team */ - -class Mage_Cms_Model_Mysql4_Block_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Cms_Model_Mysql4_Block_Collection extends Mage_Cms_Model_Resource_Block_Collection { - - protected function _construct() - { - $this->_init('cms/block'); - } - - public function toOptionArray() - { - return $this->_toOptionArray('block_id', 'title'); - } - - /** - * Add Filter by store - * - * @param int|Mage_Core_Model_Store $store - * @return Mage_Cms_Model_Mysql4_Page_Collection - */ - public function addStoreFilter($store, $withAdmin = true) - { - if ($store instanceof Mage_Core_Model_Store) { - $store = array($store->getId()); - } - - $this->getSelect()->join( - array('store_table' => $this->getTable('cms/block_store')), - 'main_table.block_id = store_table.block_id', - array() - ) - ->where('store_table.store_id in (?)', ($withAdmin ? array(0, $store) : $store)) - ->group('main_table.block_id'); - - return $this; - } } diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Page.php b/app/code/core/Mage/Cms/Model/Mysql4/Page.php index ef44dd22..a8a02368 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Page.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Page.php @@ -20,288 +20,18 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Cms page mysql resource * - * @category Mage - * @package Mage_Cms + * @category Mage + * @package Mage_Cms * @author Magento Core Team */ - -class Mage_Cms_Model_Mysql4_Page extends Mage_Core_Model_Mysql4_Abstract +class Mage_Cms_Model_Mysql4_Page extends Mage_Cms_Model_Resource_Page { - /** - * Store model - * - * @var null|Mage_Core_Model_Store - */ - protected $_store = null; - - /** - * Initialize resource model - */ - protected function _construct() - { - $this->_init('cms/page', 'page_id'); - } - - /** - * Process page data before saving - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - /* - * For two attributes which represent datetime data in DB - * we should make converting such as: - * If they are empty we need to convert them into DB - * type NULL so in DB they will be empty and not some default value. - */ - foreach (array('custom_theme_from', 'custom_theme_to') as $dataKey) { - if (!$object->getData($dataKey)) { - $object->setData($dataKey, new Zend_Db_Expr('NULL')); - } - } - - if (!$this->getIsUniquePageToStores($object)) { - Mage::throwException(Mage::helper('cms')->__('A page URL key for specified store already exists.')); - } - - if ($this->isNumericPageIdentifier($object)) { - Mage::throwException(Mage::helper('cms')->__('The page URL key cannot consist only of numbers.')); - } - - if (! $object->getId()) { - $object->setCreationTime(Mage::getSingleton('core/date')->gmtDate()); - } - - $object->setUpdateTime(Mage::getSingleton('core/date')->gmtDate()); - return $this; - } - - /** - * Assign page to store views - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $condition = $this->_getWriteAdapter()->quoteInto('page_id = ?', $object->getId()); - $this->_getWriteAdapter()->delete($this->getTable('cms/page_store'), $condition); - - foreach ((array)$object->getData('stores') as $store) { - $storeArray = array(); - $storeArray['page_id'] = $object->getId(); - $storeArray['store_id'] = $store; - $this->_getWriteAdapter()->insert($this->getTable('cms/page_store'), $storeArray); - } - - return parent::_afterSave($object); - } - - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - if (!is_numeric($value)) { - $field = 'identifier'; - } - return parent::load($object, $value, $field); - } - - /** - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _afterLoad(Mage_Core_Model_Abstract $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('cms/page_store')) - ->where('page_id = ?', $object->getId()); - - if ($data = $this->_getReadAdapter()->fetchAll($select)) { - $storesArray = array(); - foreach ($data as $row) { - $storesArray[] = $row['store_id']; - } - $object->setData('store_id', $storesArray); - } - - return parent::_afterLoad($object); - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @return Zend_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - $select = parent::_getLoadSelect($field, $value, $object); - - $storeId = $object->getStoreId(); - if ($storeId) { - $select->join( - array('cps' => $this->getTable('cms/page_store')), - $this->getMainTable().'.page_id = `cps`.page_id' - ) - ->where('is_active=1 AND `cps`.store_id IN (' . Mage_Core_Model_App::ADMIN_STORE_ID . ', ?) ', $storeId) - ->order('store_id DESC') - ->limit(1); - } - return $select; - } - - /** - * Check for unique of identifier of page to selected store(s). - * - * @param Mage_Core_Model_Abstract $object - * @return bool - */ - public function getIsUniquePageToStores(Mage_Core_Model_Abstract $object) - { - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainTable()) - ->join(array('cps' => $this->getTable('cms/page_store')), $this->getMainTable().'.page_id = `cps`.page_id') - ->where($this->getMainTable().'.identifier = ?', $object->getData('identifier')); - if ($object->getId()) { - $select->where($this->getMainTable().'.page_id <> ?',$object->getId()); - } - $stores = (array)$object->getData('stores'); - if (Mage::app()->isSingleStoreMode()) { - $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID); - } - $select->where('`cps`.store_id IN (?)', $stores); - - if ($this->_getWriteAdapter()->fetchRow($select)) { - return false; - } - - return true; - } - - /** - * Check whether page identifier is numeric - * - * @param Mage_Core_Model_Abstract $object - * @return bool - * @date Wed Mar 26 18:12:28 EET 2008 - */ - protected function isNumericPageIdentifier (Mage_Core_Model_Abstract $object) - { - return preg_match('/^[0-9]+$/', $object->getData('identifier')); - } - - /** - * Check if page identifier exist for specific store - * return page id if page exists - * - * @param string $identifier - * @param int $storeId - * @return int - */ - public function checkIdentifier($identifier, $storeId) - { - $select = $this->_getReadAdapter()->select()->from(array('main_table'=>$this->getMainTable()), 'page_id') - ->join( - array('cps' => $this->getTable('cms/page_store')), - 'main_table.page_id = `cps`.page_id' - ) - ->where('main_table.identifier=?', $identifier) - ->where('main_table.is_active=1 AND `cps`.store_id IN (' . Mage_Core_Model_App::ADMIN_STORE_ID . ', ?) ', $storeId) - ->order('store_id DESC'); - - return $this->_getReadAdapter()->fetchOne($select); - } - - /** - * Retrieves cms page title from DB by passed identifier. - * - * @param string $identifier - * @return string|false - */ - public function getCmsPageTitleByIdentifier($identifier) - { - $select = $this->_getReadAdapter()->select(); - /* @var $select Zend_Db_Select */ - $joinExpr = $this->_getReadAdapter()->quoteInto( - 'main_table.page_id = cps.page_id AND (cps.store_id = ' . Mage_Core_Model_App::ADMIN_STORE_ID . ' OR cps.store_id = ?)', $this->getStore()->getId() - ); - $select->from(array('main_table' => $this->getMainTable()), 'title') - ->joinLeft(array('cps' => $this->getTable('cms/page_store')), $joinExpr ,array()) - ->where('main_table.identifier = ?', $identifier) - ->order('cps.store_id DESC'); - return $this->_getReadAdapter()->fetchOne($select); - } - - /** - * Retrieves cms page title from DB by passed id. - * - * @param string $id - * @return string|false - */ - public function getCmsPageTitleById($id) - { - $select = $this->_getReadAdapter()->select(); - /* @var $select Zend_Db_Select */ - $select->from(array('main_table' => $this->getMainTable()), 'title') - ->where('main_table.page_id = ?', $id); - return $this->_getReadAdapter()->fetchOne($select); - } - - /** - * Retrieves cms page identifier from DB by passed id. - * - * @param string $id - * @return string|false - */ - public function getCmsPageIdentifierById($id) - { - $select = $this->_getReadAdapter()->select(); - /* @var $select Zend_Db_Select */ - $select->from(array('main_table' => $this->getMainTable()), 'identifier') - ->where('main_table.page_id = ?', $id); - return $this->_getReadAdapter()->fetchOne($select); - } - - /** - * Get store ids to which specified item is assigned - * - * @param int $id - * @return array - */ - public function lookupStoreIds($id) - { - return $this->_getReadAdapter()->fetchCol($this->_getReadAdapter()->select() - ->from($this->getTable('cms/page_store'), 'store_id') - ->where("{$this->getIdFieldName()} = ?", $id) - ); - } - - /** - * Set store model - * - * @param Mage_Core_Model_Store $store - * @return Mage_Cms_Model_Mysql4_Page - */ - public function setStore($store) - { - $this->_store = $store; - return $this; - } - - /** - * Retrieve store model - * - * @return Mage_Core_Model_Store - */ - public function getStore() - { - return Mage::app()->getStore($this->_store); - } } diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php b/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php index 7b951efa..79af149e 100644 --- a/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php +++ b/app/code/core/Mage/Cms/Model/Mysql4/Page/Collection.php @@ -20,143 +20,18 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * CMS page collection * - * @category Mage - * @package Mage_Cms + * @category Mage + * @package Mage_Cms * @author Magento Core Team */ - -class Mage_Cms_Model_Mysql4_Page_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Cms_Model_Mysql4_Page_Collection extends Mage_Cms_Model_Resource_Page_Collection { - protected $_previewFlag; - - protected function _construct() - { - $this->_init('cms/page'); - - $this->_map['fields']['page_id'] = 'main_table.page_id'; - } - - /** - * deprecated after 1.4.0.1, use toOptionIdArray() - * - * @return array - */ - public function toOptionArray() - { - return $this->_toOptionArray('identifier', 'title'); - } - - /** - * Returns pairs identifier - title for unique identifiers - * and pairs identifier|page_id - title for non-unique after first - * - * @return array - */ - public function toOptionIdArray() - { - $res = array(); - $existingIdentifiers = array(); - foreach ($this as $item) { - $identifier = $item->getData('identifier'); - - $data['value'] = $identifier; - $data['label'] = $item->getData('title'); - if (in_array($identifier, $existingIdentifiers)) { - $data['value'] .= '|' . $item->getData('page_id'); - } - else { - $existingIdentifiers[] = $identifier; - } - - $res[] = $data; - } - - return $res; - } - - public function setFirstStoreFlag($flag = false) - { - $this->_previewFlag = $flag; - return $this; - } - - protected function _afterLoad() - { - if ($this->_previewFlag) { - $items = $this->getColumnValues('page_id'); - if (count($items)) { - $select = $this->getConnection()->select() - ->from($this->getTable('cms/page_store')) - ->where($this->getTable('cms/page_store').'.page_id IN (?)', $items); - if ($result = $this->getConnection()->fetchPairs($select)) { - foreach ($this as $item) { - if (!isset($result[$item->getData('page_id')])) { - continue; - } - if ($result[$item->getData('page_id')] == 0) { - $stores = Mage::app()->getStores(false, true); - $storeId = current($stores)->getId(); - $storeCode = key($stores); - } else { - $storeId = $result[$item->getData('page_id')]; - $storeCode = Mage::app()->getStore($storeId)->getCode(); - } - $item->setData('_first_store_id', $storeId); - $item->setData('store_code', $storeCode); - } - } - } - } - - parent::_afterLoad(); - } - - /** - * Add Filter by store - * - * @param int|Mage_Core_Model_Store $store - * @return Mage_Cms_Model_Mysql4_Page_Collection - */ - public function addStoreFilter($store, $withAdmin = true) - { - if (!$this->getFlag('store_filter_added')) { - if ($store instanceof Mage_Core_Model_Store) { - $store = array($store->getId()); - } - - $this->getSelect()->join( - array('store_table' => $this->getTable('cms/page_store')), - 'main_table.page_id = store_table.page_id', - array() - ) - ->where('store_table.store_id in (?)', ($withAdmin ? array(0, $store) : $store)) - ->group('main_table.page_id'); - - $this->setFlag('store_filter_added', true); - } - - return $this; - } - - /** - * Get SQL for get record count. - * Extra group by strip added. - * - * @return Varien_Db_Select - */ - public function getSelectCountSql() - { - $countSelect = parent::getSelectCountSql(); - - $countSelect->reset(Zend_Db_Select::GROUP); - - return $countSelect; - } } diff --git a/app/code/core/Mage/Cms/Model/Mysql4/Page/Service.php b/app/code/core/Mage/Cms/Model/Mysql4/Page/Service.php new file mode 100644 index 00000000..ffd82bb0 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Mysql4/Page/Service.php @@ -0,0 +1,35 @@ + + */ +class Mage_Cms_Model_Mysql4_Page_Service extends Mage_Cms_Model_Resource_Page_Service +{ + +} diff --git a/app/code/core/Mage/Cms/Model/Observer.php b/app/code/core/Mage/Cms/Model/Observer.php index 482802b9..4a8ed4c9 100644 --- a/app/code/core/Mage/Cms/Model/Observer.php +++ b/app/code/core/Mage/Cms/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Model/Page.php b/app/code/core/Mage/Cms/Model/Page.php index 5889ed42..db70e758 100644 --- a/app/code/core/Mage/Cms/Model/Page.php +++ b/app/code/core/Mage/Cms/Model/Page.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,6 +28,43 @@ /** * Cms Page Model * + * @method Mage_Cms_Model_Resource_Page _getResource() + * @method Mage_Cms_Model_Resource_Page getResource() + * @method string getTitle() + * @method Mage_Cms_Model_Page setTitle(string $value) + * @method string getRootTemplate() + * @method Mage_Cms_Model_Page setRootTemplate(string $value) + * @method string getMetaKeywords() + * @method Mage_Cms_Model_Page setMetaKeywords(string $value) + * @method string getMetaDescription() + * @method Mage_Cms_Model_Page setMetaDescription(string $value) + * @method string getIdentifier() + * @method Mage_Cms_Model_Page setIdentifier(string $value) + * @method string getContentHeading() + * @method Mage_Cms_Model_Page setContentHeading(string $value) + * @method string getContent() + * @method Mage_Cms_Model_Page setContent(string $value) + * @method string getCreationTime() + * @method Mage_Cms_Model_Page setCreationTime(string $value) + * @method string getUpdateTime() + * @method Mage_Cms_Model_Page setUpdateTime(string $value) + * @method int getIsActive() + * @method Mage_Cms_Model_Page setIsActive(int $value) + * @method int getSortOrder() + * @method Mage_Cms_Model_Page setSortOrder(int $value) + * @method string getLayoutUpdateXml() + * @method Mage_Cms_Model_Page setLayoutUpdateXml(string $value) + * @method string getCustomTheme() + * @method Mage_Cms_Model_Page setCustomTheme(string $value) + * @method string getCustomRootTemplate() + * @method Mage_Cms_Model_Page setCustomRootTemplate(string $value) + * @method string getCustomLayoutUpdateXml() + * @method Mage_Cms_Model_Page setCustomLayoutUpdateXml(string $value) + * @method string getCustomThemeFrom() + * @method Mage_Cms_Model_Page setCustomThemeFrom(string $value) + * @method string getCustomThemeTo() + * @method Mage_Cms_Model_Page setCustomThemeTo(string $value) + * * @category Mage * @package Mage_Cms * @author Magento Core Team diff --git a/app/code/core/Mage/Cms/Model/Resource/Block.php b/app/code/core/Mage/Cms/Model/Resource/Block.php new file mode 100755 index 00000000..0cea3b84 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Resource/Block.php @@ -0,0 +1,242 @@ + + */ +class Mage_Cms_Model_Resource_Block extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('cms/block', 'block_id'); + } + + /** + * Process block data before deleting + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Page + */ + protected function _beforeDelete(Mage_Core_Model_Abstract $object) + { + $condition = array( + 'block_id = ?' => (int) $object->getId(), + ); + + $this->_getWriteAdapter()->delete($this->getTable('cms/block_store'), $condition); + + return parent::_beforeDelete($object); + } + + /** + * Perform operations before object save + * + * @param Mage_Cms_Model_Block $object + * @return Mage_Cms_Model_Resource_Block + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$this->getIsUniqueBlockToStores($object)) { + Mage::throwException(Mage::helper('cms')->__('A block identifier with the same properties already exists in the selected store.')); + } + + if (! $object->getId()) { + $object->setCreationTime(Mage::getSingleton('core/date')->gmtDate()); + } + $object->setUpdateTime(Mage::getSingleton('core/date')->gmtDate()); + return $this; + } + + /** + * Perform operations after object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Block + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $oldStores = $this->lookupStoreIds($object->getId()); + $newStores = (array)$object->getStores(); + + $table = $this->getTable('cms/block_store'); + $insert = array_diff($newStores, $oldStores); + $delete = array_diff($oldStores, $newStores); + + if ($delete) { + $where = array( + 'block_id = ?' => (int) $object->getId(), + 'store_id IN (?)' => $delete + ); + + $this->_getWriteAdapter()->delete($table, $where); + } + + if ($insert) { + $data = array(); + + foreach ($insert as $storeId) { + $data[] = array( + 'block_id' => (int) $object->getId(), + 'store_id' => (int) $storeId + ); + } + + $this->_getWriteAdapter()->insertMultiple($table, $data); + } + + return parent::_afterSave($object); + + } + + /** + * Load an object using 'identifier' field if there's no field specified and value is not numeric + * + * @param Mage_Core_Model_Abstract $object + * @param mixed $value + * @param string $field + * @return Mage_Cms_Model_Resource_Block + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (!is_numeric($value) && is_null($field)) { + $field = 'identifier'; + } + + return parent::load($object, $value, $field); + } + + /** + * Perform operations after object load + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Block + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + if ($object->getId()) { + $stores = $this->lookupStoreIds($object->getId()); + $object->setData('store_id', $stores); + $object->setData('stores', $stores); + } + + return parent::_afterLoad($object); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Cms_Model_Block $object + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + + if ($object->getStoreId()) { + $stores = array( + (int) $object->getStoreId(), + Mage_Core_Model_App::ADMIN_STORE_ID, + ); + + $select->join( + array('cbs' => $this->getTable('cms/block_store')), + $this->getMainTable().'.block_id = cbs.block_id', + array('store_id') + )->where('is_active = ?', 1) + ->where('cbs.store_id in (?) ', $stores) + ->order('store_id DESC') + ->limit(1); + } + + return $select; + } + + /** + * Check for unique of identifier of block to selected store(s). + * + * @param Mage_Core_Model_Abstract $object + * @return bool + */ + public function getIsUniqueBlockToStores(Mage_Core_Model_Abstract $object) + { + if (Mage::app()->isSingleStoreMode()) { + $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID); + } else { + $stores = (array)$object->getData('stores'); + } + + $select = $this->_getReadAdapter()->select() + ->from(array('cb' => $this->getMainTable())) + ->join( + array('cbs' => $this->getTable('cms/block_store')), + 'cb.block_id = cbs.block_id', + array() + )->where('cb.identifier = ?', $object->getData('identifier')) + ->where('cbs.store_id IN (?)', $stores); + + if ($object->getId()) { + $select->where('cb.block_id <> ?', $object->getId()); + } + + if ($this->_getReadAdapter()->fetchRow($select)) { + return false; + } + + return true; + } + + /** + * Get store ids to which specified item is assigned + * + * @param int $id + * @return array + */ + public function lookupStoreIds($id) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($this->getTable('cms/block_store'), 'store_id') + ->where('block_id = :block_id'); + + $binds = array( + ':block_id' => (int) $id + ); + + return $adapter->fetchCol($select, $binds); + } +} diff --git a/app/code/core/Mage/Cms/Model/Resource/Block/Collection.php b/app/code/core/Mage/Cms/Model/Resource/Block/Collection.php new file mode 100755 index 00000000..05afb6cc --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Resource/Block/Collection.php @@ -0,0 +1,118 @@ + + */ +class Mage_Cms_Model_Resource_Block_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('cms/block'); + $this->_map['fields']['store'] = 'store_table.store_id'; + } + + /** + * Returns pairs block_id - title + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('block_id', 'title'); + } + + /** + * Add filter by store + * + * @param int|Mage_Core_Model_Store $store + * @param bool $withAdmin + * @return Mage_Cms_Model_Resource_Block_Collection + */ + public function addStoreFilter($store, $withAdmin = true) + { + if ($store instanceof Mage_Core_Model_Store) { + $store = array($store->getId()); + } + + if (!is_array($store)) { + $store = array($store); + } + + if ($withAdmin) { + $store[] = Mage_Core_Model_App::ADMIN_STORE_ID; + } + + $this->addFilter('store', array('in' => $store), 'public'); + + return $this; + } + + /** + * Get SQL for get record count. + * Extra GROUP BY strip added. + * + * @return Varien_Db_Select + */ + public function getSelectCountSql() + { + $countSelect = parent::getSelectCountSql(); + + $countSelect->reset(Zend_Db_Select::GROUP); + + return $countSelect; + } + + /** + * Join store relation table if there is store filter + */ + protected function _renderFiltersBefore() + { + if ($this->getFilter('store')) { + $this->getSelect()->join( + array('store_table' => $this->getTable('cms/block_store')), + 'main_table.block_id = store_table.block_id', + array() + )->group('main_table.block_id'); + + /* + * Allow analytic functions usage because of one field grouping + */ + $this->_useAnalyticFunction = true; + } + return parent::_renderFiltersBefore(); + } + +} diff --git a/app/code/core/Mage/Cms/Model/Resource/Page.php b/app/code/core/Mage/Cms/Model/Resource/Page.php new file mode 100755 index 00000000..d2814500 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Resource/Page.php @@ -0,0 +1,416 @@ + + */ +class Mage_Cms_Model_Resource_Page extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Store model + * + * @var null|Mage_Core_Model_Store + */ + protected $_store = null; + + /** + * Initialize resource model + * + */ + protected function _construct() + { + $this->_init('cms/page', 'page_id'); + } + + /** + * Process page data before deleting + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Page + */ + protected function _beforeDelete(Mage_Core_Model_Abstract $object) + { + $condition = array( + 'page_id = ?' => (int) $object->getId(), + ); + + $this->_getWriteAdapter()->delete($this->getTable('cms/page_store'), $condition); + + return parent::_beforeDelete($object); + } + + /** + * Process page data before saving + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Page + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + /* + * For two attributes which represent timestamp data in DB + * we should make converting such as: + * If they are empty we need to convert them into DB + * type NULL so in DB they will be empty and not some default value + */ + foreach (array('custom_theme_from', 'custom_theme_to') as $field) { + $value = !$object->getData($field) ? null : $object->getData($field); + $object->setData($field, $this->formatDate($value)); + } + + if (!$this->getIsUniquePageToStores($object)) { + Mage::throwException(Mage::helper('cms')->__('A page URL key for specified store already exists.')); + } + + if (!$this->isValidPageIdentifier($object)) { + Mage::throwException(Mage::helper('cms')->__('The page URL key contains capital letters or disallowed symbols.')); + } + + if ($this->isNumericPageIdentifier($object)) { + Mage::throwException(Mage::helper('cms')->__('The page URL key cannot consist only of numbers.')); + } + + // modify create / update dates + if ($object->isObjectNew() && !$object->hasCreationTime()) { + $object->setCreationTime(Mage::getSingleton('core/date')->gmtDate()); + } + + $object->setUpdateTime(Mage::getSingleton('core/date')->gmtDate()); + + return parent::_beforeSave($object); + } + + /** + * Assign page to store views + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Page + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $oldStores = $this->lookupStoreIds($object->getId()); + $newStores = (array)$object->getStores(); + if (empty($newStores)) { + $newStores = (array)$object->getStoreId(); + } + $table = $this->getTable('cms/page_store'); + $insert = array_diff($newStores, $oldStores); + $delete = array_diff($oldStores, $newStores); + + if ($delete) { + $where = array( + 'page_id = ?' => (int) $object->getId(), + 'store_id IN (?)' => $delete + ); + + $this->_getWriteAdapter()->delete($table, $where); + } + + if ($insert) { + $data = array(); + + foreach ($insert as $storeId) { + $data[] = array( + 'page_id' => (int) $object->getId(), + 'store_id' => (int) $storeId + ); + } + + $this->_getWriteAdapter()->insertMultiple($table, $data); + } + + return parent::_afterSave($object); + } + + /** + * Load an object using 'identifier' field if there's no field specified and value is not numeric + * + * @param Mage_Core_Model_Abstract $object + * @param mixed $value + * @param string $field + * @return Mage_Cms_Model_Resource_Page + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (!is_numeric($value) && is_null($field)) { + $field = 'identifier'; + } + + return parent::load($object, $value, $field); + } + + /** + * Perform operations after object load + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Cms_Model_Resource_Page + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + if ($object->getId()) { + $stores = $this->lookupStoreIds($object->getId()); + + $object->setData('store_id', $stores); + + } + + return parent::_afterLoad($object); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Cms_Model_Page $object + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + + if ($object->getStoreId()) { + $storeIds = array(Mage_Core_Model_App::ADMIN_STORE_ID, (int)$object->getStoreId()); + $select->join( + array('cms_page_store' => $this->getTable('cms/page_store')), + $this->getMainTable() . '.page_id = cms_page_store.page_id', + array()) + ->where('is_active = ?', 1) + ->where('cms_page_store.store_id IN (?)', $storeIds) + ->order('cms_page_store.store_id DESC') + ->limit(1); + } + + return $select; + } + + /** + * Retrieve load select with filter by identifier, store and activity + * + * @param string $identifier + * @param int|array $store + * @param int $isActive + * @return Varien_Db_Select + */ + protected function _getLoadByIdentifierSelect($identifier, $store, $isActive = null) + { + $select = $this->_getReadAdapter()->select() + ->from(array('cp' => $this->getMainTable())) + ->join( + array('cps' => $this->getTable('cms/page_store')), + 'cp.page_id = cps.page_id', + array()) + ->where('cp.identifier = ?', $identifier) + ->where('cps.store_id IN (?)', $store); + + if (!is_null($isActive)) { + $select->where('cp.is_active = ?', $isActive); + } + + return $select; + } + + /** + * Check for unique of identifier of page to selected store(s). + * + * @param Mage_Core_Model_Abstract $object + * @return bool + */ + public function getIsUniquePageToStores(Mage_Core_Model_Abstract $object) + { + if (Mage::app()->isSingleStoreMode() || !$object->hasStores()) { + $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID); + } else { + $stores = (array)$object->getData('stores'); + } + + $select = $this->_getLoadByIdentifierSelect($object->getData('identifier'), $stores); + + if ($object->getId()) { + $select->where('cps.page_id <> ?', $object->getId()); + } + + if ($this->_getWriteAdapter()->fetchRow($select)) { + return false; + } + + return true; + } + + /** + * Check whether page identifier is numeric + * + * @date Wed Mar 26 18:12:28 EET 2008 + * + * @param Mage_Core_Model_Abstract $object + * @return bool + */ + protected function isNumericPageIdentifier(Mage_Core_Model_Abstract $object) + { + return preg_match('/^[0-9]+$/', $object->getData('identifier')); + } + + /** + * Check whether page identifier is valid + * + * @param Mage_Core_Model_Abstract $object + * @return bool + */ + protected function isValidPageIdentifier(Mage_Core_Model_Abstract $object) + { + return preg_match('/^[a-z0-9][a-z0-9_\/-]+(\.[a-z0-9_-]+)?$/', $object->getData('identifier')); + } + + + + /** + * Check if page identifier exist for specific store + * return page id if page exists + * + * @param string $identifier + * @param int $storeId + * @return int + */ + public function checkIdentifier($identifier, $storeId) + { + $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID, $storeId); + $select = $this->_getLoadByIdentifierSelect($identifier, $stores, 1); + $select->reset(Zend_Db_Select::COLUMNS) + ->columns('cp.page_id') + ->order('cps.store_id DESC') + ->limit(1); + + return $this->_getReadAdapter()->fetchOne($select); + } + + /** + * Retrieves cms page title from DB by passed identifier. + * + * @param string $identifier + * @return string|false + */ + public function getCmsPageTitleByIdentifier($identifier) + { + $stores = array(Mage_Core_Model_App::ADMIN_STORE_ID); + if ($this->_store) { + $stores[] = (int)$this->getStore()->getId(); + } + + $select = $this->_getLoadByIdentifierSelect($identifier, $stores); + $select->reset(Zend_Db_Select::COLUMNS) + ->columns('cp.title') + ->order('cps.store_id DESC') + ->limit(1); + + return $this->_getReadAdapter()->fetchOne($select); + } + + /** + * Retrieves cms page title from DB by passed id. + * + * @param string $id + * @return string|false + */ + public function getCmsPageTitleById($id) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($this->getMainTable(), 'title') + ->where('page_id = :page_id'); + + $binds = array( + 'page_id' => (int) $id + ); + + return $adapter->fetchOne($select, $binds); + } + + /** + * Retrieves cms page identifier from DB by passed id. + * + * @param string $id + * @return string|false + */ + public function getCmsPageIdentifierById($id) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($this->getMainTable(), 'identifier') + ->where('page_id = :page_id'); + + $binds = array( + 'page_id' => (int) $id + ); + + return $adapter->fetchOne($select, $binds); + } + + /** + * Get store ids to which specified item is assigned + * + * @param int $id + * @return array + */ + public function lookupStoreIds($pageId) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from($this->getTable('cms/page_store'), 'store_id') + ->where('page_id = ?',(int)$pageId); + + return $adapter->fetchCol($select); + } + + /** + * Set store model + * + * @param Mage_Core_Model_Store $store + * @return Mage_Cms_Model_Resource_Page + */ + public function setStore($store) + { + $this->_store = $store; + return $this; + } + + /** + * Retrieve store model + * + * @return Mage_Core_Model_Store + */ + public function getStore() + { + return Mage::app()->getStore($this->_store); + } +} diff --git a/app/code/core/Mage/Cms/Model/Resource/Page/Collection.php b/app/code/core/Mage/Cms/Model/Resource/Page/Collection.php new file mode 100755 index 00000000..ff2ea2e4 --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Resource/Page/Collection.php @@ -0,0 +1,206 @@ + + */ +class Mage_Cms_Model_Resource_Page_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Load data for preview flag + * + * @var bool + */ + protected $_previewFlag; + + + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('cms/page'); + $this->_map['fields']['page_id'] = 'main_table.page_id'; + $this->_map['fields']['store'] = 'store_table.store_id'; + } + + /** + * deprecated after 1.4.0.1, use toOptionIdArray() + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('identifier', 'title'); + } + + /** + * Returns pairs identifier - title for unique identifiers + * and pairs identifier|page_id - title for non-unique after first + * + * @return array + */ + public function toOptionIdArray() + { + $res = array(); + $existingIdentifiers = array(); + foreach ($this as $item) { + $identifier = $item->getData('identifier'); + + $data['value'] = $identifier; + $data['label'] = $item->getData('title'); + + if (in_array($identifier, $existingIdentifiers)) { + $data['value'] .= '|' . $item->getData('page_id'); + } else { + $existingIdentifiers[] = $identifier; + } + + $res[] = $data; + } + + return $res; + } + + /** + * Set first store flag + * + * @param bool $flag + * @return Mage_Cms_Model_Resource_Page_Collection + */ + public function setFirstStoreFlag($flag = false) + { + $this->_previewFlag = $flag; + return $this; + } + + /** + * Perform operations after collection load + * + * @return Mage_Cms_Model_Resource_Page_Collection + */ + protected function _afterLoad() + { + if ($this->_previewFlag) { + $items = $this->getColumnValues('page_id'); + $connection = $this->getConnection(); + if (count($items)) { + $select = $connection->select() + ->from(array('cps'=>$this->getTable('cms/page_store'))) + ->where('cps.page_id IN (?)', $items); + + if ($result = $connection->fetchPairs($select)) { + foreach ($this as $item) { + if (!isset($result[$item->getData('page_id')])) { + continue; + } + if ($result[$item->getData('page_id')] == 0) { + $stores = Mage::app()->getStores(false, true); + $storeId = current($stores)->getId(); + $storeCode = key($stores); + } else { + $storeId = $result[$item->getData('page_id')]; + $storeCode = Mage::app()->getStore($storeId)->getCode(); + } + $item->setData('_first_store_id', $storeId); + $item->setData('store_code', $storeCode); + } + } + } + } + + return parent::_afterLoad(); + } + + /** + * Add filter by store + * + * @param int|Mage_Core_Model_Store $store + * @param bool $withAdmin + * @return Mage_Cms_Model_Resource_Page_Collection + */ + public function addStoreFilter($store, $withAdmin = true) + { + if (!$this->getFlag('store_filter_added')) { + if ($store instanceof Mage_Core_Model_Store) { + $store = array($store->getId()); + } + + if (!is_array($store)) { + $store = array($store); + } + + if ($withAdmin) { + $store[] = Mage_Core_Model_App::ADMIN_STORE_ID; + } + + $this->addFilter('store', array('in' => $store), 'public'); + } + return $this; + } + + /** + * Join store relation table if there is store filter + */ + protected function _renderFiltersBefore() + { + if ($this->getFilter('store')) { + $this->getSelect()->join( + array('store_table' => $this->getTable('cms/page_store')), + 'main_table.page_id = store_table.page_id', + array() + )->group('main_table.page_id'); + + /* + * Allow analytic functions usage because of one field grouping + */ + $this->_useAnalyticFunction = true; + } + return parent::_renderFiltersBefore(); + } + + + /** + * Get SQL for get record count. + * Extra GROUP BY strip added. + * + * @return Varien_Db_Select + */ + public function getSelectCountSql() + { + $countSelect = parent::getSelectCountSql(); + + $countSelect->reset(Zend_Db_Select::GROUP); + + return $countSelect; + } +} diff --git a/app/code/core/Mage/Cms/Model/Resource/Page/Service.php b/app/code/core/Mage/Cms/Model/Resource/Page/Service.php new file mode 100644 index 00000000..000488da --- /dev/null +++ b/app/code/core/Mage/Cms/Model/Resource/Page/Service.php @@ -0,0 +1,97 @@ + + */ +class Mage_Cms_Model_Resource_Page_Service extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Init cms page service model + * + */ + protected function _construct() + { + $this->_init('cms/page', 'page_id'); + } + + /** + * Unlinks from $fromStoreId store pages that have same identifiers as pages in $byStoreId + * + * Routine is intented to be used before linking pages of some store ($byStoreId) to other store ($fromStoreId) + * to prevent duplication of url keys + * + * Resolved $byLinkTable can be provided when restoring links from some backup table + * + * @param int $fromStoreId + * @param int $byStoreId + * @param string $byLinkTable + * + * @return Mage_Cms_Model_Mysql4_Page_Service + */ + public function unlinkConflicts($fromStoreId, $byStoreId, $byLinkTable = null) + { + $readAdapter = $this->_getReadAdapter(); + + $linkTable = $this->getTable('cms/page_store'); + $mainTable = $this->getMainTable(); + $byLinkTable = $byLinkTable ? $byLinkTable : $linkTable; + + // Select all page ids of $fromStoreId that have identifiers as some pages in $byStoreId + $select = $readAdapter->select() + ->from(array('from_link' => $linkTable), 'page_id') + ->join( + array('from_entity' => $mainTable), + $readAdapter->quoteInto( + 'from_entity.page_id = from_link.page_id AND from_link.store_id = ?', + $fromStoreId + ), + array() + )->join( + array('by_entity' => $mainTable), + 'from_entity.identifier = by_entity.identifier AND from_entity.page_id != by_entity.page_id', + array() + )->join( + array('by_link' => $byLinkTable), + $readAdapter->quoteInto('by_link.page_id = by_entity.page_id AND by_link.store_id = ?', $byStoreId), + array() + ); + $pageIds = $readAdapter->fetchCol($select); + + // Unlink found pages + if ($pageIds) { + $writeAdapter = $this->_getWriteAdapter(); + $where = array( + 'page_id IN (?)' => $pageIds, + 'AND store_id = ?' => $fromStoreId + ); + $writeAdapter->delete($linkTable, $where); + } + return $this; + } +} diff --git a/app/code/core/Mage/Cms/Model/Template/Filter.php b/app/code/core/Mage/Cms/Model/Template/Filter.php index d6611092..5afef175 100644 --- a/app/code/core/Mage/Cms/Model/Template/Filter.php +++ b/app/code/core/Mage/Cms/Model/Template/Filter.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php b/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php index 6cfb33d2..2b703d04 100644 --- a/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -39,6 +39,7 @@ class Mage_Cms_Model_Wysiwyg_Config extends Varien_Object const WYSIWYG_ENABLED = 'enabled'; const WYSIWYG_HIDDEN = 'hidden'; const WYSIWYG_DISABLED = 'disabled'; + const IMAGE_DIRECTORY = 'wysiwyg'; /** * Return Wysiwyg config as Varien_Object @@ -68,19 +69,27 @@ public function getConfig($data = array()) 'add_widgets' => true, 'no_display' => false, 'translator' => Mage::helper('cms'), - 'files_browser_window_url' => Mage::getSingleton('adminhtml/url')->getUrl('*/cms_wysiwyg_images/index'), - 'files_browser_window_width' => (int) Mage::getConfig()->getNode('adminhtml/cms/browser/window_width'), - 'files_browser_window_height' => (int) Mage::getConfig()->getNode('adminhtml/cms/browser/window_height'), 'encode_directives' => true, 'directives_url' => Mage::getSingleton('adminhtml/url')->getUrl('*/cms_wysiwyg/directive'), - 'popup_css' => Mage::getBaseUrl('js').'mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css', - 'content_css' => Mage::getBaseUrl('js').'mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css', + 'popup_css' => + Mage::getBaseUrl('js').'mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css', + 'content_css' => + Mage::getBaseUrl('js').'mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css', 'width' => '100%', 'plugins' => array() )); $config->setData('directives_url_quoted', preg_quote($config->getData('directives_url'))); + if (Mage::getSingleton('admin/session')->isAllowed('cms/media_gallery')) { + $config->addData(array( + 'add_images' => true, + 'files_browser_window_url' => Mage::getSingleton('adminhtml/url')->getUrl('*/cms_wysiwyg_images/index'), + 'files_browser_window_width' => (int) Mage::getConfig()->getNode('adminhtml/cms/browser/window_width'), + 'files_browser_window_height'=> (int) Mage::getConfig()->getNode('adminhtml/cms/browser/window_height'), + )); + } + if (is_array($data)) { $config->addData($data); } @@ -95,11 +104,11 @@ public function getConfig($data = array()) * * @return string */ - public function getSkinImagePlaceholderUrl() + public function getSkinImagePlaceholderUrl() { return Mage::getDesign()->getSkinUrl('images/wysiwyg/skin_image.png'); } - + /** * Check whether Wysiwyg is enabled or not * @@ -107,7 +116,13 @@ public function getSkinImagePlaceholderUrl() */ public function isEnabled() { - return in_array(Mage::getStoreConfig('cms/wysiwyg/enabled'), array(self::WYSIWYG_ENABLED, self::WYSIWYG_HIDDEN)); + $storeId = $this->getStoreId(); + if (!is_null($storeId)) { + $wysiwygState = Mage::getStoreConfig('cms/wysiwyg/enabled', $storeId); + } else { + $wysiwygState = Mage::getStoreConfig('cms/wysiwyg/enabled'); + } + return in_array($wysiwygState, array(self::WYSIWYG_ENABLED, self::WYSIWYG_HIDDEN)); } /** diff --git a/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php index 1c0a0490..a8f0c52c 100644 --- a/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -59,6 +59,16 @@ class Mage_Cms_Model_Wysiwyg_Images_Storage extends Varien_Object */ public function getDirsCollection($path) { + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + $subDirectories = Mage::getModel('core/file_storage_directory_database')->getSubdirectories($path); + foreach ($subDirectories as $directory) { + $fullPath = rtrim($path, DS) . DS . $directory['name']; + if (!file_exists($fullPath)) { + mkdir($fullPath, 0777, true); + } + } + } + $conditions = array('reg_exp' => array(), 'plain' => array()); foreach ($this->getConfig()->dirs->exclude->children() as $dir) { @@ -97,6 +107,15 @@ public function getDirsCollection($path) */ public function getFilesCollection($path, $type = null) { + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + $files = Mage::getModel('core/file_storage_database')->getDirectoryFiles($path); + + $fileStorageModel = Mage::getModel('core/file_storage_file'); + foreach ($files as $file) { + $fileStorageModel->saveFile($file); + } + } + $collection = $this->getCollection($path) ->setCollectDirs(false) ->setCollectFiles(true) @@ -180,6 +199,11 @@ public function createDirectory($name, $path) $io = new Varien_Io_File(); if ($io->mkdir($newPath)) { + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + $relativePath = Mage::helper('core/file_storage_database')->getMediaRelativePath($newPath); + Mage::getModel('core/file_storage_directory_database')->createRecursive($relativePath); + } + $result = array( 'name' => $name, 'short_name' => $this->getHelper()->getShortFilename($name), @@ -209,6 +233,9 @@ public function deleteDirectory($path) $io = new Varien_Io_File(); + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + Mage::getModel('core/file_storage_directory_database')->deleteDirectory($path); + } if (!$io->rmdir($path, true)) { Mage::throwException(Mage::helper('cms')->__('Cannot delete directory %s.', $path)); } @@ -228,9 +255,12 @@ public function deleteFile($target) { $io = new Varien_Io_File(); $io->rm($target); + Mage::helper('core/file_storage_database')->deleteFile($target); + $thumb = $this->getThumbnailPath($target, true); if ($thumb) { $io->rm($thumb); + Mage::helper('core/file_storage_database')->deleteFile($thumb); } return $this; } @@ -246,7 +276,7 @@ public function deleteFile($target) */ public function uploadFile($targetPath, $type = null) { - $uploader = new Varien_File_Uploader('image'); + $uploader = new Mage_Core_Model_File_Uploader('image'); if ($allowed = $this->getAllowedExtensions($type)) { $uploader->setAllowedExtensions($allowed); } diff --git a/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php index d8efa81c..52ac19a2 100644 --- a/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php +++ b/app/code/core/Mage/Cms/Model/Wysiwyg/Images/Storage/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/controllers/IndexController.php b/app/code/core/Mage/Cms/controllers/IndexController.php index fbd2ef9c..b0eaa72c 100644 --- a/app/code/core/Mage/Cms/controllers/IndexController.php +++ b/app/code/core/Mage/Cms/controllers/IndexController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/controllers/PageController.php b/app/code/core/Mage/Cms/controllers/PageController.php index 4419a373..48d9b7da 100644 --- a/app/code/core/Mage/Cms/controllers/PageController.php +++ b/app/code/core/Mage/Cms/controllers/PageController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/data/cms_setup/data-install-1.6.0.0.php b/app/code/core/Mage/Cms/data/cms_setup/data-install-1.6.0.0.php new file mode 100644 index 00000000..92c25128 --- /dev/null +++ b/app/code/core/Mage/Cms/data/cms_setup/data-install-1.6.0.0.php @@ -0,0 +1,99 @@ + 'Footer Links', + 'identifier' => 'footer_links', + 'content' => "", + 'is_active' => 1, + 'stores' => 0 + ) +); + +$cmsPages = array( + array( + 'title' => '404 Not Found 1', + 'root_template' => 'two_columns_right', + 'meta_keywords' => 'Page keywords', + 'meta_description' + => 'Page description', + 'identifier' => 'no-route', + 'content' => "

    Whoops, our bad...

    \r\n
    \r\n
    The page you requested was not found, and we have a fine guess why.
    \r\n
    \r\n
      \r\n
    • If you typed the URL directly, please make sure the spelling is correct.
    • \r\n
    • If you clicked on a link to get here, the link is outdated.
    • \r\n
    \r\n
    \r\n
    \r\n
    What can you do?
    \r\n
    Have no fear, help is near! There are many ways you can get back on track with Magento Store.
    \r\n
    \r\n
      \r\n
    • Go back to the previous page.
    • \r\n
    • Use the search bar at the top of the page to search for your products.
    • \r\n
    • Follow these links to get you back on track!
      Store Home | My Account
    \r\n", + 'is_active' => 1, + 'stores' => array(0), + 'sort_order' => 0 + ), + array( + 'title' => 'Home page', + 'root_template' => 'two_columns_right', + 'identifier' => 'home', + 'content' => "

    Home Page

    \r\n", + 'is_active' => 1, + 'stores' => array(0), + 'sort_order' => 0 + ), + array( + 'title' => 'About Us', + 'root_template' => 'two_columns_right', + 'identifier' => 'about-magento-demo-store', + 'content' => "
    \r\n

    About Magento Store

    \r\n
    \r\n
    \r\n

    \"Varien\"

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede.

    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit.

    \r\n

    Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus. Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi. Vestibulum sapien dolor, aliquet nec, porta ac, malesuada a, libero. Praesent feugiat purus eget est. Nulla facilisi. Vestibulum tincidunt sapien eu velit. Mauris purus. Maecenas eget mauris eu orci accumsan feugiat. Pellentesque eget velit. Nunc tincidunt.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus.

    \r\n

    Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi.

    \r\n
    \r\n

    To all of you, from all of us at Magento Store - Thank you and Happy eCommerce!

    \r\n

    John Doe
    Some important guy

    \r\n
    ", + 'is_active' => 1, + 'stores' => array(0), + 'sort_order' => 0 + ), + array( + 'title' => 'Customer Service', + 'root_template' => 'three_columns', + 'identifier' => 'customer-service', + 'content' => "
    \r\n

    Customer Service

    \r\n
    \r\n\r\n
    \r\n
    Shipping & Delivery
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Privacy & Security
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Returns & Replacements
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Ordering
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Payment, Pricing & Promotions
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Viewing Orders
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Updating Account Information
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    ", + 'is_active' => 1, + 'stores' => array(0), + 'sort_order' => 0 + ), + array( + 'title' => 'Enable Cookies', + 'root_template' => 'one_column', + 'identifier' => 'enable-cookies', + 'content' => "
    \r\n
      \r\n
    • \r\n
        \r\n
      • Please enable cookies in your web browser to continue.
      • \r\n
      \r\n
    • \r\n
    \r\n
    \r\n

    What are Cookies?

    \r\n
    \r\n

    Cookies are short pieces of data that are sent to your computer when you visit a website. On later visits, this data is then returned to that website. Cookies allow us to recognize you automatically whenever you visit our site so that we can personalize your experience and provide you with better service. We also use cookies (and similar browser data, such as Flash cookies) for fraud prevention and other purposes. If your web browser is set to refuse cookies from our website, you will not be able to complete a purchase or take advantage of certain features of our website, such as storing items in your Shopping Cart or receiving personalized recommendations. As a result, we strongly encourage you to configure your web browser to accept cookies from our website.

    \r\n

    Enabling Cookies

    \r\n \r\n

    Internet Explorer 7.x

    \r\n
      \r\n
    1. \r\n

      Start Internet Explorer

      \r\n
    2. \r\n
    3. \r\n

      Under the Tools menu, click Internet Options

      \r\n

      \"\"

      \r\n
    4. \r\n
    5. \r\n

      Click the Privacy tab

      \r\n

      \"\"

      \r\n
    6. \r\n
    7. \r\n

      Click the Advanced button

      \r\n

      \"\"

      \r\n
    8. \r\n
    9. \r\n

      Put a check mark in the box for Override Automatic Cookie Handling, put another check mark in the Always accept session cookies box

      \r\n

      \"\"

      \r\n
    10. \r\n
    11. \r\n

      Click OK

      \r\n

      \"\"

      \r\n
    12. \r\n
    13. \r\n

      Click OK

      \r\n

      \"\"

      \r\n
    14. \r\n
    15. \r\n

      Restart Internet Explore

      \r\n
    16. \r\n
    \r\n

    Back to Top

    \r\n

    Internet Explorer 6.x

    \r\n
      \r\n
    1. \r\n

      Select Internet Options from the Tools menu

      \r\n

      \"\"

      \r\n
    2. \r\n
    3. \r\n

      Click on the Privacy tab

      \r\n
    4. \r\n
    5. \r\n

      Click the Default button (or manually slide the bar down to Medium) under Settings. Click OK

      \r\n

      \"\"

      \r\n
    6. \r\n
    \r\n

    Back to Top

    \r\n

    Mozilla/Firefox

    \r\n
      \r\n
    1. \r\n

      Click on the Tools-menu in Mozilla

      \r\n
    2. \r\n
    3. \r\n

      Click on the Options... item in the menu - a new window open

      \r\n
    4. \r\n
    5. \r\n

      Click on the Privacy selection in the left part of the window. (See image below)

      \r\n

      \"\"

      \r\n
    6. \r\n
    7. \r\n

      Expand the Cookies section

      \r\n
    8. \r\n
    9. \r\n

      Check the Enable cookies and Accept cookies normally checkboxes

      \r\n
    10. \r\n
    11. \r\n

      Save changes by clicking Ok.

      \r\n
    12. \r\n
    \r\n

    Back to Top

    \r\n

    Opera 7.x

    \r\n
      \r\n
    1. \r\n

      Click on the Tools menu in Opera

      \r\n
    2. \r\n
    3. \r\n

      Click on the Preferences... item in the menu - a new window open

      \r\n
    4. \r\n
    5. \r\n

      Click on the Privacy selection near the bottom left of the window. (See image below)

      \r\n

      \"\"

      \r\n
    6. \r\n
    7. \r\n

      The Enable cookies checkbox must be checked, and Accept all cookies should be selected in the "Normal cookies" drop-down

      \r\n
    8. \r\n
    9. \r\n

      Save changes by clicking Ok

      \r\n
    10. \r\n
    \r\n

    Back to Top

    \r\n
    \r\n", + 'is_active' => 1, + 'stores' => array(0) + ) +); + +/** + * Insert default blocks + */ +foreach ($cmsBlocks as $data) { + Mage::getModel('cms/block')->setData($data)->save(); +} + +/** + * Insert default and system pages + */ +foreach ($cmsPages as $data) { + Mage::getModel('cms/page')->setData($data)->save(); +} diff --git a/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php b/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php new file mode 100644 index 00000000..32588aeb --- /dev/null +++ b/app/code/core/Mage/Cms/data/cms_setup/data-upgrade-1.6.0.0.0-1.6.0.0.1.php @@ -0,0 +1,272 @@ + + Please replace this text with you Privacy Policy. + Please add any additional cookies your website uses below (e.g., Google Analytics) +

    +

    + This privacy policy sets out how {{config path="general/store_information/name"}} uses and protects any information + that you give {{config path="general/store_information/name"}} when you use this website. + {{config path="general/store_information/name"}} is committed to ensuring that your privacy is protected. + Should we ask you to provide certain information by which you can be identified when using this website, + then you can be assured that it will only be used in accordance with this privacy statement. + {{config path="general/store_information/name"}} may change this policy from time to time by updating this page. + You should check this page from time to time to ensure that you are happy with any changes. +

    +

    What we collect

    +

    We may collect the following information:

    +
      +
    • name
    • +
    • contact information including email address
    • +
    • demographic information such as postcode, preferences and interests
    • +
    • other information relevant to customer surveys and/or offers
    • +
    +

    + For the exhaustive list of cookies we collect see the List of cookies we collect section. +

    +

    What we do with the information we gather

    +

    + We require this information to understand your needs and provide you with a better service, + and in particular for the following reasons: +

    +
      +
    • Internal record keeping.
    • +
    • We may use the information to improve our products and services.
    • +
    • + We may periodically send promotional emails about new products, special offers or other information which we + think you may find interesting using the email address which you have provided. +
    • +
    • + From time to time, we may also use your information to contact you for market research purposes. + We may contact you by email, phone, fax or mail. We may use the information to customise the website + according to your interests. +
    • +
    +

    Security

    +

    + We are committed to ensuring that your information is secure. In order to prevent unauthorised access or disclosure, + we have put in place suitable physical, electronic and managerial procedures to safeguard and secure + the information we collect online. +

    +

    How we use cookies

    +

    + A cookie is a small file which asks permission to be placed on your computer's hard drive. + Once you agree, the file is added and the cookie helps analyse web traffic or lets you know when you visit + a particular site. Cookies allow web applications to respond to you as an individual. The web application + can tailor its operations to your needs, likes and dislikes by gathering and remembering information about + your preferences. +

    +

    + We use traffic log cookies to identify which pages are being used. This helps us analyse data about web page traffic + and improve our website in order to tailor it to customer needs. We only use this information for statistical + analysis purposes and then the data is removed from the system. +

    +

    + Overall, cookies help us provide you with a better website, by enabling us to monitor which pages you find useful + and which you do not. A cookie in no way gives us access to your computer or any information about you, + other than the data you choose to share with us. You can choose to accept or decline cookies. + Most web browsers automatically accept cookies, but you can usually modify your browser setting + to decline cookies if you prefer. This may prevent you from taking full advantage of the website. +

    +

    Links to other websites

    +

    + Our website may contain links to other websites of interest. However, once you have used these links + to leave our site, you should note that we do not have any control over that other website. + Therefore, we cannot be responsible for the protection and privacy of any information which you provide whilst + visiting such sites and such sites are not governed by this privacy statement. + You should exercise caution and look at the privacy statement applicable to the website in question. +

    +

    Controlling your personal information

    +

    You may choose to restrict the collection or use of your personal information in the following ways:

    +
      +
    • + whenever you are asked to fill in a form on the website, look for the box that you can click to indicate + that you do not want the information to be used by anybody for direct marketing purposes +
    • +
    • + if you have previously agreed to us using your personal information for direct marketing purposes, + you may change your mind at any time by writing to or emailing us at + {{config path="trans_email/ident_general/email"}} +
    • +
    +

    + We will not sell, distribute or lease your personal information to third parties unless we have your permission + or are required by law to do so. We may use your personal information to send you promotional information + about third parties which we think you may find interesting if you tell us that you wish this to happen. +

    +

    + You may request details of personal information which we hold about you under the Data Protection Act 1998. + A small fee will be payable. If you would like a copy of the information held on you please write to + {{config path="general/store_information/address"}}. +

    +

    + If you believe that any information we are holding on you is incorrect or incomplete, + please write to or email us as soon as possible, at the above address. + We will promptly correct any information found to be incorrect. +

    +

    List of cookies we collect

    +

    The table below lists the cookies we collect and what information they store.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    COOKIE nameCOOKIE Description
    CARTThe association with your shopping cart.
    CATEGORY_INFOStores the category info on the page, that allows to display pages more quickly.
    COMPAREThe items that you have in the Compare Products list.
    CURRENCYYour preferred currency
    CUSTOMERAn encrypted version of your customer id with the store.
    CUSTOMER_AUTHAn indicator if you are currently logged into the store.
    CUSTOMER_INFOAn encrypted version of the customer group you belong to.
    CUSTOMER_SEGMENT_IDSStores the Customer Segment ID
    EXTERNAL_NO_CACHEA flag, which indicates whether caching is disabled or not.
    FRONTENDYou sesssion ID on the server.
    GUEST-VIEWAllows guests to edit their orders.
    LAST_CATEGORYThe last category you visited.
    LAST_PRODUCTThe most recent product you have viewed.
    NEWMESSAGEIndicates whether a new message has been received.
    NO_CACHEIndicates whether it is allowed to use cache.
    PERSISTENT_SHOPPING_CARTA link to information about your cart and viewing history if you have asked the site.
    POLLThe ID of any polls you have recently voted in.
    POLLNInformation on what polls you have voted on.
    RECENTLYCOMPAREDThe items that you have recently compared.
    STFInformation on products you have emailed to friends.
    STOREThe store view or language you have selected.
    USER_ALLOWED_SAVE_COOKIEIndicates whether a customer allowed to use cookies.
    VIEWED_PRODUCT_IDSThe products that you have recently viewed.
    WISHLISTAn encrypted list of products added to your Wishlist.
    WISHLIST_CNTThe number of items in your Wishlist.
    +EOD; + +$privacyPageData = array( + 'title' => 'Privacy Policy', + 'content_heading' => 'Privacy Policy', + 'root_template' => 'one_column', + 'identifier' => 'privacy-policy-cookie-restriction-mode', + 'content' => $pageContent, + 'is_active' => 1, + 'stores' => array(0), + 'sort_order' => 0 +); + +Mage::getModel('cms/page')->setData($privacyPageData)->save(); + +$footerLinksBlock = Mage::getModel('cms/block')->load('footer_links','identifier'); + +if ($footerLinksBlock->getId()) { + $content = $footerLinksBlock->getContent(); + if (preg_match('/
      (.*?)<\\/ul>/ims',$content, $matches)) { + $content = preg_replace('/
    • /ims', '
    • ',$content); + $replacment = '
    • ' + . "" + . "Privacy Policy
    • \r\n
    "; + $content = preg_replace('/<\\/ul>/ims', $replacment, $content); + $footerLinksBlock->setContent($content)->save(); + } +} diff --git a/app/code/core/Mage/Cms/etc/adminhtml.xml b/app/code/core/Mage/Cms/etc/adminhtml.xml index 7d0d1f70..57673a7a 100644 --- a/app/code/core/Mage/Cms/etc/adminhtml.xml +++ b/app/code/core/Mage/Cms/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -70,6 +70,10 @@ + + Media Gallery + 20 + diff --git a/app/code/core/Mage/Cms/etc/config.xml b/app/code/core/Mage/Cms/etc/config.xml index d93c8400..f8264af1 100644 --- a/app/code/core/Mage/Cms/etc/config.xml +++ b/app/code/core/Mage/Cms/etc/config.xml @@ -21,25 +21,25 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.13 + 1.6.0.0.1 - - standard - - Mage_Cms - cms - - + + standard + + Mage_Cms + cms + + @@ -68,13 +68,13 @@ - - - - cms.xml - - - + + + + cms.xml + + + @@ -97,15 +97,15 @@ - catalog - downloadable + + - + @@ -140,10 +140,11 @@ Mage_Cms_Model - cms_mysql4 + cms_resource - - Mage_Cms_Model_Mysql4 + + Mage_Cms_Model_Resource + cms_mysql4 cms_page
    @@ -158,7 +159,7 @@ cms_block_store
    -
    +
    @@ -168,7 +169,9 @@ - Mage_Cms_Block + + Mage_Cms_Block + @@ -189,7 +192,6 @@ - @@ -206,5 +208,12 @@ enabled + + + + wysiwyg + + +
    diff --git a/app/code/core/Mage/Cms/etc/system.xml b/app/code/core/Mage/Cms/etc/system.xml index f4a78108..e673ef28 100644 --- a/app/code/core/Mage/Cms/etc/system.xml +++ b/app/code/core/Mage/Cms/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/Cms/etc/widget.xml b/app/code/core/Mage/Cms/etc/widget.xml index 4d44f1ce..56653fee 100644 --- a/app/code/core/Mage/Cms/etc/widget.xml +++ b/app/code/core/Mage/Cms/etc/widget.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/Cms/sql/cms_setup/install-1.6.0.0.php b/app/code/core/Mage/Cms/sql/cms_setup/install-1.6.0.0.php new file mode 100644 index 00000000..292b1178 --- /dev/null +++ b/app/code/core/Mage/Cms/sql/cms_setup/install-1.6.0.0.php @@ -0,0 +1,178 @@ +startSetup(); + +/** + * Create table 'cms/block' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('cms/block')) + ->addColumn('block_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Block ID') + ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Block Title') + ->addColumn('identifier', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Block String Identifier') + ->addColumn('content', Varien_Db_Ddl_Table::TYPE_TEXT, '2M', array( + ), 'Block Content') + ->addColumn('creation_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Block Creation Time') + ->addColumn('update_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Block Modification Time') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '1', + ), 'Is Block Active') + ->setComment('CMS Block Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'cms/block_store' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('cms/block_store')) + ->addColumn('block_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'primary' => true, + ), 'Block ID') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Store ID') + ->addIndex($installer->getIdxName('cms/block_store', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('cms/block_store', 'block_id', 'cms/block', 'block_id'), + 'block_id', $installer->getTable('cms/block'), 'block_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('cms/block_store', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('CMS Block To Store Linkage Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'cms/page' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('cms/page')) + ->addColumn('page_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Page ID') + ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true + ), 'Page Title') + ->addColumn('root_template', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true + ), 'Page Template') + ->addColumn('meta_keywords', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => true, + ), 'Page Meta Keywords') + ->addColumn('meta_description', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => true, + ), 'Page Meta Description') + ->addColumn('identifier', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'nullable' => true, + 'default' => null, + ), 'Page String Identifier') + ->addColumn('content_heading', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + ), 'Page Content Heading') + ->addColumn('content', Varien_Db_Ddl_Table::TYPE_TEXT, '2M', array( + ), 'Page Content') + ->addColumn('creation_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Page Creation Time') + ->addColumn('update_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Page Modification Time') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '1', + ), 'Is Page Active') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Page Sort Order') + ->addColumn('layout_update_xml', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => true, + ), 'Page Layout Update Content') + ->addColumn('custom_theme', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'nullable' => true, + ), 'Page Custom Theme') + ->addColumn('custom_root_template', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + ), 'Page Custom Template') + ->addColumn('custom_layout_update_xml', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => true, + ), 'Page Custom Layout Update Content') + ->addColumn('custom_theme_from', Varien_Db_Ddl_Table::TYPE_DATE, null, array( + 'nullable' => true, + ), 'Page Custom Theme Active From Date') + ->addColumn('custom_theme_to', Varien_Db_Ddl_Table::TYPE_DATE, null, array( + 'nullable' => true, + ), 'Page Custom Theme Active To Date') + ->addIndex($installer->getIdxName('cms/page', array('identifier')), + array('identifier')) + ->setComment('CMS Page Table'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'cms/page_store' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('cms/page_store')) + ->addColumn('page_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'primary' => true, + ), 'Page ID') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Store ID') + ->addIndex($installer->getIdxName('cms/page_store', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('cms/page_store', 'page_id', 'cms/page', 'page_id'), + 'page_id', $installer->getTable('cms/page'), 'page_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('cms/page_store', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('CMS Page To Store Linkage Table'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php index fb2e9fa3..1aaf7478 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -65,7 +65,7 @@ UNIQUE KEY `identifier` (`identifier`,`store_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='CMS pages'; -insert into {$this->getTable('cms_page')}(`page_id`,`title`,`root_template`,`meta_keywords`,`meta_description`,`identifier`,`content`,`creation_time`,`update_time`,`is_active`,`store_id`,`sort_order`) values (1,'404 Not Found 1','right_column','Page keywords','Page description','no-route','

    Whoops, our bad...

    \r\n
    \r\n
    The page you requested was not found, and we have a fine guess why.
    \r\n
    \r\n
      \r\n
    • If you typed the URL directly, please make sure the spelling is correct.
    • \r\n
    • If you clicked on a link to get here, the link is outdated.
    • \r\n
    \r\n
    \r\n
    \r\n
    What can you do?
    \r\n
    Have no fear, help is near! There are many ways you can get back on track with Magento Store.
    \r\n
    \r\n
      \r\n
    • Go back to the previous page.
    • \r\n
    • Use the search bar at the top of the page to search for your products.
    • \r\n
    • Follow these links to get you back on track!
      Store Home | My Account
    \r\n','2007-06-20 18:38:32','2007-08-26 19:11:13',1,0,0),(2,'Home page','right_column','','','home','

    Home Page

    \r\n','2007-08-23 10:03:25','2007-09-06 13:26:53',1,0,0),(3,'About Us','one_column','','','about-magento-demo-store','
    \r\n

    About Magento Store

    \r\n
    \r\n
    \r\n

    \"Varien\"

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede.

    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit.

    \r\n

    Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus. Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi. Vestibulum sapien dolor, aliquet nec, porta ac, malesuada a, libero. Praesent feugiat purus eget est. Nulla facilisi. Vestibulum tincidunt sapien eu velit. Mauris purus. Maecenas eget mauris eu orci accumsan feugiat. Pellentesque eget velit. Nunc tincidunt.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus.

    \r\n

    Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi.

    \r\n
    \r\n

    To all of you, from all of us at Magento Store - Thank you and Happy eCommerce!

    \r\n

    John Doe
    Some important guy

    \r\n
    ','2007-08-30 14:01:18','2007-08-30 14:01:18',1,0,0),(4,'Customer Service','three_column','','','customer-service','
    \r\n

    Customer Service

    \r\n
    \r\n\r\n
    \r\n
    Shipping & Delivery
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Privacy & Security
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Returns & Replacements
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Ordering
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Payment, Pricing & Promotions
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Viewing Orders
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Updating Account Information
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    ','2007-08-30 14:02:20','2007-08-30 14:03:37',1,0,0); +insert into {$this->getTable('cms_page')}(`page_id`,`title`,`root_template`,`meta_keywords`,`meta_description`,`identifier`,`content`,`creation_time`,`update_time`,`is_active`,`store_id`,`sort_order`) values (1,'404 Not Found 1','right_column','Page keywords','Page description','no-route','

    Whoops, our bad...

    \r\n
    \r\n
    The page you requested was not found, and we have a fine guess why.
    \r\n
    \r\n
      \r\n
    • If you typed the URL directly, please make sure the spelling is correct.
    • \r\n
    • If you clicked on a link to get here, the link is outdated.
    • \r\n
    \r\n
    \r\n
    \r\n
    What can you do?
    \r\n
    Have no fear, help is near! There are many ways you can get back on track with Magento Store.
    \r\n
    \r\n
      \r\n
    • Go back to the previous page.
    • \r\n
    • Use the search bar at the top of the page to search for your products.
    • \r\n
    • Follow these links to get you back on track!
      Store Home | My Account
    \r\n','2007-06-20 18:38:32','2007-08-26 19:11:13',1,0,0),(2,'Home page','right_column','','','home','

    Home Page

    \r\n','2007-08-23 10:03:25','2007-09-06 13:26:53',1,0,0),(3,'About Us','one_column','','','about-magento-demo-store','
    \r\n

    About Magento Store

    \r\n
    \r\n
    \r\n

    \"Varien\"

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede.

    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit.

    \r\n

    Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus. Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi. Vestibulum sapien dolor, aliquet nec, porta ac, malesuada a, libero. Praesent feugiat purus eget est. Nulla facilisi. Vestibulum tincidunt sapien eu velit. Mauris purus. Maecenas eget mauris eu orci accumsan feugiat. Pellentesque eget velit. Nunc tincidunt.

    \r\n
    \r\n

    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper

    \r\n

    Maecenas ullamcorper, odio vel tempus egestas, dui orci faucibus orci, sit amet aliquet lectus dolor et quam. Pellentesque consequat luctus purus.

    \r\n

    Nunc et risus. Etiam a nibh. Phasellus dignissim metus eget nisi.

    \r\n
    \r\n

    To all of you, from all of us at Magento Store - Thank you and Happy eCommerce!

    \r\n

    John Doe
    Some important guy

    \r\n
    ','2007-08-30 14:01:18','2007-08-30 14:01:18',1,0,0),(4,'Customer Service','three_column','','','customer-service','
    \r\n

    Customer Service

    \r\n
    \r\n\r\n
    \r\n
    Shipping & Delivery
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Privacy & Security
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Returns & Replacements
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Ordering
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Payment, Pricing & Promotions
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Viewing Orders
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    Updating Account Information
    \r\n
    Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi luctus. Duis lobortis. Nulla nec velit. Mauris pulvinar erat non massa. Suspendisse tortor turpis, porta nec, tempus vitae, iaculis semper, pede. Cras vel libero id lectus rhoncus porta. Suspendisse convallis felis ac enim. Vivamus tortor nisl, lobortis in, faucibus et, tempus at, dui. Nunc risus. Proin scelerisque augue. Nam ullamcorper. Phasellus id massa. Pellentesque nisl. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nunc augue. Aenean sed justo non leo vehicula laoreet. Praesent ipsum libero, auctor ac, tempus nec, tempor nec, justo.
    \r\n
    ','2007-08-30 14:02:20','2007-08-30 14:03:37',1,0,0); "); diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.0-0.7.1.php index bc162d8e..2f9a53a1 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.1-0.7.2.php index e2ac89a7..e9934d29 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php index b683dd3a..6490b422 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.10-0.7.11.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.11-0.7.12.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.11-0.7.12.php index 465450dd..7962536a 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.11-0.7.12.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.11-0.7.12.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.12-0.7.13.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.12-0.7.13.php index da704e7f..2213dd5a 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.12-0.7.13.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.12-0.7.13.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.2-0.7.3.php index 4eaaa0f7..03a71373 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.2-0.7.3.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.4-0.7.5.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.4-0.7.5.php index ae2d5dc6..832c3630 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.4-0.7.5.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.4-0.7.5.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.5-0.7.6.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.5-0.7.6.php index 1746fc7b..5481e790 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.5-0.7.6.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.5-0.7.6.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.7-0.7.8.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.7-0.7.8.php index aca7b8ff..3013a38d 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.7-0.7.8.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.7-0.7.8.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php index 9994d00c..0e3fe5d7 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php index 0e0a55ee..53be8fd4 100644 --- a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-0.7.9-0.7.10.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cms - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..a3dfb756 --- /dev/null +++ b/app/code/core/Mage/Cms/sql/cms_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,315 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('cms/block_store'), + 'FK_CMS_BLOCK_STORE_BLOCK' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('cms/block_store'), + 'FK_CMS_BLOCK_STORE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('cms/page_store'), + 'FK_CMS_PAGE_STORE_PAGE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('cms/page_store'), + 'FK_CMS_PAGE_STORE_STORE' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('cms/block_store'), + 'FK_CMS_BLOCK_STORE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('cms/page'), + 'IDENTIFIER' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('cms/page_store'), + 'FK_CMS_PAGE_STORE_STORE' +); + + +/* + * Change columns + */ +$tables = array( + $installer->getTable('cms/page') => array( + 'columns' => array( + 'page_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Page ID' + ), + 'title' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Page Title' + ), + 'root_template' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Page Template' + ), + 'meta_keywords' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Page Meta Keywords' + ), + 'meta_description' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Page Meta Description' + ), + 'identifier' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'nullable' => false, + 'comment' => 'Page String Identifier' + ), + 'content_heading' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Page Content Heading' + ), + 'content' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2M', + 'comment' => 'Page Content' + ), + 'creation_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Page Creation Time' + ), + 'update_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Page Modification Time' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '1', + 'comment' => 'Is Page Active' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Page Sort Order' + ), + 'layout_update_xml' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Page Layout Update Content' + ), + 'custom_theme' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'comment' => 'Page Custom Theme' + ), + 'custom_root_template' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Page Custom Template' + ), + 'custom_layout_update_xml' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Page Custom Layout Update Content' + ), + 'custom_theme_from' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATE, + 'comment' => 'Page Custom Theme Active From Date' + ), + 'custom_theme_to' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATE, + 'comment' => 'Page Custom Theme Active To Date' + ) + ), + 'comment' => 'CMS Page Table' + ), + $installer->getTable('cms/page_store') => array( + 'columns' => array( + 'page_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Page ID' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Store ID' + ) + ), + 'comment' => 'CMS Page To Store Linkage Table' + ), + $installer->getTable('cms_block') => array( + 'columns' => array( + 'block_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Block ID' + ), + 'title' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Block Title' + ), + 'identifier' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Block String Identifier' + ), + 'content' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2M', + 'comment' => 'Block Content' + ), + 'creation_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Block Creation Time' + ), + 'update_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Block Modification Time' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '1', + 'comment' => 'Is Block Active' + ) + ), + 'comment' => 'CMS Block Table' + ), + $installer->getTable('cms/block_store') => array( + 'columns' => array( + 'block_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Block ID' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Store ID' + ) + ), + 'comment' => 'CMS Block To Store Linkage Table' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('cms/page'), + $installer->getIdxName('cms/page', array('identifier')), + array('identifier') +); + +$installer->getConnection()->addIndex( + $installer->getTable('cms/page_store'), + $installer->getIdxName('cms/page_store', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('cms/block_store'), + $installer->getIdxName('cms/block_store', array('store_id')), + array('store_id') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('cms/block_store', 'block_id', 'cms/block', 'block_id'), + $installer->getTable('cms/block_store'), + 'block_id', + $installer->getTable('cms/block'), + 'block_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('cms/block_store', 'store_id', 'core/store', 'store_id'), + $installer->getTable('cms/block_store'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('cms/page_store', 'page_id', 'cms/page', 'page_id'), + $installer->getTable('cms/page_store'), + 'page_id', + $installer->getTable('cms/page'), + 'page_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('cms/page_store', 'store_id', 'core/store', 'store_id'), + $installer->getTable('cms/page_store'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Core/Block/Abstract.php b/app/code/core/Mage/Core/Block/Abstract.php index 6778d346..dc92040a 100644 --- a/app/code/core/Mage/Core/Block/Abstract.php +++ b/app/code/core/Mage/Core/Block/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -37,6 +37,9 @@ */ abstract class Mage_Core_Block_Abstract extends Varien_Object { + /** + * Cache group Tag + */ const CACHE_GROUP = 'block_html'; /** * Block name in layout @@ -60,7 +63,7 @@ abstract class Mage_Core_Block_Abstract extends Varien_Object protected $_parent; /** - * Short alias of this block to be refered from parent + * Short alias of this block that was refered from parent * * @var string */ @@ -78,28 +81,28 @@ abstract class Mage_Core_Block_Abstract extends Varien_Object * * @var array */ - protected $_children = array(); + protected $_children = array(); /** * Sorted children list * * @var array */ - protected $_sortedChildren = array(); + protected $_sortedChildren = array(); /** * Children blocks HTML cache array * * @var array */ - protected $_childrenHtmlCache = array(); + protected $_childrenHtmlCache = array(); /** * Arbitrary groups of child blocks * * @var array */ - protected $_childGroups = array(); + protected $_childGroups = array(); /** * Request object @@ -113,14 +116,14 @@ abstract class Mage_Core_Block_Abstract extends Varien_Object * * @var Mage_Core_Block_Messages */ - protected $_messagesBlock = null; + protected $_messagesBlock = null; /** * Whether this block was not explicitly named * * @var boolean */ - protected $_isAnonymous = false; + protected $_isAnonymous = false; /** * Parent block @@ -141,8 +144,24 @@ abstract class Mage_Core_Block_Abstract extends Varien_Object */ protected $_frameCloseTag; + /** + * Url object + * + * @var Mage_Core_Model_Url + */ protected static $_urlModel; + /** + * @var Varien_Object + */ + private static $_transportObject; + + /** + * Array of block sort priority instructions + * + * @var array + */ + protected $_sortInstructions = array(); /** * Internal constructor, that is called from real constructor @@ -161,6 +180,7 @@ protected function _construct() * Retrieve request object * * @return Mage_Core_Controller_Request_Http + * @throws Exception */ public function getRequest() { @@ -223,7 +243,7 @@ public function setLayout(Mage_Core_Model_Layout $layout) /** * Preparing global layout * - * You can redefine this method in child classes for changin layout + * You can redefine this method in child classes for changing layout * * @return Mage_Core_Block_Abstract */ @@ -251,28 +271,56 @@ public function getIsAnonymous() return $this->_isAnonymous; } + /** + * Set the anonymous flag + * + * @param bool $flag + * @return Mage_Core_Block_Abstract + */ public function setIsAnonymous($flag) { - $this->_isAnonymous = $flag; + $this->_isAnonymous = (bool)$flag; return $this; } + /** + * Returns anonymous block suffix + * + * @return string + */ public function getAnonSuffix() { return $this->_anonSuffix; } + /** + * Set anonymous suffix for current block + * + * @param string $suffix + * @return Mage_Core_Block_Abstract + */ public function setAnonSuffix($suffix) { $this->_anonSuffix = $suffix; return $this; } + /** + * Returns block alias + * + * @return string + */ public function getBlockAlias() { return $this->_alias; } + /** + * Set block alias + * + * @param string $alias + * @return Mage_Core_Block_Abstract + */ public function setBlockAlias($alias) { $this->_alias = $alias; @@ -282,7 +330,7 @@ public function setBlockAlias($alias) /** * Set block's name in layout and unsets previous link if such exists. * - * @param $name + * @param string $name * @return Mage_Core_Block_Abstract */ public function setNameInLayout($name) @@ -295,8 +343,14 @@ public function setNameInLayout($name) return $this; } + /** + * Retrieve sorted list of children. + * + * @return array + */ public function getSortedChildren() { + $this->sortChildren(); return $this->_sortedChildren; } @@ -309,7 +363,7 @@ public function getSortedChildren() * @param mixed $value * @return Mage_Core_Block_Abstract */ - public function setAttribute($name, $value=null) + public function setAttribute($name, $value = null) { return $this->setData($name, $value); } @@ -317,7 +371,7 @@ public function setAttribute($name, $value=null) /** * Set child block * - * @param string $name + * @param string $alias * @param Mage_Core_Block_Abstract $block * @return Mage_Core_Block_Abstract */ @@ -333,9 +387,9 @@ public function setChild($alias, $block) if ($block->getIsAnonymous()) { $suffix = $block->getAnonSuffix(); if (empty($suffix)) { - $suffix = 'child'.sizeof($this->_children); + $suffix = 'child' . sizeof($this->_children); } - $blockName = $this->getNameInLayout().'.'.$suffix; + $blockName = $this->getNameInLayout() . '.' . $suffix; if ($this->getLayout()) { $this->getLayout()->unsetBlock($block->getNameInLayout()) @@ -359,18 +413,18 @@ public function setChild($alias, $block) /** * Unset child block * - * @param string $name - * @return Mage_Core_Block_Abstract + * @param string $alias + * @return Mage_Core_Block_Abstract */ public function unsetChild($alias) { if (isset($this->_children[$alias])) { + /** @var Mage_Core_Block_Abstract $block */ + $block = $this->_children[$alias]; + $name = $block->getNameInLayout(); unset($this->_children[$alias]); - } - - if (!empty($this->_sortedChildren)) { - $key = array_search($alias, $this->_sortedChildren); - if ($key!==false) { + $key = array_search($name, $this->_sortedChildren); + if ($key !== false) { unset($this->_sortedChildren[$key]); } } @@ -423,7 +477,7 @@ public function unsetCallChild($alias, $callback, $result, $params) */ public function unsetChildren() { - $this->_children = array(); + $this->_children = array(); $this->_sortedChildren = array(); return $this; } @@ -434,9 +488,9 @@ public function unsetChildren() * @param string $name * @return mixed */ - public function getChild($name='') + public function getChild($name = '') { - if (''===$name) { + if ($name === '') { return $this->_children; } elseif (isset($this->_children[$name])) { return $this->_children[$name]; @@ -449,11 +503,12 @@ public function getChild($name='') * * @param string $name * @param boolean $useCache + * @param boolean $sorted * @return string */ - public function getChildHtml($name='', $useCache=true, $sorted=false) + public function getChildHtml($name = '', $useCache = true, $sorted = false) { - if ('' === $name) { + if ($name === '') { if ($sorted) { $children = array(); foreach ($this->getSortedChildren() as $childName) { @@ -472,6 +527,13 @@ public function getChildHtml($name='', $useCache=true, $sorted=false) } } + /** + * @param string $name Parent block name + * @param string $childName OPTIONAL Child block name + * @param bool $useCache OPTIONAL Use cache flag + * @param bool $sorted OPTIONAL @see getChildHtml() + * @return string + */ public function getChildChildHtml($name, $childName = '', $useCache = true, $sorted = false) { if (empty($name)) { @@ -505,7 +567,7 @@ public function getSortedChildBlocks() * @param boolean $useCache * @return string */ - protected function _getChildHtml($name, $useCache=true) + protected function _getChildHtml($name, $useCache = true) { if ($useCache && isset($this->_childrenHtmlCache[$name])) { return $this->_childrenHtmlCache[$name]; @@ -542,7 +604,7 @@ protected function _beforeChildToHtml($name, $child) */ public function getBlockHtml($name) { - if (!($layout = $this->getLayout()) && !($layout = Mage::app()->getFrontController()->getAction()->getLayout())) { + if (!($layout = $this->getLayout()) && !($layout = $this->getAction()->getLayout())) { return ''; } if (!($block = $layout->getBlock($name))) { @@ -560,7 +622,7 @@ public function getBlockHtml($name) * @param string $alias * @return object $this */ - public function insert($block, $siblingName='', $after=false, $alias='') + public function insert($block, $siblingName = '', $after = false, $alias = '') { if (is_string($block)) { $block = $this->getLayout()->getBlock($block); @@ -584,7 +646,7 @@ public function insert($block, $siblingName='', $after=false, $alias='') $this->setChild($name, $block); } - if (''===$siblingName) { + if ($siblingName === '') { if ($after) { array_push($this->_sortedChildren, $name); } else { @@ -592,7 +654,7 @@ public function insert($block, $siblingName='', $after=false, $alias='') } } else { $key = array_search($siblingName, $this->_sortedChildren); - if (false!==$key) { + if (false !== $key) { if ($after) { $key++; } @@ -604,6 +666,54 @@ public function insert($block, $siblingName='', $after=false, $alias='') array_unshift($this->_sortedChildren, $name); } } + + $this->_sortInstructions[$name] = array($siblingName, (bool)$after, false !== $key); + } + + return $this; + } + + /** + * Sort block's children + * + * @param boolean $force force re-sort all children + * @return Mage_Core_Block_Abstract + */ + public function sortChildren($force = false) + { + foreach ($this->_sortInstructions as $name => $list) { + list($siblingName, $after, $exists) = $list; + if ($exists && !$force) { + continue; + } + $this->_sortInstructions[$name][2] = true; + + $index = array_search($name, $this->_sortedChildren); + $siblingKey = array_search($siblingName, $this->_sortedChildren); + + if ($index === false || $siblingKey === false) { + continue; + } + + if ($after) { + // insert after block + if ($index == $siblingKey + 1) { + continue; + } + // remove sibling from array + array_splice($this->_sortedChildren, $index, 1, array()); + // insert sibling after + array_splice($this->_sortedChildren, $siblingKey + 1, 0, array($name)); + } else { + // insert before block + if ($index == $siblingKey - 1) { + continue; + } + // remove sibling from array + array_splice($this->_sortedChildren, $index, 1, array()); + // insert sibling after + array_splice($this->_sortedChildren, $siblingKey, 0, array($name)); + } } return $this; @@ -616,7 +726,7 @@ public function insert($block, $siblingName='', $after=false, $alias='') * @param string $alias * @return Mage_Core_Block_Abstract */ - public function append($block, $alias='') + public function append($block, $alias = '') { $this->insert($block, '', true, $alias); return $this; @@ -717,13 +827,13 @@ protected function _beforeToHtml() * @param $closeTag * @return Mage_Core_Block_Abstract */ - public function setFrameTags($openTag, $closeTag=null) + public function setFrameTags($openTag, $closeTag = null) { $this->_frameOpenTag = $openTag; if ($closeTag) { $this->_frameCloseTag = $closeTag; } else { - $this->_frameCloseTag = '/'.$openTag; + $this->_frameCloseTag = '/' . $openTag; } return $this; } @@ -731,20 +841,20 @@ public function setFrameTags($openTag, $closeTag=null) /** * Produce and return block's html output * - * It is a final method, but you can override _toHmtl() method in descendants if needed + * It is a final method, but you can override _toHtml() method in descendants if needed. * * @return string */ final public function toHtml() { Mage::dispatchEvent('core_block_abstract_to_html_before', array('block' => $this)); - if (Mage::getStoreConfig('advanced/modules_disable_output/'.$this->getModuleName())) { + if (Mage::getStoreConfig('advanced/modules_disable_output/' . $this->getModuleName())) { return ''; } $html = $this->_loadCache(); - if (!$html) { + if ($html === false) { $translate = Mage::getSingleton('core/translate'); - /* @var $translate Mage_Core_Model_Translate */ + /** @var $translate Mage_Core_Model_Translate */ if ($this->hasData('translate_inline')) { $translate->setTranslateInline($this->getData('translate_inline')); } @@ -769,13 +879,13 @@ final public function toHtml() /** * Use single transport object instance for all blocks */ - static $transport; - if ($transport === null) { - $transport = new Varien_Object; + if (self::$_transportObject === null) { + self::$_transportObject = new Varien_Object; } - $transport->setHtml($html); - Mage::dispatchEvent('core_block_abstract_to_html_after', array('block' => $this, 'transport' => $transport)); - $html = $transport->getHtml(); + self::$_transportObject->setHtml($html); + Mage::dispatchEvent('core_block_abstract_to_html_after', + array('block' => $this, 'transport' => self::$_transportObject)); + $html = self::$_transportObject->getHtml(); return $html; } @@ -802,7 +912,7 @@ protected function _toHtml() } /** - * Enter description here... + * Returns url model class name * * @return string */ @@ -812,13 +922,13 @@ protected function _getUrlModelClass() } /** - * Enter description here... + * Create and return url object * * @return Mage_Core_Model_Url */ protected function _getUrlModel() { - return Mage::getModel($this->_getUrlModelClass());; + return Mage::getModel($this->_getUrlModelClass()); } /** @@ -828,7 +938,7 @@ protected function _getUrlModel() * @param array $params * @return string */ - public function getUrl($route='', $params=array()) + public function getUrl($route = '', $params = array()) { return $this->_getUrlModel()->getUrl($route, $params); } @@ -840,7 +950,7 @@ public function getUrl($route='', $params=array()) * @param array $params * @return string */ - public function getUrlBase64($route='', $params=array()) + public function getUrlBase64($route = '', $params = array()) { return Mage::helper('core')->urlEncode($this->getUrl($route, $params)); } @@ -864,7 +974,7 @@ public function getUrlEncoded($route = '', $params = array()) * @param array $params * @return string */ - public function getSkinUrl($file=null, array $params=array()) + public function getSkinUrl($file = null, array $params = array()) { return Mage::getDesign()->getSkinUrl($file, $params); } @@ -895,7 +1005,7 @@ public function setMessagesBlock(Mage_Core_Block_Messages $block) } /** - * Enter description here... + * Return block helper * * @param string $type * @return Mage_Core_Block_Abstract @@ -903,11 +1013,10 @@ public function setMessagesBlock(Mage_Core_Block_Messages $block) public function getHelper($type) { return $this->getLayout()->getBlockSingleton($type); - //return $this->helper($type); } /** - * Enter description here... + * Returns helper object * * @param string $name * @return Mage_Core_Block_Abstract @@ -921,27 +1030,27 @@ public function helper($name) } /** - * Retrieve formating date + * Retrieve formatting date * * @param string $date * @param string $format * @param bool $showTime * @return string */ - public function formatDate($date=null, $format='short', $showTime=false) + public function formatDate($date = null, $format = Mage_Core_Model_Locale::FORMAT_TYPE_SHORT, $showTime = false) { return $this->helper('core')->formatDate($date, $format, $showTime); } /** - * Retrieve formating time + * Retrieve formatting time * * @param string $time * @param string $format * @param bool $showDate * @return string */ - public function formatTime($time=null, $format='short', $showDate=false) + public function formatTime($time = null, $format = Mage_Core_Model_Locale::FORMAT_TYPE_SHORT, $showDate = false) { return $this->helper('core')->formatTime($time, $format, $showDate); } @@ -997,7 +1106,7 @@ public function escapeHtml($data, $allowedTags = null) } /** - * Wrapper for standart strip_tags() function with extra functionality for html entities + * Wrapper for standard strip_tags() function with extra functionality for html entities * * @param string $data * @param string $allowableTags @@ -1084,7 +1193,7 @@ protected function _afterCacheUrl($html) if (Mage::app()->useCache(self::CACHE_GROUP)) { Mage::app()->setUseSessionVar(false); Varien_Profiler::start('CACHE_URL'); - $html = Mage::getSingleton('core/url')->sessionUrlVar($html); + $html = Mage::getSingleton($this->_getUrlModelClass())->sessionUrlVar($html); Varien_Profiler::stop('CACHE_URL'); } return $html; @@ -1164,7 +1273,18 @@ protected function _loadCache() if (is_null($this->getCacheLifetime()) || !Mage::app()->useCache(self::CACHE_GROUP)) { return false; } - return Mage::app()->loadCache($this->getCacheKey()); + $cacheKey = $this->getCacheKey(); + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session'); + $cacheData = Mage::app()->loadCache($cacheKey); + if ($cacheData) { + $cacheData = str_replace( + $this->_getSidPlaceholder($cacheKey), + $session->getSessionIdQueryParam() . '=' . $session->getEncryptedSessionId(), + $cacheData + ); + } + return $cacheData; } /** @@ -1178,7 +1298,31 @@ protected function _saveCache($data) if (is_null($this->getCacheLifetime()) || !Mage::app()->useCache(self::CACHE_GROUP)) { return false; } - Mage::app()->saveCache($data, $this->getCacheKey(), $this->getCacheTags(), $this->getCacheLifetime()); + $cacheKey = $this->getCacheKey(); + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session'); + $data = str_replace( + $session->getSessionIdQueryParam() . '=' . $session->getEncryptedSessionId(), + $this->_getSidPlaceholder($cacheKey), + $data + ); + + Mage::app()->saveCache($data, $cacheKey, $this->getCacheTags(), $this->getCacheLifetime()); return $this; } + + /** + * Get SID placeholder for cache + * + * @param null|string $cacheKey + * @return string + */ + protected function _getSidPlaceholder($cacheKey = null) + { + if (is_null($cacheKey)) { + $cacheKey = $this->getCacheKey(); + } + + return ''; + } } diff --git a/app/code/core/Mage/Core/Block/Flush.php b/app/code/core/Mage/Core/Block/Flush.php index f07505d8..66d3fe80 100644 --- a/app/code/core/Mage/Core/Block/Flush.php +++ b/app/code/core/Mage/Core/Block/Flush.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Html/Calendar.php b/app/code/core/Mage/Core/Block/Html/Calendar.php index dee672f6..7515697b 100644 --- a/app/code/core/Mage/Core/Block/Html/Calendar.php +++ b/app/code/core/Mage/Core/Block/Html/Calendar.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,7 +29,7 @@ * Prepares localization data for calendar * * @category Mage - * @package Mage + * @package Mage_Core * @author Magento Core Team */ class Mage_Core_Block_Html_Calendar extends Mage_Core_Block_Template diff --git a/app/code/core/Mage/Core/Block/Html/Date.php b/app/code/core/Mage/Core/Block/Html/Date.php index 15652c22..86478afd 100644 --- a/app/code/core/Mage/Core/Block/Html/Date.php +++ b/app/code/core/Mage/Core/Block/Html/Date.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -40,23 +40,32 @@ protected function _toHtml() $displayFormat = Varien_Date::convertZendToStrFtime($this->getFormat(), true, (bool)$this->getTime()); $html = 'getValue().'" class="'.$this->getClass().'" '.$this->getExtraParams().'/> '; + $html .= 'value="' . $this->escapeHtml($this->getValue()) . '" class="' . $this->getClass() . '" ' . $this->getExtraParams() . '/> '; $html .= '' . $this->helper('core')->__('Select Date') . 'helper('core')->__('Select Date') . '" id="' . $this->getId() . '_trig" />'; $html .= - ''; diff --git a/app/code/core/Mage/Core/Block/Html/Link.php b/app/code/core/Mage/Core/Block/Html/Link.php index bf9a25b0..5cac033c 100644 --- a/app/code/core/Mage/Core/Block/Html/Link.php +++ b/app/code/core/Mage/Core/Block/Html/Link.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Html/Select.php b/app/code/core/Mage/Core/Block/Html/Select.php index 3e76e261..d848348f 100644 --- a/app/code/core/Mage/Core/Block/Html/Select.php +++ b/app/code/core/Mage/Core/Block/Html/Select.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,71 +30,128 @@ * * @category Mage * @package Mage_Core - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Core_Block_Html_Select extends Mage_Core_Block_Abstract { protected $_options = array(); + /** + * Get options of the element + * + * @return array + */ public function getOptions() { return $this->_options; } + /** + * Set options for the HTML select + * + * @param array $options + * @return Mage_Core_Block_Html_Select + */ public function setOptions($options) { $this->_options = $options; return $this; } + /** + * Add an option to HTML select + * + * @param string $value HTML value + * @param string $label HTML label + * @param array $params HTML attributes + * @return Mage_Core_Block_Html_Select + */ public function addOption($value, $label, $params=array()) { - $this->_options[] = array('value'=>$value, 'label'=>$label); + $this->_options[] = array('value' => $value, 'label' => $label, 'params' => $params); return $this; } + /** + * Set element's HTML ID + * + * @param string $id ID + * @return Mage_Core_Block_Html_Select + */ public function setId($id) { $this->setData('id', $id); return $this; } + /** + * Set element's CSS class + * + * @param string $class Class + * @return Mage_Core_Block_Html_Select + */ public function setClass($class) { $this->setData('class', $class); return $this; } + /** + * Set element's HTML title + * + * @param string $title Title + * @return Mage_Core_Block_Html_Select + */ public function setTitle($title) { $this->setData('title', $title); return $this; } + /** + * HTML ID of the element + * + * @return string + */ public function getId() { return $this->getData('id'); } + /** + * CSS class of the element + * + * @return string + */ public function getClass() { return $this->getData('class'); } + /** + * Returns HTML title of the element + * + * @return string + */ public function getTitle() { return $this->getData('title'); } + /** + * Render HTML + * + * @return string + */ protected function _toHtml() { if (!$this->_beforeToHtml()) { return ''; } - $html = 'getExtraParams() . '>'; $values = $this->getValue(); if (!is_array($values)){ @@ -108,17 +165,18 @@ protected function _toHtml() $isArrayOption = true; foreach ($this->getOptions() as $key => $option) { if ($isArrayOption && is_array($option)) { - $value = $option['value']; - $label = $option['label']; - } - else { - $value = $key; - $label = $option; + $value = $option['value']; + $label = (string)$option['label']; + $params = (!empty($option['params'])) ? $option['params'] : array(); + } else { + $value = (string)$key; + $label = (string)$option; $isArrayOption = false; + $params = array(); } if (is_array($value)) { - $html.= ''; + $html .= ''; foreach ($value as $keyGroup => $optionGroup) { if (!is_array($optionGroup)) { $optionGroup = array( @@ -126,44 +184,79 @@ protected function _toHtml() 'label' => $optionGroup ); } - $html.= $this->_optionToHtml( + $html .= $this->_optionToHtml( $optionGroup, in_array($optionGroup['value'], $values) ); } - $html.= ''; + $html .= ''; } else { - $html.= $this->_optionToHtml(array( - 'value' => $value, - 'label' => $label - ), + $html .= $this->_optionToHtml( + array( + 'value' => $value, + 'label' => $label, + 'params' => $params + ), in_array($value, $values) ); } } - $html.= ''; + $html .= ''; return $html; } - protected function _optionToHtml($option, $selected=false) + /** + * Return option HTML node + * + * @param array $option + * @param boolean $selected + * @return string + */ + protected function _optionToHtml($option, $selected = false) { $selectedHtml = $selected ? ' selected="selected"' : ''; if ($this->getIsRenderToJsTemplate() === true) { $selectedHtml .= ' #{option_extra_attr_' . self::calcOptionHash($option['value']) . '}'; } - $html = ''; - return $html; + $params = ''; + if (!empty($option['params']) && is_array($option['params'])) { + foreach ($option['params'] as $key => $value) { + if (is_array($value)) { + foreach ($value as $keyMulti => $valueMulti) { + $params .= sprintf(' %s="%s" ', $keyMulti, $valueMulti); + } + } else { + $params .= sprintf(' %s="%s" ', $key, $value); + } + } + } + + return sprintf('', + $this->escapeHtml($option['value']), + $selectedHtml, + $params, + $this->escapeHtml($option['label'])); } + /** + * Alias for toHtml() + * + * @return string + */ public function getHtml() { return $this->toHtml(); } + /** + * Calculate CRC32 hash for option value + * + * @param string $optionValue Value of the option + * @return string + */ public function calcOptionHash($optionValue) { return sprintf('%u', crc32($this->getName() . $this->getId() . $optionValue)); } - } diff --git a/app/code/core/Mage/Core/Block/Messages.php b/app/code/core/Mage/Core/Block/Messages.php index dfa739a7..b3feda3d 100644 --- a/app/code/core/Mage/Core/Block/Messages.php +++ b/app/code/core/Mage/Core/Block/Messages.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -54,6 +54,13 @@ class Mage_Core_Block_Messages extends Mage_Core_Block_Template */ protected $_messagesSecondLevelTagName = 'li'; + /** + * Store content wrapper html tag name for messages html output + * + * @var string + */ + protected $_messagesContentWrapperTagName = 'span'; + /** * Flag which require message text escape * @@ -61,6 +68,13 @@ class Mage_Core_Block_Messages extends Mage_Core_Block_Template */ protected $_escapeMessageFlag = false; + /** + * Storage for used types of message storages + * + * @var array + */ + protected $_usedStorageTypes = array('core/session'); + public function _prepareLayout() { $this->addMessages(Mage::getSingleton('core/session')->getMessages(true)); @@ -231,7 +245,9 @@ public function getGroupedHtml() foreach ( $messages as $message ) { $html.= '<' . $this->_messagesSecondLevelTagName . '>'; + $html.= '<' . $this->_messagesContentWrapperTagName . '>'; $html.= ($this->_escapeMessageFlag) ? $this->htmlEscape($message->getText()) : $message->getText(); + $html.= '_messagesContentWrapperTagName . '>'; $html.= '_messagesSecondLevelTagName . '>'; } $html .= '_messagesFirstLevelTagName . '>'; @@ -268,4 +284,26 @@ public function setMessagesSecondLevelTagName($tagName) { $this->_messagesSecondLevelTagName = $tagName; } + + /** + * Get cache key informative items + * + * @return array + */ + public function getCacheKeyInfo() + { + return array( + 'storage_types' => serialize($this->_usedStorageTypes) + ); + } + + /** + * Add used storage type + * + * @param string $type + */ + public function addStorageType($type) + { + $this->_usedStorageTypes[] = $type; + } } diff --git a/app/code/core/Mage/Core/Block/Profiler.php b/app/code/core/Mage/Core/Block/Profiler.php index 763f3314..02f0b669 100644 --- a/app/code/core/Mage/Core/Block/Profiler.php +++ b/app/code/core/Mage/Core/Block/Profiler.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Store/Switcher.php b/app/code/core/Mage/Core/Block/Store/Switcher.php index 062df915..9adaac9d 100644 --- a/app/code/core/Mage/Core/Block/Store/Switcher.php +++ b/app/code/core/Mage/Core/Block/Store/Switcher.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Template.php b/app/code/core/Mage/Core/Block/Template.php index ed465c3b..d684e3fb 100644 --- a/app/code/core/Mage/Core/Block/Template.php +++ b/app/code/core/Mage/Core/Block/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,6 +34,9 @@ */ class Mage_Core_Block_Template extends Mage_Core_Block_Abstract { + const XML_PATH_DEBUG_TEMPLATE_HINTS = 'dev/debug/template_hints'; + const XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS = 'dev/debug/template_hints_blocks'; + const XML_PATH_TEMPLATE_ALLOW_SYMLINK = 'dev/template/allow_symlink'; /** * View scripts directory @@ -53,6 +56,13 @@ class Mage_Core_Block_Template extends Mage_Core_Block_Abstract protected $_jsUrl; + /** + * Is allowed symlinks flag + * + * @var bool + */ + protected $_allowSymlinks = null; + protected static $_showTemplateHints; protected static $_showTemplateHintsBlocks; @@ -150,19 +160,25 @@ public function assign($key, $value=null) } /** - * Set template location dire + * Set template location directory * * @param string $dir * @return Mage_Core_Block_Template */ public function setScriptPath($dir) { - $this->_viewDir = $dir; + $scriptPath = realpath($dir); + if (strpos($scriptPath, realpath(Mage::getBaseDir('design'))) === 0 || $this->_getAllowSymlinks()) { + $this->_viewDir = $dir; + } else { + Mage::log('Not valid script path:' . $dir, Zend_Log::CRIT, null, null, true); + } return $this; } /** - * Check if dirrect output is allowed for block + * Check if direct output is allowed for block + * * @return bool */ public function getDirectOutput() @@ -176,9 +192,9 @@ public function getDirectOutput() public function getShowTemplateHints() { if (is_null(self::$_showTemplateHints)) { - self::$_showTemplateHints = Mage::getStoreConfig('dev/debug/template_hints') + self::$_showTemplateHints = Mage::getStoreConfig(self::XML_PATH_DEBUG_TEMPLATE_HINTS) && Mage::helper('core')->isDevAllowed(); - self::$_showTemplateHintsBlocks = Mage::getStoreConfig('dev/debug/template_hints_blocks') + self::$_showTemplateHintsBlocks = Mage::getStoreConfig(self::XML_PATH_DEBUG_TEMPLATE_HINTS_BLOCKS) && Mage::helper('core')->isDevAllowed(); } return self::$_showTemplateHints; @@ -194,22 +210,39 @@ public function fetchView($fileName) { Varien_Profiler::start($fileName); - extract ($this->_viewVars); + // EXTR_SKIP protects from overriding + // already defined variables + extract ($this->_viewVars, EXTR_SKIP); $do = $this->getDirectOutput(); if (!$do) { ob_start(); } if ($this->getShowTemplateHints()) { - echo '
    '.$fileName.'
    '; + echo << +
    {$fileName}
    +HTML; if (self::$_showTemplateHintsBlocks) { $thisClass = get_class($this); - echo '
    '.$thisClass.'
    '; + echo <<{$thisClass}
    +HTML; } } try { - include $this->_viewDir . DS . $fileName; + $includeFilePath = realpath($this->_viewDir . DS . $fileName); + if (strpos($includeFilePath, realpath($this->_viewDir)) === 0 || $this->_getAllowSymlinks()) { + include $includeFilePath; + } else { + Mage::log('Not valid template file:'.$fileName, Zend_Log::CRIT, null, null, true); + } + } catch (Exception $e) { ob_get_clean(); throw $e; @@ -309,4 +342,17 @@ public function getCacheKeyInfo() 'template' => $this->getTemplate() ); } + + /** + * Get is allowed symliks flag + * + * @return bool + */ + protected function _getAllowSymlinks() + { + if (is_null($this->_allowSymlinks)) { + $this->_allowSymlinks = Mage::getStoreConfigFlag(self::XML_PATH_TEMPLATE_ALLOW_SYMLINK); + } + return $this->_allowSymlinks; + } } diff --git a/app/code/core/Mage/Core/Block/Template/Facade.php b/app/code/core/Mage/Core/Block/Template/Facade.php index 119d7132..62a5f3ff 100644 --- a/app/code/core/Mage/Core/Block/Template/Facade.php +++ b/app/code/core/Mage/Core/Block/Template/Facade.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Template/Smarty.php b/app/code/core/Mage/Core/Block/Template/Smarty.php index 2e68064d..25ad7288 100644 --- a/app/code/core/Mage/Core/Block/Template/Smarty.php +++ b/app/code/core/Mage/Core/Block/Template/Smarty.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Template/Zend.php b/app/code/core/Mage/Core/Block/Template/Zend.php index 2a5b62cc..ee24aa8f 100644 --- a/app/code/core/Mage/Core/Block/Template/Zend.php +++ b/app/code/core/Mage/Core/Block/Template/Zend.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text.php b/app/code/core/Mage/Core/Block/Text.php index 31542f95..3f96583d 100644 --- a/app/code/core/Mage/Core/Block/Text.php +++ b/app/code/core/Mage/Core/Block/Text.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/List.php b/app/code/core/Mage/Core/Block/Text/List.php index fe30ca2a..f0c286f7 100644 --- a/app/code/core/Mage/Core/Block/Text/List.php +++ b/app/code/core/Mage/Core/Block/Text/List.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/List/Item.php b/app/code/core/Mage/Core/Block/Text/List/Item.php index b10cb52b..2c511542 100644 --- a/app/code/core/Mage/Core/Block/Text/List/Item.php +++ b/app/code/core/Mage/Core/Block/Text/List/Item.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/List/Link.php b/app/code/core/Mage/Core/Block/Text/List/Link.php index 448558c7..e019907f 100644 --- a/app/code/core/Mage/Core/Block/Text/List/Link.php +++ b/app/code/core/Mage/Core/Block/Text/List/Link.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag.php b/app/code/core/Mage/Core/Block/Text/Tag.php index db84f1ec..2daec2a1 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag.php +++ b/app/code/core/Mage/Core/Block/Text/Tag.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag/Css.php b/app/code/core/Mage/Core/Block/Text/Tag/Css.php index 55057de1..5635775d 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag/Css.php +++ b/app/code/core/Mage/Core/Block/Text/Tag/Css.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag/Css/Admin.php b/app/code/core/Mage/Core/Block/Text/Tag/Css/Admin.php index 05293b19..cd56e194 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag/Css/Admin.php +++ b/app/code/core/Mage/Core/Block/Text/Tag/Css/Admin.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag/Debug.php b/app/code/core/Mage/Core/Block/Text/Tag/Debug.php index dc34ad18..e27f86ef 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag/Debug.php +++ b/app/code/core/Mage/Core/Block/Text/Tag/Debug.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag/Js.php b/app/code/core/Mage/Core/Block/Text/Tag/Js.php index 79ae8fe2..ec9b4091 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag/Js.php +++ b/app/code/core/Mage/Core/Block/Text/Tag/Js.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Block/Text/Tag/Meta.php b/app/code/core/Mage/Core/Block/Text/Tag/Meta.php index 345f9128..9bb3d10f 100644 --- a/app/code/core/Mage/Core/Block/Text/Tag/Meta.php +++ b/app/code/core/Mage/Core/Block/Text/Tag/Meta.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Controller/Front/Action.php b/app/code/core/Mage/Core/Controller/Front/Action.php old mode 100644 new mode 100755 index 90d182f2..ba615ccb --- a/app/code/core/Mage/Core/Controller/Front/Action.php +++ b/app/code/core/Mage/Core/Controller/Front/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,6 +33,11 @@ */ class Mage_Core_Controller_Front_Action extends Mage_Core_Controller_Varien_Action { + /** + * Session namespace to refer in other places + */ + const SESSION_NAMESPACE = 'frontend'; + /** * Currently used area * @@ -45,10 +50,10 @@ class Mage_Core_Controller_Front_Action extends Mage_Core_Controller_Varien_Acti * * @var string */ - protected $_sessionNamespace = 'frontend'; + protected $_sessionNamespace = self::SESSION_NAMESPACE; /** - * Predispatch: shoud set layout area + * Predispatch: should set layout area * * @return Mage_Core_Controller_Front_Action */ @@ -86,4 +91,72 @@ public function __() array_unshift($args, $expr); return Mage::app()->getTranslator()->translate($args); } + + /** + * Declare headers and content file in response for file download + * + * @param string $fileName + * @param string|array $content set to null to avoid starting output, $contentLength should be set explicitly in + * that case + * @param string $contentType + * @param int $contentLength explicit content length, if strlen($content) isn't applicable + * @return Mage_Adminhtml_Controller_Action + */ + protected function _prepareDownloadResponse($fileName, $content, $contentType = 'application/octet-stream', + $contentLength = null + ) { + $session = Mage::getSingleton('admin/session'); + if ($session->isFirstPageAfterLogin()) { + $this->_redirect($session->getUser()->getStartupPageUrl()); + return $this; + } + + $isFile = false; + $file = null; + if (is_array($content)) { + if (!isset($content['type']) || !isset($content['value'])) { + return $this; + } + if ($content['type'] == 'filename') { + $isFile = true; + $file = $content['value']; + $contentLength = filesize($file); + } + } + + $this->getResponse() + ->setHttpResponseCode(200) + ->setHeader('Pragma', 'public', true) + ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) + ->setHeader('Content-type', $contentType, true) + ->setHeader('Content-Length', is_null($contentLength) ? strlen($content) : $contentLength) + ->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'"') + ->setHeader('Last-Modified', date('r')); + + if (!is_null($content)) { + if ($isFile) { + $this->getResponse()->clearBody(); + $this->getResponse()->sendHeaders(); + + $ioAdapter = new Varien_Io_File(); + if (!$ioAdapter->fileExists($file)) { + Mage::throwException(Mage::helper('core')->__('File not found')); + } + $ioAdapter->open(array('path' => $ioAdapter->dirname($file))); + $ioAdapter->streamOpen($file, 'r'); + while ($buffer = $ioAdapter->streamRead()) { + print $buffer; + } + $ioAdapter->streamClose(); + if (!empty($content['rm'])) { + $ioAdapter->rm($file); + } + + exit(0); + } else { + $this->getResponse()->setBody($content); + } + } + return $this; + } } diff --git a/app/code/core/Mage/Core/Controller/Front/Router.php b/app/code/core/Mage/Core/Controller/Front/Router.php index 8c75e2de..8625f592 100644 --- a/app/code/core/Mage/Core/Controller/Front/Router.php +++ b/app/code/core/Mage/Core/Controller/Front/Router.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Controller/Request/Http.php b/app/code/core/Mage/Core/Controller/Request/Http.php index ffa7d599..d7ec794a 100644 --- a/app/code/core/Mage/Core/Controller/Request/Http.php +++ b/app/code/core/Mage/Core/Controller/Request/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,6 +35,8 @@ class Mage_Core_Controller_Request_Http extends Zend_Controller_Request_Http { const XML_NODE_DIRECT_FRONT_NAMES = 'global/request/direct_front_name'; + const DEFAULT_HTTP_PORT = 80; + const DEFAULT_HTTPS_PORT = 443; /** * ORIGINAL_PATH_INFO @@ -51,10 +53,11 @@ class Mage_Core_Controller_Request_Http extends Zend_Controller_Request_Http */ protected $_rewritedPathInfo= null; protected $_requestedRouteName = null; + protected $_routingInfo = array(); protected $_route; - protected $_directFrontNames = array(); + protected $_directFrontNames = null; protected $_controllerModule = null; /** @@ -72,15 +75,6 @@ class Mage_Core_Controller_Request_Http extends Zend_Controller_Request_Http */ protected $_beforeForwardInfo = array(); - public function __construct($uri = null) - { - parent::__construct($uri); - $names = Mage::getConfig()->getNode(self::XML_NODE_DIRECT_FRONT_NAMES); - if ($names) { - $this->_directFrontNames = $names->asArray(); - } - } - /** * Returns ORIGINAL_PATH_INFO. * This value is calculated instead of reading PATH_INFO @@ -224,6 +218,14 @@ public function isDirectAccessFrontendName($code) */ public function getDirectFrontNames() { + if (is_null($this->_directFrontNames)) { + $names = Mage::getConfig()->getNode(self::XML_NODE_DIRECT_FRONT_NAMES); + if ($names) { + $this->_directFrontNames = $names->asArray(); + } else { + return array(); + } + } return $this->_directFrontNames; } @@ -295,7 +297,7 @@ public function getHttpHost($trimPort = true) /** * Set a member of the $_POST superglobal * - * @param striing|array $key + * @param string|array $key * @param mixed $value * * @return Mage_Core_Controller_Request_Http @@ -361,6 +363,36 @@ public function getActionName() return $this->_action; } + /** + * Retrieve an alias + * + * Retrieve the actual key represented by the alias $name. + * + * @param string $name + * @return string|null Returns null when no alias exists + */ + public function getAlias($name) + { + $aliases = $this->getAliases(); + if (isset($aliases[$name])) { + return $aliases[$name]; + } + return null; + } + + /** + * Retrieve the list of all aliases + * + * @return array + */ + public function getAliases() + { + if (isset($this->_routingInfo['aliases'])) { + return $this->_routingInfo['aliases']; + } + return parent::getAliases(); + } + /** * Get route name used in request (ignore rewrite) * @@ -368,6 +400,9 @@ public function getActionName() */ public function getRequestedRouteName() { + if (isset($this->_routingInfo['requested_route'])) { + return $this->_routingInfo['requested_route']; + } if ($this->_requestedRouteName === null) { if ($this->_rewritedPathInfo !== null && isset($this->_rewritedPathInfo[0])) { $fronName = $this->_rewritedPathInfo[0]; @@ -388,6 +423,9 @@ public function getRequestedRouteName() */ public function getRequestedControllerName() { + if (isset($this->_routingInfo['requested_controller'])) { + return $this->_routingInfo['requested_controller']; + } if (($this->_rewritedPathInfo !== null) && isset($this->_rewritedPathInfo[1])) { return $this->_rewritedPathInfo[1]; } @@ -401,12 +439,29 @@ public function getRequestedControllerName() */ public function getRequestedActionName() { + if (isset($this->_routingInfo['requested_action'])) { + return $this->_routingInfo['requested_action']; + } if (($this->_rewritedPathInfo !== null) && isset($this->_rewritedPathInfo[2])) { return $this->_rewritedPathInfo[2]; } return $this->getActionName(); } + /** + * Set routing info data + * + * @param array $data + * @return Mage_Core_Controller_Request_Http + */ + public function setRoutingInfo($data) + { + if (is_array($data)) { + $this->_routingInfo = $data; + } + return $this; + } + /** * Collect properties changed by _forward in protected storage * before _forward was called first time. @@ -459,4 +514,20 @@ public function isStraight($flag = null) } return $this->_isStraight; } + + /** + * Check is Request from AJAX + * + * @return boolean + */ + public function isAjax() + { + if ($this->isXmlHttpRequest()) { + return true; + } + if ($this->getParam('ajax') || $this->getParam('isAjax')) { + return true; + } + return false; + } } diff --git a/app/code/core/Mage/Core/Controller/Response/Http.php b/app/code/core/Mage/Core/Controller/Response/Http.php index e871cf36..db9796e9 100644 --- a/app/code/core/Mage/Core/Controller/Response/Http.php +++ b/app/code/core/Mage/Core/Controller/Response/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,11 +32,19 @@ */ class Mage_Core_Controller_Response_Http extends Zend_Controller_Response_Http { + /** + * Transport object for observers to perform + * + * @var Varien_Object + */ + protected static $_transportObject = null; + /** * Fixes CGI only one Status header allowed bug * * @link http://bugs.php.net/bug.php?id=36705 * + * @return Mage_Core_Controller_Response_Http */ public function sendHeaders() { @@ -66,7 +74,7 @@ public function sendHeaders() } } } - parent::sendHeaders(); + return parent::sendHeaders(); } public function sendResponse() @@ -74,4 +82,27 @@ public function sendResponse() Mage::dispatchEvent('http_response_send_before', array('response'=>$this)); return parent::sendResponse(); } + + /** + * Additionally check for session messages in several domains case + * + * @param string $url + * @param int $code + * @return Mage_Core_Controller_Response_Http + */ + public function setRedirect($url, $code = 302) + { + /** + * Use single transport object instance + */ + if (self::$_transportObject === null) { + self::$_transportObject = new Varien_Object; + } + self::$_transportObject->setUrl($url); + self::$_transportObject->setCode($code); + Mage::dispatchEvent('controller_response_redirect', + array('response' => $this, 'transport' => self::$_transportObject)); + + return parent::setRedirect(self::$_transportObject->getUrl(), self::$_transportObject->getCode()); + } } diff --git a/app/code/core/Mage/Core/Controller/Varien/Action.php b/app/code/core/Mage/Core/Controller/Varien/Action.php old mode 100644 new mode 100755 index 195df3df..8f16e3c5 --- a/app/code/core/Mage/Core/Controller/Varien/Action.php +++ b/app/code/core/Mage/Core/Controller/Varien/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,7 +32,7 @@ * * @category Mage * @package Mage_Core - * @author Magento Core Team + * @author Magento Core Team */ abstract class Mage_Core_Controller_Varien_Action { @@ -147,7 +147,6 @@ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Cont protected function _construct() { - } public function hasAction($action) @@ -242,12 +241,12 @@ public function getLayout() /** * Load layout by handles(s) * - * @param string $handles - * @param string $cacheId - * @param boolean $generateBlocks + * @param string|null|bool $handles + * @param bool $generateBlocks + * @param bool $generateXml * @return Mage_Core_Controller_Varien_Action */ - public function loadLayout($handles=null, $generateBlocks=true, $generateXml=true) + public function loadLayout($handles = null, $generateBlocks = true, $generateXml = true) { // if handles were specified in arguments load them first if (false!==$handles && ''!==$handles) { @@ -282,7 +281,9 @@ public function addActionLayoutHandles() // load theme handle $package = Mage::getSingleton('core/design_package'); - $update->addHandle('THEME_'.$package->getArea().'_'.$package->getPackageName().'_'.$package->getTheme('layout')); + $update->addHandle( + 'THEME_'.$package->getArea().'_'.$package->getPackageName().'_'.$package->getTheme('layout') + ); // load action handle $update->addHandle(strtolower($this->getFullActionName())); @@ -450,14 +451,21 @@ public function dispatch($action) } } + /** + * Retrieve action method name + * + * @param string $action + * @return string + */ public function getActionMethodName($action) { - $method = $action.'Action'; - return $method; + return $action . 'Action'; } /** - * Dispatches event before action + * Dispatch event before action + * + * @return void */ public function preDispatch() { @@ -469,23 +477,44 @@ public function preDispatch() } } + // Prohibit disabled store actions + if (Mage::isInstalled() && !Mage::app()->getStore()->getIsActive()) { + Mage::app()->throwStoreException(); + } + if ($this->_rewrite()) { return; } if (!$this->getFlag('', self::FLAG_NO_START_SESSION)) { - $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions); - $checkCookie = $checkCookie && !$this->getRequest()->getParam('nocookie', false); + $checkCookie = in_array($this->getRequest()->getActionName(), $this->_cookieCheckActions) + && !$this->getRequest()->getParam('nocookie', false); $cookies = Mage::getSingleton('core/cookie')->get(); - if ($checkCookie && empty($cookies)) { - $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start(); + + if (empty($cookies)) { + if ($session->getCookieShouldBeReceived()) { + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + $session->unsCookieShouldBeReceived(); + $session->setSkipSessionIdFlag(true); + } elseif ($checkCookie) { + if (isset($_GET[$session->getSessionIdQueryParam()]) && Mage::app()->getUseSessionInUrl() + && $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE + ) { + $session->setCookieShouldBeReceived(true); + } else { + $this->setFlag('', self::FLAG_NO_COOKIES_REDIRECT, true); + } + } } - Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace))->start(); } Mage::app()->loadArea($this->getLayout()->getArea()); - if ($this->getFlag('', self::FLAG_NO_COOKIES_REDIRECT) && Mage::getStoreConfig('web/browser_capabilities/cookies')) { + if ($this->getFlag('', self::FLAG_NO_COOKIES_REDIRECT) + && Mage::getStoreConfig('web/browser_capabilities/cookies') + ) { $this->_forward('noCookies', 'index', 'core'); return; } @@ -494,16 +523,13 @@ public function preDispatch() return; } - Mage::dispatchEvent('controller_action_predispatch', array('controller_action'=>$this)); - Mage::dispatchEvent( - 'controller_action_predispatch_'.$this->getRequest()->getRouteName(), - array('controller_action'=>$this) - ); Varien_Autoload::registerScope($this->getRequest()->getRouteName()); - Mage::dispatchEvent( - 'controller_action_predispatch_'.$this->getFullActionName(), - array('controller_action'=>$this) - ); + + Mage::dispatchEvent('controller_action_predispatch', array('controller_action' => $this)); + Mage::dispatchEvent('controller_action_predispatch_' . $this->getRequest()->getRouteName(), + array('controller_action' => $this)); + Mage::dispatchEvent('controller_action_predispatch_' . $this->getFullActionName(), + array('controller_action' => $this)); } /** @@ -540,7 +566,6 @@ public function norouteAction($coreRoute = null) $this->renderLayout(); } else { $status->setForwarded(true); - #$this->_forward('cmsNoRoute', 'index', 'cms'); $this->_forward( $status->getForwardAction(), $status->getForwardController(), @@ -577,7 +602,7 @@ public function noCookiesAction() * @param string $action * @param string|null $controller * @param string|null $module - * @param string|null $params + * @param array|null $params */ protected function _forward($action, $controller = null, $module = null, array $params = null) { @@ -585,15 +610,15 @@ protected function _forward($action, $controller = null, $module = null, array $ $request->initForward(); - if (!is_null($params)) { + if (isset($params)) { $request->setParams($params); } - if (!is_null($controller)) { + if (isset($controller)) { $request->setControllerName($controller); // Module should only be reset if controller has been specified - if (!is_null($module)) { + if (isset($module)) { $request->setModuleName($module); } } @@ -602,22 +627,45 @@ protected function _forward($action, $controller = null, $module = null, array $ ->setDispatched(false); } + /** + * Initializing layout messages by message storage(s), loading and adding messages to layout messages block + * + * @param string|array $messagesStorage + * @return Mage_Core_Controller_Varien_Action + */ protected function _initLayoutMessages($messagesStorage) { - if ($storage = Mage::getSingleton($messagesStorage)) { - $this->getLayout()->getMessagesBlock()->addMessages($storage->getMessages(true)); - $this->getLayout()->getMessagesBlock()->setEscapeMessageFlag( - $storage->getEscapeMessages(true) - ); + if (!is_array($messagesStorage)) { + $messagesStorage = array($messagesStorage); } - else { - Mage::throwException( - Mage::helper('core')->__('Invalid messages storage "%s" for layout messages initialization', (string)$messagesStorage) - ); + foreach ($messagesStorage as $storageName) { + $storage = Mage::getSingleton($storageName); + if ($storage) { + $block = $this->getLayout()->getMessagesBlock(); + $block->addMessages($storage->getMessages(true)); + $block->setEscapeMessageFlag($storage->getEscapeMessages(true)); + $block->addStorageType($storageName); + } + else { + Mage::throwException( + Mage::helper('core')->__('Invalid messages storage "%s" for layout messages initialization', (string) $storageName) + ); + } } return $this; } + /** + * Initializing layout messages by message storage(s), loading and adding messages to layout messages block + * + * @param string|array $messagesStorage + * @return Mage_Core_Controller_Varien_Action + */ + public function initLayoutMessages($messagesStorage) + { + return $this->_initLayoutMessages($messagesStorage); + } + /** * Set redirect url into response * @@ -635,17 +683,42 @@ protected function _redirectUrl($url) * * @param string $path * @param array $arguments + * @return Mage_Core_Controller_Varien_Action */ - protected function _redirect($path, $arguments=array()) + protected function _redirect($path, $arguments = array()) { + return $this->setRedirectWithCookieCheck($path, $arguments); + } + + /** + * Set redirect into response with session id in URL if it is enabled. + * It allows to distinguish primordial request from browser with cookies disabled. + * + * @param string $path + * @param array $arguments + * @return Mage_Core_Controller_Varien_Action + */ + public function setRedirectWithCookieCheck($path, array $arguments = array()) + { + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session', array('name' => $this->_sessionNamespace)); + if ($session->getCookieShouldBeReceived() && Mage::app()->getUseSessionInUrl() + && $this->_sessionNamespace != Mage_Adminhtml_Controller_Action::SESSION_NAMESPACE + ) { + $arguments += array('_query' => array( + $session->getSessionIdQueryParam() => $session->getSessionId() + )); + } $this->getResponse()->setRedirect(Mage::getUrl($path, $arguments)); return $this; } + /** * Redirect to success page * * @param string $defaultUrl + * @return Mage_Core_Controller_Varien_Action */ protected function _redirectSuccess($defaultUrl) { @@ -664,6 +737,7 @@ protected function _redirectSuccess($defaultUrl) * Redirect to error page * * @param string $defaultUrl + * @return Mage_Core_Controller_Varien_Action */ protected function _redirectError($defaultUrl) { @@ -679,7 +753,7 @@ protected function _redirectError($defaultUrl) } /** - * Set referer url for redirect in responce + * Set referer url for redirect in response * * @param string $defaultUrl * @return Mage_Core_Controller_Varien_Action @@ -714,6 +788,8 @@ protected function _getRefererUrl() $refererUrl = Mage::helper('core')->urlDecode($url); } + $refererUrl = Mage::helper('core')->escapeUrl($refererUrl); + if (!$this->_isUrlInternal($refererUrl)) { $refererUrl = Mage::app()->getStore()->getBaseUrl(); } @@ -733,7 +809,8 @@ protected function _isUrlInternal($url) * Url must start from base secure or base unsecure url */ if ((strpos($url, Mage::app()->getStore()->getBaseUrl()) === 0) - || (strpos($url, Mage::app()->getStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true)) === 0)) { + || (strpos($url, Mage::app()->getStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true)) === 0) + ) { return true; } } @@ -846,6 +923,7 @@ protected function _validateFormKey() * * @see self::_renderTitles() * @param string|false|-1|null $text + * @param bool $resetIfExists * @return Mage_Core_Controller_Varien_Action */ protected function _title($text = null, $resetIfExists = true) @@ -949,4 +1027,72 @@ protected function _filterDateTime($array, $dateFields) } return $array; } + + /** + * Declare headers and content file in response for file download + * + * @param string $fileName + * @param string|array $content set to null to avoid starting output, $contentLength should be set explicitly in + * that case + * @param string $contentType + * @param int $contentLength explicit content length, if strlen($content) isn't applicable + * @return Mage_Core_Controller_Varien_Action + */ + protected function _prepareDownloadResponse( + $fileName, + $content, + $contentType = 'application/octet-stream', + $contentLength = null) + { + $session = Mage::getSingleton('admin/session'); + if ($session->isFirstPageAfterLogin()) { + $this->_redirect($session->getUser()->getStartupPageUrl()); + return $this; + } + + $isFile = false; + $file = null; + if (is_array($content)) { + if (!isset($content['type']) || !isset($content['value'])) { + return $this; + } + if ($content['type'] == 'filename') { + $isFile = true; + $file = $content['value']; + $contentLength = filesize($file); + } + } + + $this->getResponse() + ->setHttpResponseCode(200) + ->setHeader('Pragma', 'public', true) + ->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0', true) + ->setHeader('Content-type', $contentType, true) + ->setHeader('Content-Length', is_null($contentLength) ? strlen($content) : $contentLength, true) + ->setHeader('Content-Disposition', 'attachment; filename="'.$fileName.'"', true) + ->setHeader('Last-Modified', date('r'), true); + + if (!is_null($content)) { + if ($isFile) { + $this->getResponse()->clearBody(); + $this->getResponse()->sendHeaders(); + + $ioAdapter = new Varien_Io_File(); + $ioAdapter->open(array('path' => $ioAdapter->dirname($file))); + $ioAdapter->streamOpen($file, 'r'); + while ($buffer = $ioAdapter->streamRead()) { + print $buffer; + } + $ioAdapter->streamClose(); + if (!empty($content['rm'])) { + $ioAdapter->rm($file); + } + + exit(0); + } else { + $this->getResponse()->setBody($content); + } + } + return $this; + } } diff --git a/app/code/core/Mage/Core/Controller/Varien/Exception.php b/app/code/core/Mage/Core/Controller/Varien/Exception.php index c4de8c0a..db2e3531 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Exception.php +++ b/app/code/core/Mage/Core/Controller/Varien/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Controller/Varien/Front.php b/app/code/core/Mage/Core/Controller/Varien/Front.php index 80a3c09a..0bddf590 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Front.php +++ b/app/code/core/Mage/Core/Controller/Varien/Front.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -119,7 +119,7 @@ public function getRouters() } /** - * Init Fron Controller + * Init Front Controller * * @return Mage_Core_Controller_Varien_Front */ @@ -169,7 +169,6 @@ public function dispatch() Varien_Profiler::start('mage::dispatch::config_url_rewrite'); $this->rewrite(); Varien_Profiler::stop('mage::dispatch::config_url_rewrite'); - Varien_Profiler::start('mage::dispatch::routers_match'); $i = 0; while (!$request->isDispatched() && $i++<100) { @@ -183,7 +182,8 @@ public function dispatch() if ($i>100) { Mage::throwException('Front controller reached 100 router match iterations'); } - + // This event gives possibility to launch something before sending output (allow cookie setting) + Mage::dispatchEvent('controller_front_send_response_before', array('front'=>$this)); Varien_Profiler::start('mage::app::dispatch::send_response'); $this->getResponse()->sendResponse(); Varien_Profiler::stop('mage::app::dispatch::send_response'); @@ -296,27 +296,75 @@ protected function _checkBaseUrl($request) if (!Mage::isInstalled() || $request->getPost()) { return; } - if (!Mage::getStoreConfigFlag('web/url/redirect_to_base')) { + + $redirectCode = (int)Mage::getStoreConfig('web/url/redirect_to_base'); + if (!$redirectCode) { return; + } elseif ($redirectCode != 301) { + $redirectCode = 302; } - $baseUrl = Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB, Mage::app()->getStore()->isCurrentlySecure()); + if ($this->_isAdminFrontNameMatched($request)) { + return; + } + $baseUrl = Mage::getBaseUrl( + Mage_Core_Model_Store::URL_TYPE_WEB, + Mage::app()->getStore()->isCurrentlySecure() + ); if (!$baseUrl) { return; } $uri = @parse_url($baseUrl); - $host = isset($uri['host']) ? $uri['host'] : ''; - $path = isset($uri['path']) ? $uri['path'] : ''; - $requestUri = $request->getRequestUri() ? $request->getRequestUri() : '/'; - if ($host && $host != $request->getHttpHost() || $path && strpos($requestUri, $path) === false) - { + if (isset($uri['scheme']) && $uri['scheme'] != $request->getScheme() + || isset($uri['host']) && $uri['host'] != $request->getHttpHost() + || isset($uri['path']) && strpos($requestUri, $uri['path']) === false + ) { Mage::app()->getFrontController()->getResponse() - ->setRedirect($baseUrl) + ->setRedirect($baseUrl, $redirectCode) ->sendResponse(); exit; } } + + /** + * Check if requested path starts with one of the admin front names + * + * @param Zend_Controller_Request_Http $request + * @return boolean + */ + protected function _isAdminFrontNameMatched($request) + { + $useCustomAdminPath = (bool)(string)Mage::getConfig() + ->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_PATH); + $customAdminPath = (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_PATH); + $adminPath = ($useCustomAdminPath) ? $customAdminPath : null; + + if (!$adminPath) { + $adminPath = (string)Mage::getConfig() + ->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_ADMINHTML_ROUTER_FRONTNAME); + } + $adminFrontNames = array($adminPath); + + // Check for other modules that can use admin router (a lot of Magento extensions do that) + $adminFrontNameNodes = Mage::getConfig()->getNode('admin/routers') + ->xpath('*[not(self::adminhtml) and use = "admin"]/args/frontName'); + + if (is_array($adminFrontNameNodes)) { + foreach ($adminFrontNameNodes as $frontNameNode) { + /** @var $frontNameNode SimpleXMLElement */ + array_push($adminFrontNames, (string)$frontNameNode); + } + } + + $pathPrefix = ltrim($request->getPathInfo(), '/'); + $urlDelimiterPos = strpos($pathPrefix, '/'); + if ($urlDelimiterPos) { + $pathPrefix = substr($pathPrefix, 0, $urlDelimiterPos); + } + + return in_array($pathPrefix, $adminFrontNames); + } } diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Abstract.php b/app/code/core/Mage/Core/Controller/Varien/Router/Abstract.php index b5b3e37a..3d305c09 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Abstract.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php index c20dda01..54f7a59a 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Admin.php @@ -20,17 +20,20 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Core_Controller_Varien_Router_Admin extends Mage_Core_Controller_Varien_Router_Standard { + /** + * Fetch default path + */ public function fetchDefault() { // set defaults - $d = explode('/', (string)Mage::getConfig()->getNode('default/web/default/admin')); + $d = explode('/', $this->_getDefaultPath()); $this->getFront()->setDefault(array( 'module' => !empty($d[0]) ? $d[0] : '', 'controller' => !empty($d[1]) ? $d[1] : 'index', @@ -38,6 +41,15 @@ public function fetchDefault() )); } + /** + * Get router default request path + * @return string + */ + protected function _getDefaultPath() + { + return (string)Mage::getConfig()->getNode('default/web/default/admin'); + } + /** * dummy call to pass through checking * @@ -75,15 +87,46 @@ protected function _noRouteShouldBeApplied() return true; } + /** + * Check whether URL for corresponding path should use https protocol + * + * @param string $path + * @return bool + */ protected function _shouldBeSecure($path) { - return substr((string)Mage::getConfig()->getNode('default/web/unsecure/base_url'),0,5)==='https' + return substr((string)Mage::getConfig()->getNode('default/web/unsecure/base_url'), 0, 5) === 'https' || Mage::getStoreConfigFlag('web/secure/use_in_adminhtml', Mage_Core_Model_App::ADMIN_STORE_ID) - && substr((string)Mage::getConfig()->getNode('default/web/secure/base_url'),0,5)==='https'; + && substr((string)Mage::getConfig()->getNode('default/web/secure/base_url'), 0, 5) === 'https'; } + /** + * Retrieve current secure url + * + * @param Mage_Core_Controller_Request_Http $request + * @return string + */ protected function _getCurrentSecureUrl($request) { - return Mage::app()->getStore(Mage_Core_Model_App::ADMIN_STORE_ID)->getBaseUrl('link', true).ltrim($request->getPathInfo(), '/'); + return Mage::app()->getStore(Mage_Core_Model_App::ADMIN_STORE_ID) + ->getBaseUrl('link', true) . ltrim($request->getPathInfo(), '/'); + } + + /** + * Emulate custom admin url + * + * @param string $configArea + * @param bool $useRouterName + */ + public function collectRoutes($configArea, $useRouterName) + { + if ((string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_USE_CUSTOM_ADMIN_PATH)) { + $customUrl = (string)Mage::getConfig()->getNode(Mage_Adminhtml_Helper_Data::XML_PATH_CUSTOM_ADMIN_PATH); + $xmlPath = Mage_Adminhtml_Helper_Data::XML_PATH_ADMINHTML_ROUTER_FRONTNAME; + if ((string)Mage::getConfig()->getNode($xmlPath) != $customUrl) { + Mage::getConfig()->setNode($xmlPath, $customUrl, true); + } + } + parent::collectRoutes($configArea, $useRouterName); } } diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Default.php b/app/code/core/Mage/Core/Controller/Varien/Router/Default.php index 0a976263..e2eb1973 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Default.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Default.php @@ -20,26 +20,41 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Core_Controller_Varien_Router_Default extends Mage_Core_Controller_Varien_Router_Abstract { + /** + * Modify request and set to no-route action + * If store is admin and specified different admin front name, + * change store to default (Possible when enabled Store Code in URL) + * + * @param Zend_Controller_Request_Http $request + * @return boolean + */ public function match(Zend_Controller_Request_Http $request) { - $d = explode('/', Mage::app()->getStore()->getConfig('web/default/no_route')); - $request->setModuleName(isset($d[0]) ? $d[0] : 'core') - ->setControllerName(isset($d[1]) ? $d[1] : 'index') - ->setActionName(isset($d[2]) ? $d[2] : 'index'); + $noRoute = explode('/', Mage::app()->getStore()->getConfig('web/default/no_route')); + $moduleName = isset($noRoute[0]) ? $noRoute[0] : 'core'; + $controllerName = isset($noRoute[1]) ? $noRoute[1] : 'index'; + $actionName = isset($noRoute[2]) ? $noRoute[2] : 'index'; + + if (Mage::app()->getStore()->isAdmin()) { + $adminFrontName = (string)Mage::getConfig()->getNode('admin/routers/adminhtml/args/frontName'); + if ($adminFrontName != $moduleName) { + $moduleName = 'core'; + $controllerName = 'index'; + $actionName = 'noRoute'; + Mage::app()->setCurrentStore(Mage::app()->getDefaultStoreView()); + } + } + + $request->setModuleName($moduleName) + ->setControllerName($controllerName) + ->setActionName($actionName); return true; } - /* - public function getUrl($routeName, $params) - { - return 'no-route'; - } - */ - } diff --git a/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php b/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php index 0c461efb..99b98d41 100644 --- a/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php +++ b/app/code/core/Mage/Core/Controller/Varien/Router/Standard.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -101,7 +101,12 @@ protected function _afterModuleMatch() return true; } - + /** + * Match the request + * + * @param Zend_Controller_Request_Http $request + * @return boolean + */ public function match(Zend_Controller_Request_Http $request) { //checking before even try to find out that current module @@ -117,9 +122,8 @@ public function match(Zend_Controller_Request_Http $request) if ($path) { $p = explode('/', $path); - } - else { - $p = explode('/', Mage::getStoreConfig('web/default/front')); + } else { + $p = explode('/', $this->_getDefaultPath()); } // get module name @@ -130,7 +134,7 @@ public function match(Zend_Controller_Request_Http $request) $module = $p[0]; } else { $module = $this->getFront()->getDefault('module'); - $request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, ''); + $request->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, ''); } } if (!$module) { @@ -146,20 +150,11 @@ public function match(Zend_Controller_Request_Http $request) */ $modules = $this->getModuleByFrontName($module); - /** - * If we did not found anything we searching exact this module - * name in array values - */ if ($modules === false) { - if ($moduleFrontName = $this->getModuleByName($module, $this->_modules)) { - $modules = array($module); - $module = $moduleFrontName; - } else { - return false; - } + return false; } - //checkings after we foundout that this router should be used for current module + // checks after we found out that this router should be used for current module if (!$this->_afterModuleMatch()) { return false; } @@ -215,7 +210,7 @@ public function match(Zend_Controller_Request_Http $request) } /** - * if we did not found any siutibul + * if we did not found any suitable */ if (!$found) { if ($this->_noRouteShouldBeApplied()) { @@ -228,7 +223,8 @@ public function match(Zend_Controller_Request_Http $request) } // instantiate controller class - $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, $front->getResponse()); + $controllerInstance = Mage::getControllerInstance($controllerClassName, $request, + $front->getResponse()); if (!$controllerInstance->hasAction($action)) { return false; @@ -245,8 +241,8 @@ public function match(Zend_Controller_Request_Http $request) $request->setControllerModule($realModule); // set parameters from pathinfo - for ($i=3, $l=sizeof($p); $i<$l; $i+=2) { - $request->setParam($p[$i], isset($p[$i+1]) ? $p[$i+1] : ''); + for ($i = 3, $l = sizeof($p); $i < $l; $i += 2) { + $request->setParam($p[$i], isset($p[$i+1]) ? urldecode($p[$i+1]) : ''); } // dispatch action @@ -256,6 +252,15 @@ public function match(Zend_Controller_Request_Http $request) return true; } + /** + * Get router default request path + * @return string + */ + protected function _getDefaultPath() + { + return Mage::getStoreConfig('web/default/front'); + } + /** * Allow to control if we need to enable no route functionality in current router * @@ -407,18 +412,29 @@ public function rewrite(array $p) $p[2] = trim((string)$action); } } -#echo "
    ".print_r($p,1)."
    "; + return $p; } - protected function _checkShouldBeSecure($request, $path='') + /** + * Check that request uses https protocol if it should. + * Function redirects user to correct URL if needed. + * + * @param Mage_Core_Controller_Request_Http $request + * @param string $path + * @return void + */ + protected function _checkShouldBeSecure($request, $path = '') { if (!Mage::isInstalled() || $request->getPost()) { return; } - if ($this->_shouldBeSecure($path) && !Mage::app()->getStore()->isCurrentlySecure()) { + if ($this->_shouldBeSecure($path) && !$request->isSecure()) { $url = $this->_getCurrentSecureUrl($request); + if ($request->getRouteName() != 'adminhtml' && Mage::app()->getUseSessionInUrl()) { + $url = Mage::getSingleton('core/url')->getRedirectUrl($url); + } Mage::app()->getFrontController()->getResponse() ->setRedirect($url) @@ -436,11 +452,17 @@ protected function _getCurrentSecureUrl($request) return Mage::getBaseUrl('link', true).ltrim($request->getPathInfo(), '/'); } + /** + * Check whether URL for corresponding path should use https protocol + * + * @param string $path + * @return bool + */ protected function _shouldBeSecure($path) { - return substr(Mage::getStoreConfig('web/unsecure/base_url'),0,5)==='https' + return substr(Mage::getStoreConfig('web/unsecure/base_url'), 0, 5) === 'https' || Mage::getStoreConfigFlag('web/secure/use_in_frontend') - && substr(Mage::getStoreConfig('web/secure/base_url'),0,5)=='https' - && Mage::getConfig()->shouldUrlBeSecure($path); + && substr(Mage::getStoreConfig('web/secure/base_url'), 0, 5) == 'https' + && Mage::getConfig()->shouldUrlBeSecure($path); } } diff --git a/app/code/core/Mage/Core/Exception.php b/app/code/core/Mage/Core/Exception.php index edc593f7..92d66ac5 100644 --- a/app/code/core/Mage/Core/Exception.php +++ b/app/code/core/Mage/Core/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Helper/Abstract.php b/app/code/core/Mage/Core/Helper/Abstract.php old mode 100644 new mode 100755 index 65d645d8..2897e56d --- a/app/code/core/Mage/Core/Helper/Abstract.php +++ b/app/code/core/Mage/Core/Helper/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -215,10 +215,10 @@ public function escapeHtml($data, $allowedTags = null) if (is_array($allowedTags) and !empty($allowedTags)) { $allowed = implode('|', $allowedTags); $result = preg_replace('/<([\/\s\r\n]*)(' . $allowed . ')([\/\s\r\n]*)>/si', '##$1$2$3##', $data); - $result = htmlspecialchars($result); + $result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8', false); $result = preg_replace('/##([\/\s\r\n]*)(' . $allowed . ')([\/\s\r\n]*)##/si', '<$1$2$3>', $result); } else { - $result = htmlspecialchars($data); + $result = htmlspecialchars($data, ENT_COMPAT, 'UTF-8', false); } } else { $result = $data; @@ -227,6 +227,19 @@ public function escapeHtml($data, $allowedTags = null) return $result; } + /** + * Remove html tags, but leave "<" and ">" signs + * + * @param string $html + * @return string + */ + public function removeTags($html) + { + $html = preg_replace("# <(?![/a-z]) | (?<=\s)>(?![a-z]) #exi", "htmlentities('$0')", $html); + $html = strip_tags($html); + return htmlspecialchars_decode($html); + } + /** * Wrapper for standart strip_tags() function with extra functionality for html entities * @@ -280,6 +293,22 @@ public function jsQuoteEscape($data, $quote='\'') return str_replace($quote, '\\'.$quote, $data); } + /** + * Escape quotes inside html attributes + * Use $addSlashes = false for escaping js that inside html attribute (onClick, onSubmit etc) + * + * @param string $data + * @param bool $addSlashes + * @return string + */ + public function quoteEscape($data, $addSlashes = false) + { + if ($addSlashes === true) { + $data = addslashes($data); + } + return htmlspecialchars($data, ENT_QUOTES, null, false); + } + /** * Retrieve url * @@ -318,7 +347,7 @@ public function getLayout() * base64_encode() for URLs encoding * * @param string $url - * @return string + * @return string */ public function urlEncode($url) { @@ -329,7 +358,7 @@ public function urlEncode($url) * base64_dencode() for URLs dencoding * * @param string $url - * @return string + * @return string */ public function urlDecode($url) { @@ -341,7 +370,7 @@ public function urlDecode($url) * Translate array * * @param array $arr - * @return array + * @return array */ public function translateArray($arr = array()) { diff --git a/app/code/core/Mage/Core/Helper/Cookie.php b/app/code/core/Mage/Core/Helper/Cookie.php new file mode 100644 index 00000000..17d5020a --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Cookie.php @@ -0,0 +1,96 @@ + + */ +class Mage_Core_Helper_Cookie extends Mage_Core_Helper_Abstract +{ + /** + * Cookie name for users who allowed cookie save + */ + const IS_USER_ALLOWED_SAVE_COOKIE = 'user_allowed_save_cookie'; + + /** + * Path to configuration, check is enable cookie restriction mode + */ + const XML_PATH_COOKIE_RESTRICTION = 'web/cookie/cookie_restriction'; + + /** + * Cookie restriction lifetime configuration path + */ + const XML_PATH_COOKIE_RESTRICTION_LIFETIME = 'web/cookie/cookie_restriction_lifetime'; + + /** + * Check if cookie restriction notice should be displayed + * + * @return bool + */ + public function isUserNotAllowSaveCookie() + { + $acceptedSaveCookiesWebsites = $this->_getAcceptedSaveCookiesWebsites(); + return Mage::getStoreConfig(self::XML_PATH_COOKIE_RESTRICTION) && + empty($acceptedSaveCookiesWebsites[Mage::app()->getWebsite()->getId()]); + } + + /** + * Return serialzed list of accepted save cookie website + * + * @return string + */ + public function getAcceptedSaveCookiesWebsiteIds() + { + $acceptedSaveCookiesWebsites = $this->_getAcceptedSaveCookiesWebsites(); + $acceptedSaveCookiesWebsites[Mage::app()->getWebsite()->getId()] = 1; + return serialize($acceptedSaveCookiesWebsites); + } + + /** + * Get accepted save cookies websites + * + * @return array + */ + protected function _getAcceptedSaveCookiesWebsites() + { + $serializedList = Mage::getSingleton('core/cookie')->get(self::IS_USER_ALLOWED_SAVE_COOKIE); + $unSerializedList = unserialize($serializedList); + return is_array($unSerializedList) ? $unSerializedList : array(); + } + + /** + * Get cookie restriction lifetime (in seconds) + * + * @return int + */ + public function getCookieRestrictionLifetime() + { + return (int)Mage::getStoreConfig(self::XML_PATH_COOKIE_RESTRICTION_LIFETIME); + } +} diff --git a/app/code/core/Mage/Core/Helper/Data.php b/app/code/core/Mage/Core/Helper/Data.php index 9c9e8b29..b64d966a 100644 --- a/app/code/core/Mage/Core/Helper/Data.php +++ b/app/code/core/Mage/Core/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,18 +31,55 @@ */ class Mage_Core_Helper_Data extends Mage_Core_Helper_Abstract { + const XML_PATH_DEFAULT_COUNTRY = 'general/country/default'; + const XML_PATH_PROTECTED_FILE_EXTENSIONS = 'general/file/protected_extensions'; + const XML_PATH_PUBLIC_FILES_VALID_PATHS = 'general/file/public_files_valid_paths'; + const XML_PATH_ENCRYPTION_MODEL = 'global/helpers/core/encryption_model'; + const XML_PATH_DEV_ALLOW_IPS = 'dev/restrict/allow_ips'; + const XML_PATH_CACHE_BETA_TYPES = 'global/cache/betatypes'; + const XML_PATH_CONNECTION_TYPE = 'global/resources/default_setup/connection/type'; + + const CHARS_LOWERS = 'abcdefghijklmnopqrstuvwxyz'; + const CHARS_UPPERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const CHARS_DIGITS = '0123456789'; + const CHARS_SPECIALS = '!$*+-.=?@^_|~'; + const CHARS_PASSWORD_LOWERS = 'abcdefghjkmnpqrstuvwxyz'; + const CHARS_PASSWORD_UPPERS = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; + const CHARS_PASSWORD_DIGITS = '23456789'; + const CHARS_PASSWORD_SPECIALS = '!$*-.=?@_'; + + /** + * Config pathes to merchant country code and merchant VAT number + */ + const XML_PATH_MERCHANT_COUNTRY_CODE = 'general/store_information/merchant_country'; + const XML_PATH_MERCHANT_VAT_NUMBER = 'general/store_information/merchant_vat_number'; + const XML_PATH_EU_COUNTRIES_LIST = 'general/country/eu_countries'; + + /** + * Const for correct dividing decimal values + */ + const DIVIDE_EPSILON = 10000; + /** * @var Mage_Core_Model_Encryption */ protected $_encryptor = null; + protected $_allowedFormats = array( + Mage_Core_Model_Locale::FORMAT_TYPE_FULL, + Mage_Core_Model_Locale::FORMAT_TYPE_LONG, + Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM, + Mage_Core_Model_Locale::FORMAT_TYPE_SHORT + ); + + /** * @return Mage_Core_Model_Encryption */ public function getEncryptor() { if ($this->_encryptor === null) { - $encryptionModel = (string)Mage::getConfig()->getNode('global/helpers/core/encryption_model'); + $encryptionModel = (string)Mage::getConfig()->getNode(self::XML_PATH_ENCRYPTION_MODEL); if ($encryptionModel) { $this->_encryptor = new $encryptionModel; } else { @@ -62,14 +99,33 @@ public function getEncryptor() * @param bool $includeContainer * @return mixed */ - public static function currency($value, $format=true, $includeContainer = true) + public static function currency($value, $format = true, $includeContainer = true) + { + return self::currencyByStore($value, null, $format, $includeContainer); + } + + /** + * Convert and format price value for specified store + * + * @param float $value + * @param int|Mage_Core_Model_Store $store + * @param bool $format + * @param bool $includeContainer + * @return mixed + */ + public static function currencyByStore($value, $store = null, $format = true, $includeContainer = true) { try { - $value = Mage::app()->getStore()->convertPrice($value, $format, $includeContainer); + if (!($store instanceof Mage_Core_Model_Store)) { + $store = Mage::app()->getStore($store); + } + + $value = $store->convertPrice($value, $format, $includeContainer); } catch (Exception $e){ $value = $e->getMessage(); } + return $value; } @@ -98,19 +154,16 @@ public function formatPrice($price, $includeContainer = true) } /** - * Format date using current locale options + * Format date using current locale options and time zone. * - * @param date|Zend_Date|null $date in GMT timezone - * @param string $format - * @param bool $showTime + * @param date|Zend_Date|null $date + * @param string $format See Mage_Core_Model_Locale::FORMAT_TYPE_* constants + * @param bool $showTime Whether to include time * @return string */ - public function formatDate($date=null, $format='short', $showTime=false) + public function formatDate($date = null, $format = Mage_Core_Model_Locale::FORMAT_TYPE_SHORT, $showTime = false) { - if (Mage_Core_Model_Locale::FORMAT_TYPE_FULL !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_LONG !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_SHORT !==$format) { + if (!in_array($format, $this->_allowedFormats, true)) { return $date; } if (!($date instanceof Zend_Date) && $date && !strtotime($date)) { @@ -118,15 +171,13 @@ public function formatDate($date=null, $format='short', $showTime=false) } if (is_null($date)) { $date = Mage::app()->getLocale()->date(Mage::getSingleton('core/date')->gmtTimestamp(), null, null); - } - elseif (!$date instanceof Zend_Date) { - $date = Mage::app()->getLocale()->date(strtotime($date), null, null, $showTime); + } else if (!$date instanceof Zend_Date) { + $date = Mage::app()->getLocale()->date(strtotime($date), null, null); } if ($showTime) { $format = Mage::app()->getLocale()->getDateTimeFormat($format); - } - else { + } else { $format = Mage::app()->getLocale()->getDateFormat($format); } @@ -137,33 +188,27 @@ public function formatDate($date=null, $format='short', $showTime=false) * Format time using current locale options * * @param date|Zend_Date|null $time - * @param string $format - * @param bool $showTime + * @param string $format + * @param bool $showDate * @return string */ - public function formatTime($time=null, $format='short', $showDate=false) + public function formatTime($time = null, $format = Mage_Core_Model_Locale::FORMAT_TYPE_SHORT, $showDate = false) { - if (Mage_Core_Model_Locale::FORMAT_TYPE_FULL !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_LONG !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM !==$format && - Mage_Core_Model_Locale::FORMAT_TYPE_SHORT !==$format) { + if (!in_array($format, $this->_allowedFormats, true)) { return $time; } if (is_null($time)) { $date = Mage::app()->getLocale()->date(time()); - } - elseif ($time instanceof Zend_Date) { + } else if ($time instanceof Zend_Date) { $date = $time; - } - else { + } else { $date = Mage::app()->getLocale()->date(strtotime($time)); } if ($showDate) { $format = Mage::app()->getLocale()->getDateTimeFormat($format); - } - else { + } else { $format = Mage::app()->getLocale()->getTimeFormat($format); } @@ -203,10 +248,10 @@ public function validateKey($key) return $this->getEncryptor()->validateKey($key); } - public function getRandomString($len, $chars=null) + public function getRandomString($len, $chars = null) { if (is_null($chars)) { - $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + $chars = self::CHARS_LOWERS . self::CHARS_UPPERS . self::CHARS_DIGITS; } mt_srand(10000000*(double)microtime()); for ($i = 0, $str = '', $lc = strlen($chars)-1; $i < $len; $i++) { @@ -249,16 +294,33 @@ public function removeAccents($string, $german=false) if (empty($replacements[$german])) { $subst = array( // single ISO-8859-1 letters - 192=>'A', 193=>'A', 194=>'A', 195=>'A', 196=>'A', 197=>'A', 199=>'C', 208=>'D', 200=>'E', 201=>'E', 202=>'E', 203=>'E', 204=>'I', 205=>'I', 206=>'I', 207=>'I', 209=>'N', 210=>'O', 211=>'O', 212=>'O', 213=>'O', 214=>'O', 216=>'O', 138=>'S', 217=>'U', 218=>'U', 219=>'U', 220=>'U', 221=>'Y', 142=>'Z', 224=>'a', 225=>'a', 226=>'a', 227=>'a', 228=>'a', 229=>'a', 231=>'c', 232=>'e', 233=>'e', 234=>'e', 235=>'e', 236=>'i', 237=>'i', 238=>'i', 239=>'i', 241=>'n', 240=>'o', 242=>'o', 243=>'o', 244=>'o', 245=>'o', 246=>'o', 248=>'o', 154=>'s', 249=>'u', 250=>'u', 251=>'u', 252=>'u', 253=>'y', 255=>'y', 158=>'z', + 192=>'A', 193=>'A', 194=>'A', 195=>'A', 196=>'A', 197=>'A', 199=>'C', + 208=>'D', 200=>'E', 201=>'E', 202=>'E', 203=>'E', 204=>'I', 205=>'I', + 206=>'I', 207=>'I', 209=>'N', 210=>'O', 211=>'O', 212=>'O', 213=>'O', + 214=>'O', 216=>'O', 138=>'S', 217=>'U', 218=>'U', 219=>'U', 220=>'U', + 221=>'Y', 142=>'Z', 224=>'a', 225=>'a', 226=>'a', 227=>'a', 228=>'a', + 229=>'a', 231=>'c', 232=>'e', 233=>'e', 234=>'e', 235=>'e', 236=>'i', + 237=>'i', 238=>'i', 239=>'i', 241=>'n', 240=>'o', 242=>'o', 243=>'o', + 244=>'o', 245=>'o', 246=>'o', 248=>'o', 154=>'s', 249=>'u', 250=>'u', + 251=>'u', 252=>'u', 253=>'y', 255=>'y', 158=>'z', // HTML entities - 258=>'A', 260=>'A', 262=>'C', 268=>'C', 270=>'D', 272=>'D', 280=>'E', 282=>'E', 286=>'G', 304=>'I', 313=>'L', 317=>'L', 321=>'L', 323=>'N', 327=>'N', 336=>'O', 340=>'R', 344=>'R', 346=>'S', 350=>'S', 354=>'T', 356=>'T', 366=>'U', 368=>'U', 377=>'Z', 379=>'Z', 259=>'a', 261=>'a', 263=>'c', 269=>'c', 271=>'d', 273=>'d', 281=>'e', 283=>'e', 287=>'g', 305=>'i', 322=>'l', 314=>'l', 318=>'l', 324=>'n', 328=>'n', 337=>'o', 341=>'r', 345=>'r', 347=>'s', 351=>'s', 357=>'t', 355=>'t', 367=>'u', 369=>'u', 378=>'z', 380=>'z', + 258=>'A', 260=>'A', 262=>'C', 268=>'C', 270=>'D', 272=>'D', 280=>'E', + 282=>'E', 286=>'G', 304=>'I', 313=>'L', 317=>'L', 321=>'L', 323=>'N', + 327=>'N', 336=>'O', 340=>'R', 344=>'R', 346=>'S', 350=>'S', 354=>'T', + 356=>'T', 366=>'U', 368=>'U', 377=>'Z', 379=>'Z', 259=>'a', 261=>'a', + 263=>'c', 269=>'c', 271=>'d', 273=>'d', 281=>'e', 283=>'e', 287=>'g', + 305=>'i', 322=>'l', 314=>'l', 318=>'l', 324=>'n', 328=>'n', 337=>'o', + 341=>'r', 345=>'r', 347=>'s', 351=>'s', 357=>'t', 355=>'t', 367=>'u', + 369=>'u', 378=>'z', 380=>'z', // ligatures 198=>'Ae', 230=>'ae', 140=>'Oe', 156=>'oe', 223=>'ss', ); if ($german) { // umlauts - $subst = array_merge($subst, array(196=>'Ae', 228=>'ae', 214=>'Oe', 246=>'oe', 220=>'Ue', 252=>'ue')); + $subst = array_merge($subst, array( + 196=>'Ae', 228=>'ae', 214=>'Oe', 246=>'oe', 220=>'Ue', 252=>'ue' + )); } $replacements[$german] = array(); @@ -283,7 +345,7 @@ public function isDevAllowed($storeId=null) { $allow = true; - $allowedIps = Mage::getStoreConfig('dev/restrict/allow_ips', $storeId); + $allowedIps = Mage::getStoreConfig(self::XML_PATH_DEV_ALLOW_IPS, $storeId); $remoteAddr = Mage::helper('core/http')->getRemoteAddr(); if (!empty($allowedIps) && !empty($remoteAddr)) { $allowedIps = preg_split('#\s*,\s*#', $allowedIps, null, PREG_SPLIT_NO_EMPTY); @@ -319,7 +381,7 @@ public function getCacheTypes() public function getCacheBetaTypes() { $types = array(); - $config = Mage::getConfig()->getNode('global/cache/betatypes'); + $config = Mage::getConfig()->getNode(self::XML_PATH_CACHE_BETA_TYPES); if ($config) { foreach ($config->children() as $type=>$node) { $types[$type] = (string)$node->label; @@ -381,6 +443,13 @@ public function copyFieldset($fieldset, $aspect, $source, $target, $root='global $result = true; } + $eventName = sprintf('core_copy_fieldset_%s_%s', $fieldset, $aspect); + Mage::dispatchEvent($eventName, array( + 'target' => $target, + 'source' => $source, + 'root' => $root + )); + return $result; } @@ -598,15 +667,18 @@ public function jsonDecode($encodedValue, $objectDecodeType = Zend_Json::TYPE_AR */ public function uniqHash($prefix = '') { - return $prefix . md5(uniqid(microtime(), true)); + return $prefix . md5(uniqid(microtime().mt_rand(), true)); } /** * Merge specified files into one * - * By default will not merge, if there is already merged file exists and it was modified after its components - * If target file is specified, will attempt to write merged contents into it, otherwise will return merged content - * May apply callback to each file contents. Callback gets parameters: (, ) + * By default will not merge, if there is already merged file exists and it + * was modified after its components + * If target file is specified, will attempt to write merged contents into it, + * otherwise will return merged content + * May apply callback to each file contents. Callback gets parameters: + * (, ) * May filter files by specified extension(s) * Returns false on error * @@ -617,7 +689,8 @@ public function uniqHash($prefix = '') * @param array|string $extensionsFilter * @return bool|string */ - public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = false, $beforeMergeCallback = null, $extensionsFilter = array()) + public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = false, + $beforeMergeCallback = null, $extensionsFilter = array()) { try { // check whether merger is required @@ -628,7 +701,7 @@ public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = fa } else { $targetMtime = filemtime($targetFile); foreach ($srcFiles as $file) { - if (filemtime($file) > $targetMtime) { + if (!file_exists($file) || @filemtime($file) > $targetMtime) { $shouldMerge = true; break; } @@ -690,4 +763,118 @@ public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = fa } return false; } + + /** + * Return default country code + * + * @param Mage_Core_Model_Store|string|int $store + * @return string + */ + public function getDefaultCountry($store = null) + { + return Mage::getStoreConfig(self::XML_PATH_DEFAULT_COUNTRY, $store); + } + + /** + * Return list with protected file extensions + * + * @param Mage_Core_Model_Store|string|int $store + * @return array + */ + public function getProtectedFileExtensions($store = null) + { + return Mage::getStoreConfig(self::XML_PATH_PROTECTED_FILE_EXTENSIONS, $store); + } + + /** + * Return list with public files valid paths + * + * @return array + */ + public function getPublicFilesValidPath() + { + return Mage::getStoreConfig(self::XML_PATH_PUBLIC_FILES_VALID_PATHS); + } + + /** + * Check LFI protection + * + * @throws Mage_Core_Exception + * @param string $name + * @return bool + */ + public function checkLfiProtection($name) + { + if (preg_match('#\.\.[\\\/]#', $name)) { + throw new Mage_Core_Exception($this->__('Requested file may not include parent directory traversal ("../", "..\\" notation)')); + } + return true; + } + + /** + * Check whether database compatible mode is used (configs enable it for MySQL by default). + * + * @return bool + */ + public function useDbCompatibleMode() + { + $connType = (string) Mage::getConfig()->getNode(self::XML_PATH_CONNECTION_TYPE); + $path = 'global/resource/connection/types/' . $connType . '/compatibleMode'; + $value = (string) Mage::getConfig()->getNode($path); + return (bool) $value; + } + + /** + * Retrieve merchant country code + * + * @param Mage_Core_Model_Store|string|int|null $store + * @return string + */ + public function getMerchantCountryCode($store = null) + { + return (string) Mage::getStoreConfig(self::XML_PATH_MERCHANT_COUNTRY_CODE, $store); + } + + /** + * Retrieve merchant VAT number + * + * @param Mage_Core_Model_Store|string|int|null $store + * @return string + */ + public function getMerchantVatNumber($store = null) + { + return (string) Mage::getStoreConfig(self::XML_PATH_MERCHANT_VAT_NUMBER, $store); + } + + /** + * Check whether specified country is in EU countries list + * + * @param string $countryCode + * @param null|int $storeId + * @return bool + */ + public function isCountryInEU($countryCode, $storeId = null) + { + $euCountries = explode(',', Mage::getStoreConfig(self::XML_PATH_EU_COUNTRIES_LIST, $storeId)); + return in_array($countryCode, $euCountries); + } + + /** + * Returns the floating point remainder (modulo) of the division of the arguments + * + * @param float|int $dividend + * @param float|int $divisor + * @return float|int + */ + public function getExactDivision($dividend, $divisor) + { + $epsilon = $divisor / self::DIVIDE_EPSILON; + + $remainder = fmod($dividend, $divisor); + if (abs($remainder - $divisor) < $epsilon || abs($remainder) < $epsilon) { + $remainder = 0; + } + + return $remainder; + } } diff --git a/app/code/core/Mage/Core/Helper/File/Storage.php b/app/code/core/Mage/Core/Helper/File/Storage.php new file mode 100644 index 00000000..e09f923f --- /dev/null +++ b/app/code/core/Mage/Core/Helper/File/Storage.php @@ -0,0 +1,137 @@ + + */ +class Mage_Core_Helper_File_Storage extends Mage_Core_Helper_Abstract +{ + /** + * Current storage code + * + * @var int + */ + protected $_currentStorage = null; + + /** + * List of internal storages + * + * @var array + */ + protected $_internalStorageList = array( + Mage_Core_Model_File_Storage::STORAGE_MEDIA_FILE_SYSTEM + ); + + /** + * Return saved storage code + * + * @return int + */ + public function getCurrentStorageCode() + { + if (is_null($this->_currentStorage)) { + $this->_currentStorage = (int) Mage::app() + ->getConfig()->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA); + } + + return $this->_currentStorage; + } + + /** + * Retrieve file system storage model + * + * @return Mage_Core_Model_File_Storage_File + */ + public function getStorageFileModel() + { + return Mage::getSingleton('core/file_storage_file'); + } + + /** + * Check if storage is internal + * + * @param int|null $storage + * @return bool + */ + public function isInternalStorage($storage = null) + { + $storage = (!is_null($storage)) ? (int) $storage : $this->getCurrentStorageCode(); + + return in_array($storage, $this->_internalStorageList); + } + + /** + * Retrieve storage model + * + * @param int|null $storage + * @param array $params + * @return Mage_Core_Model_Abstract|bool + */ + public function getStorageModel($storage = null, $params = array()) + { + return Mage::getSingleton('core/file_storage')->getStorageModel($storage, $params); + } + + /** + * Check if needed to copy file from storage to file system and + * if file exists in the storage + * + * @param string $filename + * @return bool|int + */ + public function processStorageFile($filename) + { + if ($this->isInternalStorage()) { + return false; + } + + $dbHelper = Mage::helper('core/file_storage_database'); + + $relativePath = $dbHelper->getMediaRelativePath($filename); + $file = $this->getStorageModel()->loadByFilename($relativePath); + + if (!$file->getId()) { + return false; + } + + return $this->saveFileToFileSystem($file); + } + + /** + * Save file to file system + * + * @param Mage_Core_Model_File_Storage_Database $file + * @return bool|int + */ + public function saveFileToFileSystem($file) + { + return $this->getStorageFileModel()->saveFile($file, true); + } +} diff --git a/app/code/core/Mage/Core/Helper/File/Storage/Database.php b/app/code/core/Mage/Core/Helper/File/Storage/Database.php new file mode 100644 index 00000000..413c9629 --- /dev/null +++ b/app/code/core/Mage/Core/Helper/File/Storage/Database.php @@ -0,0 +1,307 @@ + + */ +class Mage_Core_Helper_File_Storage_Database extends Mage_Core_Helper_Abstract +{ + /** + * Database storage model + * @var null|Mage_Core_Model_File_Storage_Database + */ + protected $_databaseModel = null; + + /** + * Storage resource model + * @var null|Mage_Core_Model_Mysql4_File_Storage_Database + */ + protected $_resourceModel = null; + + /** + * Db usage flag + * + * @var bool + */ + protected $_useDb = null; + + /** + * Media dir + * + * @var string + */ + protected $_mediaBaseDirectory; + + /** + * Check if we use DB storage + * Note: Disabled as not completed feature + * + * @return bool + */ + public function checkDbUsage() + { + if (null === $this->_useDb) { + $currentStorage = (int) Mage::app()->getConfig() + ->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA); + $this->_useDb = ($currentStorage == Mage_Core_Model_File_Storage::STORAGE_MEDIA_DATABASE); + } + + return $this->_useDb; + } + + /** + * Get database storage model + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function getStorageDatabaseModel() + { + if (is_null($this->_databaseModel)) { + $this->_databaseModel = Mage::getModel('core/file_storage_database'); + } + + return $this->_databaseModel; + } + + /** + * Get file storage model + * + * @return Mage_Core_Model_File_Storage_File + */ + public function getStorageFileModel() + { + return Mage::getSingleton('core/file_storage_file'); + } + + /** + * Get storage model + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function getResourceStorageModel() + { + if (is_null($this->_resourceModel)) { + $this->_resourceModel = $this->getStorageDatabaseModel()->getResource(); + } + return $this->_resourceModel; + } + + /** + * Save file in DB storage + * + * @param string $filename + */ + public function saveFile($filename) + { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel()->saveFile($this->_removeAbsPathFromFileName($filename)); + } + } + + /** + * Rename file in DB storage + * + * @param string $oldName + * @param string $newName + */ + public function renameFile($oldName, $newName) + { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel() + ->renameFile($this->_removeAbsPathFromFileName($oldName), $this->_removeAbsPathFromFileName($newName)); + } + } + + /** + * Copy file in DB storage + * + * @param string $oldName + * @param string $newName + */ + public function copyFile($oldName, $newName) { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel() + ->copyFile($this->_removeAbsPathFromFileName($oldName), $this->_removeAbsPathFromFileName($newName)); + } + } + + /** + * Check whether file exists in DB + * + * @param string $filename can be both full path or partial (like in DB) + * @return bool|null + */ + public function fileExists($filename) + { + if ($this->checkDbUsage()) { + return $this->getStorageDatabaseModel()->fileExists($this->_removeAbsPathFromFileName($filename)); + } else { + return null; + } + } + + /** + * Get unique name for passed file in case this file already exists + * + * @param string $directory - can be both full path or partial (like in DB) + * @param string $filename - not just a filename. Can have directory chunks. return will have this form + * @return string + */ + public function getUniqueFilename($directory, $filename) + { + if ($this->checkDbUsage()) { + $directory = $this->_removeAbsPathFromFileName($directory); + if($this->fileExists($directory . $filename)) { + $index = 1; + $extension = strrchr($filename, '.'); + $filenameWoExtension = substr($filename, 0, -1 * strlen($extension)); + while ($this->fileExists($directory . $filenameWoExtension . '_' . $index . $extension)) { + $index ++; + } + $filename = $filenameWoExtension . '_' . $index . $extension; + } + } + return $filename; + } + + /** + * Save database file to file system + * + * @param string $filename + * @return bool|int + */ + public function saveFileToFilesystem($filename) { + if ($this->checkDbUsage()) { + /** @var $file Mage_Core_Model_File_Storage_Database */ + $file = Mage::getModel('core/file_storage_database') + ->loadByFilename($this->_removeAbsPathFromFileName($filename)); + if (!$file->getId()) { + return false; + } + + return $this->getStorageFileModel()->saveFile($file, true); + } + } + + /** + * Return relative uri for media content by full path + * + * @param string $fullPath + * @return string + */ + public function getMediaRelativePath($fullPath) + { + $relativePath = ltrim(str_replace($this->getMediaBaseDir(), '', $fullPath), '\\/'); + return str_replace(DS, '/', $relativePath); + } + + /** + * Deletes from DB files, which belong to one folder + * + * @param string $folderName + */ + public function deleteFolder($folderName) + { + if ($this->checkDbUsage()) { + $this->getResourceStorageModel()->deleteFolder($this->_removeAbsPathFromFileName($folderName)); + } + } + + /** + * Deletes from DB files, which belong to one folder + * + * @param string $filename + */ + public function deleteFile($filename) + { + if ($this->checkDbUsage()) { + $this->getStorageDatabaseModel()->deleteFile($this->_removeAbsPathFromFileName($filename)); + } + } + + /** + * Saves uploaded by Mage_Core_Model_File_Uploader file to DB with existence tests + * + * param $result should be result from Mage_Core_Model_File_Uploader::save() method + * Checks in DB, whether uploaded file exists ($result['file']) + * If yes, renames file on FS (!!!!!) + * Saves file with unique name into DB + * If passed file exists returns new name, file was renamed to (in the same context) + * Otherwise returns $result['file'] + * + * @param array $result + * @return string + */ + public function saveUploadedFile($result = array()) + { + if ($this->checkDbUsage()) { + $path = rtrim(str_replace(array('\\', '/'), DS, $result['path']), DS); + $file = '/' . ltrim($result['file'], '\\/'); + + $uniqueResultFile = $this->getUniqueFilename($path, $file); + + if ($uniqueResultFile !== $file) { + $ioFile = new Varien_Io_File(); + $ioFile->open(array('path' => $path)); + $ioFile->mv($path . $file, $path . $uniqueResultFile); + } + $this->saveFile($path . $uniqueResultFile); + + return $uniqueResultFile; + } else { + return $result['file']; + } + } + + /** + * Convert full file path to local (as used by model) + * If not - returns just a filename + * + * @param string $filename + * @return string + */ + protected function _removeAbsPathFromFileName($filename) + { + return $this->getMediaRelativePath($filename); + } + + /** + * Return Media base dir + * + * @return string + */ + public function getMediaBaseDir() + { + if (null === $this->_mediaBaseDirectory) { + $this->_mediaBaseDirectory = rtrim(Mage::getBaseDir('media'), '\\/'); + } + return $this->_mediaBaseDirectory; + } +} diff --git a/app/code/core/Mage/Core/Helper/Hint.php b/app/code/core/Mage/Core/Helper/Hint.php new file mode 100644 index 00000000..19afeeb5 --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Hint.php @@ -0,0 +1,80 @@ + + */ +class Mage_Core_Helper_Hint extends Mage_Core_Helper_Abstract +{ + /** + * List of available hints + * + * @var null|array + */ + protected $_availableHints; + + /** + * Retrieve list of available hints as [hint code] => [hint url] + * + * @return array + */ + public function getAvailableHints() + { + if (null === $this->_availableHints) { + $hints = array(); + $config = Mage::getConfig()->getNode('default/hints'); + if ($config) { + foreach ($config->children() as $type => $node) { + if ((string)$node->enabled) { + $hints[$type] = (string)$node->url; + } + } + } + $this->_availableHints = $hints; + } + return $this->_availableHints; + } + + /** + * Get Hint Url by Its Code + * + * @param string $code + * @return null|string + */ + public function getHintByCode($code) + { + $hint = null; + $hints = $this->getAvailableHints(); + if (array_key_exists($code, $hints)) { + $hint = $hints[$code]; + } + return $hint; + } +} diff --git a/app/code/core/Mage/Core/Helper/Http.php b/app/code/core/Mage/Core/Helper/Http.php index cab38c83..2aff4089 100644 --- a/app/code/core/Mage/Core/Helper/Http.php +++ b/app/code/core/Mage/Core/Helper/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Helper/Js.php b/app/code/core/Mage/Core/Helper/Js.php index b464a4eb..96cbec5c 100644 --- a/app/code/core/Mage/Core/Helper/Js.php +++ b/app/code/core/Mage/Core/Helper/Js.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,6 +31,16 @@ */ class Mage_Core_Helper_Js extends Mage_Core_Helper_Abstract { + /** + * Key for cache + */ + const JAVASCRIPT_TRANSLATE_CONFIG_KEY = 'javascript_translate_config'; + + /** + * Translate file name + */ + const JAVASCRIPT_TRANSLATE_CONFIG_FILENAME = 'jstranslator.xml'; + /** * Array of senteces of JS translations * @@ -38,6 +48,13 @@ class Mage_Core_Helper_Js extends Mage_Core_Helper_Abstract */ protected $_translateData = null; + /** + * Translate config + * + * @var Varien_Simplexml_Config + */ + protected $_config = null; + /** * Retrieve JSON of JS sentences translation * @@ -67,7 +84,9 @@ public function getTranslatorScript() */ public function getScript($script) { - return ''; + return ''; } /** @@ -121,76 +140,19 @@ public function getJsSkinUrl($file) */ protected function _getTranslateData() { - if ($this->_translateData ===null) { - $this->_translateData = array( - 'Please select an option.' => $this->__('Please select an option.'), - 'This is a required field.' => $this->__('This is a required field.'), - 'Please enter a valid number in this field.' => $this->__('Please enter a valid number in this field.'), - 'Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.' => - $this->__('Please use numbers only in this field. please avoid spaces or other characters such as dots or commas.'), - 'Please use letters only (a-z) in this field.' => $this->__('Please use letters only (a-z) in this field.'), - 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => - $this->__('Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.'), - 'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.' => - $this->__('Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.'), - 'Please use only letters (a-z) or numbers (0-9) or spaces and # only in this field.' => - $this->__('Please use only letters (a-z) or numbers (0-9) or spaces and # only in this field.'), - 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.' => - $this->__('Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.'), - 'Please enter a valid date.' => $this->__('Please enter a valid date.'), - 'Please enter a valid email address. For example johndoe@domain.com.' => - $this->__('Please enter a valid email address. For example johndoe@domain.com.'), - 'Please enter 6 or more characters.' => $this->__('Please enter 6 or more characters.'), - 'Please make sure your passwords match.' => $this->__('Please make sure your passwords match.'), - 'Please enter a valid URL. http:// is required' => $this->__('Please enter a valid URL. http:// is required'), - 'Please enter a valid URL. For example http://www.example.com or www.example.com' => - $this->__('Please enter a valid URL. For example http://www.example.com or www.example.com'), - 'Please enter a valid social security number. For example 123-45-6789.' => - $this->__('Please enter a valid social security number. For example 123-45-6789.'), - 'Please enter a valid zip code. For example 90602 or 90602-1234.' => - $this->__('Please enter a valid zip code. For example 90602 or 90602-1234.'), - 'Please enter a valid zip code.' => $this->__('Please enter a valid zip code.'), - 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.' => - $this->__('Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.'), - 'Please enter a valid $ amount. For example $100.00.' => - $this->__('Please enter a valid $ amount. For example $100.00.'), - 'Please select one of the above options.' => $this->__('Please select one of the above options.'), - 'Please select one of the options.' => $this->__('Please select one of the options.'), - 'Please enter a valid number in this field.' => $this->__('Please enter a valid number in this field.'), - 'Please select State/Province.' => $this->__('Please select State/Province.'), - 'Please enter valid password.' => $this->__('Please enter valid password.'), - 'Please enter 6 or more characters. Leading or trailing spaces will be ignored.' => - $this->__('Please enter 6 or more characters. Leading or trailing spaces will be ignored.'), - 'Please use letters only (a-z or A-Z) in this field.' => $this->__('Please use letters only (a-z or A-Z) in this field.'), - 'Please enter a number greater than 0 in this field.' => - $this->__('Please enter a number greater than 0 in this field.'), - 'Please enter a valid credit card number.' => $this->__('Please enter a valid credit card number.'), - 'Please wait, loading...' => $this->__('Please wait, loading...'), - 'Please choose to register or to checkout as a guest' => $this->__('Please choose to register or to checkout as a guest'), - 'Error: Passwords do not match' => $this->__('Error: Passwords do not match'), - 'Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.' => - $this->__('Your order cannot be completed at this time as there is no shipping methods available for it. Please make necessary changes in your shipping address.'), - 'Please specify shipping method.' => $this->__('Please specify shipping method.'), - 'Your order cannot be completed at this time as there is no payment methods available for it.' => - $this->__('Your order cannot be completed at this time as there is no payment methods available for it.'), - 'Please specify payment method.' => $this->__('Please specify payment method.'), - 'Credit card number doesn\'t match credit card type' => $this->__('Credit card number does not match credit card type'), - 'Card type does not match credit card number' => $this->__('Card type does not match credit card number'), - 'Please enter a valid credit card verification number.' => $this->__('Please enter a valid credit card verification number.'), - 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.' => - $this->__('Please use only letters (a-z or A-Z), numbers (0-9) or underscores (_) in this field, first character must be a letter.'), - 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%' => $this->__('Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%'), - 'Maximum length exceeded.' => $this->__('Maximum length exceeded.'), - - - //Mage_Rule + if ($this->_translateData === null) { + $this->_translateData = array(); + $messages = $this->_getXmlConfig()->getXpath('*/message'); + if (!empty($messages)) { + foreach ($messages as $message) { + $messageText = (string)$message; + $module = $message->getParent()->getAttribute("module"); + $this->_translateData[$messageText] = Mage::helper(empty($module) ? 'core' : $module + )->__($messageText); + } + } - 'Your session has been expired, you will be relogged in now.' => $this->__('Your session has been expired, you will be relogged in now.'), - 'Incorrect credit card expiration date' => $this->__('Incorrect credit card expiration date'), - // Date - 'This date is a required value.' => $this->__('This date is a required value.'), - ); - foreach ($this->_translateData as $key=>$value) { + foreach ($this->_translateData as $key => $value) { if ($key == $value) { unset($this->_translateData[$key]); } @@ -199,4 +161,30 @@ protected function _getTranslateData() return $this->_translateData; } + /** + * Load config from files and try to cache it + * + * @return Varien_Simplexml_Config + */ + protected function _getXmlConfig() + { + if (is_null($this->_config)) { + $canUsaCache = Mage::app()->useCache('config'); + $cachedXml = Mage::app()->loadCache(self::JAVASCRIPT_TRANSLATE_CONFIG_KEY); + if ($canUsaCache && $cachedXml) { + $xmlConfig = new Varien_Simplexml_Config($cachedXml); + } else { + $xmlConfig = new Varien_Simplexml_Config(); + $xmlConfig->loadString(''); + Mage::getConfig()->loadModulesConfiguration(self::JAVASCRIPT_TRANSLATE_CONFIG_FILENAME, $xmlConfig); + + if ($canUsaCache) { + Mage::app()->saveCache($xmlConfig->getXmlString(), self::JAVASCRIPT_TRANSLATE_CONFIG_KEY, + array(Mage_Core_Model_Config::CACHE_TAG)); + } + } + $this->_config = $xmlConfig; + } + return $this->_config; + } } diff --git a/app/code/core/Mage/Core/Helper/String.php b/app/code/core/Mage/Core/Helper/String.php index e3180a2f..12991d90 100644 --- a/app/code/core/Mage/Core/Helper/String.php +++ b/app/code/core/Mage/Core/Helper/String.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -115,7 +115,8 @@ public function splitInjection($str, $length = 50, $needle = '-', $insert = ' ') if ($this->strlen($part) >= $length) { $lastDelimetr = $this->strpos($this->strrev($part), $needle); $tmpNewStr = ''; - $tmpNewStr = $this->substr($this->strrev($part), 0, $lastDelimetr) . $insert . $this->substr($this->strrev($part), $lastDelimetr); + $tmpNewStr = $this->substr($this->strrev($part), 0, $lastDelimetr) + . $insert . $this->substr($this->strrev($part), $lastDelimetr); $newStr .= $this->strrev($tmpNewStr); } else { $newStr .= $part; @@ -264,7 +265,8 @@ function splitWords($str, $uniqueOnly = false, $maxWordLength = 0, $wordSeparato */ public function cleanString($string) { - return '"libiconv"' == ICONV_IMPL ? iconv(self::ICONV_CHARSET, self::ICONV_CHARSET . '//IGNORE', $string) : $string; + return '"libiconv"' == ICONV_IMPL ? + iconv(self::ICONV_CHARSET, self::ICONV_CHARSET . '//IGNORE', $string) : $string; } /** @@ -279,4 +281,26 @@ public function strpos($haystack, $needle, $offset = null) { return iconv_strpos($haystack, $needle, $offset, self::ICONV_CHARSET); } + + /** + * Sorts array with multibyte string keys + * + * @param array $sort + * @return array + */ + public function ksortMultibyte(array &$sort) + { + if (empty($sort)) { + return false; + } + $oldLocale = setlocale(LC_COLLATE, "0"); + $localeCode = Mage::app()->getLocale()->getLocaleCode(); + // use fallback locale if $localeCode is not available + setlocale(LC_COLLATE, $localeCode . '.UTF8', 'C.UTF-8', 'en_US.utf8'); + ksort($sort, SORT_LOCALE_STRING); + setlocale(LC_COLLATE, $oldLocale); + + return $sort; + } + } diff --git a/app/code/core/Mage/Core/Helper/Translate.php b/app/code/core/Mage/Core/Helper/Translate.php new file mode 100644 index 00000000..1c5b9b7b --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Translate.php @@ -0,0 +1,54 @@ + + */ +class Mage_Core_Helper_Translate extends Mage_Core_Helper_Abstract +{ + /** + * Save translation data to database for specific area + * + * @param array $translate + * @param string $area + * @param string $returnType + * @return string + */ + public function apply($translate, $area, $returnType = 'json') + { + try { + if ($area) { + Mage::getDesign()->setArea($area); + } + Mage::getModel('core/translate_inline')->processAjaxPost($translate); + return $returnType == 'json' ? "{success:true}" : true; + } catch (Exception $e) { + return $returnType == 'json' ? "{error:true,message:'" . $e->getMessage() . "'}" : false; + } + } +} diff --git a/app/code/core/Mage/Core/Helper/Url.php b/app/code/core/Mage/Core/Helper/Url.php index f4f2f2bb..2fd8608a 100644 --- a/app/code/core/Mage/Core/Helper/Url.php +++ b/app/code/core/Mage/Core/Helper/Url.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -41,7 +41,18 @@ class Mage_Core_Helper_Url extends Mage_Core_Helper_Abstract */ public function getCurrentUrl() { - return $this->_getUrl('*/*/*', array('_current' => true, '_use_rewrite' => true)); + $request = Mage::app()->getRequest(); + $port = $request->getServer('SERVER_PORT'); + if ($port) { + $defaultPorts = array( + Mage_Core_Controller_Request_Http::DEFAULT_HTTP_PORT, + Mage_Core_Controller_Request_Http::DEFAULT_HTTPS_PORT + ); + $port = (in_array($port, $defaultPorts)) ? '' : ':' . $port; + } + $url = $request->getScheme() . '://' . $request->getHttpHost() . $port . $request->getServer('REQUEST_URI'); + return $url; +// return $this->_getUrl('*/*/*', array('_current' => true, '_use_rewrite' => true)); } /** @@ -81,4 +92,55 @@ protected function _prepareString($string) return $string; } + /** + * Add request parameter into url + * + * @param $url string + * @param $param array( 'key' => value ) + * @return string + */ + public function addRequestParam($url, $param) + { + $startDelimiter = (false === strpos($url,'?'))? '?' : '&'; + + $arrQueryParams = array(); + foreach($param as $key=>$value) { + if (is_numeric($key) || is_object($value)) { + continue; + } + + if (is_array($value)) { + // $key[]=$value1&$key[]=$value2 ... + $arrQueryParams[] = $key . '[]=' . implode('&' . $key . '[]=', $value); + } elseif (is_null($value)) { + $arrQueryParams[] = $key; + } else { + $arrQueryParams[] = $key . '=' . $value; + } + } + $url .= $startDelimiter . implode('&', $arrQueryParams); + + return $url; + } + + /** + * Remove request parameter from url + * + * @param string $url + * @param string $paramKey + * @return string + */ + public function removeRequestParam($url, $paramKey, $caseSensitive = false) + { + $regExpression = '/\\?[^#]*?(' . preg_quote($paramKey, '/') . '\\=[^#&]*&?)/' . ($caseSensitive ? '' : 'i'); + while (preg_match($regExpression, $url, $mathes) != 0) { + $paramString = $mathes[1]; + if (preg_match('/&$/', $paramString) == 0) { + $url = preg_replace('/(&|\\?)?' . preg_quote($paramString, '/') . '/', '', $url); + } else { + $url = str_replace($paramString, '', $url); + } + } + return $url; + } } diff --git a/app/code/core/Mage/Core/Helper/Url/Rewrite.php b/app/code/core/Mage/Core/Helper/Url/Rewrite.php new file mode 100644 index 00000000..4974cca3 --- /dev/null +++ b/app/code/core/Mage/Core/Helper/Url/Rewrite.php @@ -0,0 +1,97 @@ + + */ +class Mage_Core_Helper_Url_Rewrite extends Mage_Core_Helper_Abstract +{ + /** + * Validation error constants + */ + const VERR_MANYSLASHES = 1; // Too many slashes in a row of request path, e.g. '///foo//' + const VERR_ANCHOR = 2; // Anchor is not supported in request path, e.g. 'foo#bar' + + /** + * Core func to validate request path + * If something is wrong with a path it throws localized error message and error code, + * that can be checked to by wrapper func to alternate error message + * + * @return bool + */ + protected function _validateRequestPath($requestPath) + { + if (strpos($requestPath, '//') !== false) { + throw new Exception($this->__('Two and more slashes together are not permitted in request path'), self::VERR_MANYSLASHES); + } + if (strpos($requestPath, '#') !== false) { + throw new Exception($this->__('Anchor symbol (#) is not supported in request path'), self::VERR_ANCHOR); + } + return true; + } + + /** + * Validates request path + * Either returns TRUE (success) or throws error (validation failed) + * + * @return bool + */ + public function validateRequestPath($requestPath) + { + try { + $this->_validateRequestPath($requestPath); + } catch (Exception $e) { + Mage::throwException($e->getMessage()); + } + return true; + } + + /** + * Validates suffix for url rewrites to inform user about errors in it + * Either returns TRUE (success) or throws error (validation failed) + * + * @return bool + */ + public function validateSuffix($suffix) + { + try { + $this->_validateRequestPath($suffix); // Suffix itself must be a valid request path + } catch (Exception $e) { + // Make message saying about suffix, not request path + switch ($e->getCode()) { + case self::VERR_MANYSLASHES: + Mage::throwException($this->__('Two and more slashes together are not permitted in url rewrite suffix')); + case self::VERR_ANCHOR: + Mage::throwException($this->__('Anchor symbol (#) is not supported in url rewrite suffix')); + } + } + return true; + } +} diff --git a/app/code/core/Mage/Core/Model/Abstract.php b/app/code/core/Mage/Core/Model/Abstract.php index 7b62e6d8..d38f7064 100644 --- a/app/code/core/Mage/Core/Model/Abstract.php +++ b/app/code/core/Mage/Core/Model/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -282,6 +282,18 @@ public function afterLoad() return $this; } + /** + * Check whether model has changed data. + * Can be overloaded in child classes to perform advanced check whether model needs to be saved + * e.g. usign resouceModel->hasDataChanged() or any other technique + * + * @return boolean + */ + protected function _hasModelChanged() + { + return $this->hasDataChanges(); + } + /** * Save object data * @@ -295,7 +307,7 @@ public function save() if ($this->isDeleted()) { return $this->delete(); } - if (!$this->hasDataChanges()) { + if (!$this->_hasModelChanged()) { return $this; } $this->_getResource()->beginTransaction(); @@ -544,4 +556,38 @@ public function getEntityId() { return $this->_getData('entity_id'); } + + /** + * Clearing object for correct deleting by garbage collector + * + * @return Mage_Core_Model_Abstract + */ + final public function clearInstance() + { + $this->_clearReferences(); + Mage::dispatchEvent($this->_eventPrefix.'_clear', $this->_getEventData()); + $this->_clearData(); + return $this; + } + + /** + * Clearing cyclic references + * + * @return Mage_Core_Model_Abstract + */ + protected function _clearReferences() + { + return $this; + } + + /** + * Clearing object's data + * + * @return Mage_Core_Model_Abstract + */ + protected function _clearData() + { + return $this; + } + } diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index 9907a4ce..7703208e 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,6 +38,13 @@ class Mage_Core_Model_App const XML_PATH_INSTALL_DATE = 'global/install/date'; + const XML_PATH_SKIP_PROCESS_MODULES_UPDATES = 'global/skip_process_modules_updates'; + + /** + * if this node set to true, we will ignore Developer Mode for applying updates + */ + const XML_PATH_IGNORE_DEV_MODE = 'global/skip_process_modules_updates_ignore_dev_mode'; + const DEFAULT_ERROR_HANDLER = 'mageCoreErrorHandler'; const DISTRO_LOCALE_CODE = 'en_US'; @@ -226,6 +233,11 @@ class Mage_Core_Model_App */ protected $_useSessionVar = false; + /** + * Cache locked flag + * + * @var null|bool + */ protected $_isCacheLocked = null; /** @@ -238,12 +250,12 @@ public function __construct() /** * Initialize application without request processing * - * @param string|array $code - * @param string $type - * @param string $etcDir + * @param string|array $code + * @param string $type + * @param string|array $options * @return Mage_Core_Model_App */ - public function init($code, $type=null, $options=array()) + public function init($code, $type = null, $options = array()) { $this->_initEnvironment(); if (is_string($options)) { @@ -252,6 +264,7 @@ public function init($code, $type=null, $options=array()) Varien_Profiler::start('mage::app::init::config'); $this->_config = Mage::getConfig(); + $this->_config->setOptions($options); $this->_initBaseConfig(); $this->_initCache(); $this->_config->init($options); @@ -265,29 +278,64 @@ public function init($code, $type=null, $options=array()) } /** - * Run application. Run process responsible for request processing and sending response. - * List of suppported parametes: - * scope_code - code of default scope (website/store_group/store code) - * scope_type - type of default scope (website/group/store) - * options - configuration options - * - * @param array $params application run parameters + * Common logic for all run types * + * @param string|array $options * @return Mage_Core_Model_App */ - public function run($params) + public function baseInit($options) { - $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : ''; - $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store'; - $options = isset($params['options']) ? $params['options'] : array(); - $this->_initEnvironment(); $this->_config = Mage::getConfig(); $this->_config->setOptions($options); $this->_initBaseConfig(); - $this->_initCache(); + $cacheInitOptions = is_array($options) && array_key_exists('cache', $options) ? $options['cache'] : array(); + $this->_initCache($cacheInitOptions); + + return $this; + } + + /** + * Run light version of application with specified modules support + * + * @see Mage_Core_Model_App->run() + * + * @param string|array $scopeCode + * @param string $scopeType + * @param string|array $options + * @param string|array $modules + * @return Mage_Core_Model_App + */ + public function initSpecified($scopeCode, $scopeType = null, $options = array(), $modules = array()) + { + $this->baseInit($options); + + if (!empty($modules)) { + $this->_config->addAllowedModules($modules); + } + $this->_initModules(); + $this->_initCurrentStore($scopeCode, $scopeType); + + return $this; + } + + /** + * Run application. Run process responsible for request processing and sending response. + * List of supported parameters: + * scope_code - code of default scope (website/store_group/store code) + * scope_type - type of default scope (website/group/store) + * options - configuration options + * + * @param array $params application run parameters + * @return Mage_Core_Model_App + */ + public function run($params) + { + $options = isset($params['options']) ? $params['options'] : array(); + $this->baseInit($options); + Mage::register('application_params', $params); if ($this->_cache->processRequest()) { $this->getResponse()->sendResponse(); @@ -296,6 +344,8 @@ public function run($params) $this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS); if ($this->_config->isLocalConfigLoaded()) { + $scopeCode = isset($params['scope_code']) ? $params['scope_code'] : ''; + $scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store'; $this->_initCurrentStore($scopeCode, $scopeType); $this->_initRequest(); Mage_Core_Model_Resource_Setup::applyAllDataUpdates(); @@ -335,17 +385,21 @@ protected function _initBaseConfig() /** * Initialize application cache instance * + * @param array $cacheInitOptions * @return Mage_Core_Model_App */ - protected function _initCache() + protected function _initCache(array $cacheInitOptions = array()) { + $this->_isCacheLocked = true; $options = $this->_config->getNode('global/cache'); if ($options) { $options = $options->asArray(); } else { $options = array(); } + $options = array_merge($options, $cacheInitOptions); $this->_cache = Mage::getModel('core/cache', $options); + $this->_isCacheLocked = false; return $this; } @@ -358,7 +412,7 @@ protected function _initModules() { if (!$this->_config->loadModulesCache()) { $this->_config->loadModules(); - if ($this->_config->isLocalConfigLoaded()) { + if ($this->_config->isLocalConfigLoaded() && !$this->_shouldSkipProcessModulesUpdates()) { Varien_Profiler::start('mage::app::init::apply_db_schema_updates'); Mage_Core_Model_Resource_Setup::applyAllUpdates(); Varien_Profiler::stop('mage::app::init::apply_db_schema_updates'); @@ -369,6 +423,25 @@ protected function _initModules() return $this; } + /** + * Check whether modules updates processing should be skipped + * + * @return bool + */ + protected function _shouldSkipProcessModulesUpdates() + { + if (!Mage::isInstalled()) { + return false; + } + + $ignoreDevelopmentMode = (bool)(string)$this->_config->getNode(self::XML_PATH_IGNORE_DEV_MODE); + if (Mage::getIsDeveloperMode() && !$ignoreDevelopmentMode) { + return false; + } + + return (bool)(string)$this->_config->getNode(self::XML_PATH_SKIP_PROCESS_MODULES_UPDATES); + } + /** * Init request object * @@ -415,6 +488,8 @@ protected function _initCurrentStore($scopeCode, $scopeType) $this->_checkCookieStore($scopeType); $this->_checkGetStore($scopeType); } + $this->_useSessionInUrl = $this->getStore()->getConfig( + Mage_Core_Model_Session_Abstract::XML_PATH_USE_FRONTEND_SID); return $this; } @@ -474,9 +549,9 @@ protected function _checkGetStore($type) if ($this->_currentStore == $store) { $store = $this->getStore($store); if ($store->getWebsite()->getDefaultStore()->getId() == $store->getId()) { - $this->getCookie()->delete('store'); + $this->getCookie()->delete(Mage_Core_Model_Store::COOKIE_NAME); } else { - $this->getCookie()->set('store', $this->_currentStore, true); + $this->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $this->_currentStore, true); } } return $this; @@ -494,7 +569,7 @@ protected function _checkCookieStore($type) return $this; } - $store = $this->getCookie()->get('store'); + $store = $this->getCookie()->get(Mage_Core_Model_Store::COOKIE_NAME); if ($store && isset($this->_stores[$store]) && $this->_stores[$store]->getId() && $this->_stores[$store]->getIsActive()) { @@ -529,12 +604,17 @@ protected function _initStores() $this->_website = null; $this->_websites = array(); + /** @var $websiteCollection Mage_Core_Model_Website */ $websiteCollection = Mage::getModel('core/website')->getCollection() - ->initCache($this->getCache(), 'app', array(Mage_Core_Model_Website::CACHE_TAG)) - ->setLoadDefault(true); + ->initCache($this->getCache(), 'app', array(Mage_Core_Model_Website::CACHE_TAG)) + ->setLoadDefault(true); + + /** @var $websiteCollection Mage_Core_Model_Store_Group */ $groupCollection = Mage::getModel('core/store_group')->getCollection() - ->initCache($this->getCache(), 'app', array(Mage_Core_Model_Store_Group::CACHE_TAG)) - ->setLoadDefault(true); + ->initCache($this->getCache(), 'app', array(Mage_Core_Model_Store_Group::CACHE_TAG)) + ->setLoadDefault(true); + + /** @var $websiteCollection Mage_Core_Model_Store */ $storeCollection = Mage::getModel('core/store')->getCollection() ->initCache($this->getCache(), 'app', array(Mage_Core_Model_Store::CACHE_TAG)) ->setLoadDefault(true); @@ -549,7 +629,7 @@ protected function _initStores() $groupStores = array(); foreach ($storeCollection as $store) { - /* @var $store Mage_Core_Model_Store */ + /** @var $store Mage_Core_Model_Store */ $store->initConfigCache(); $store->setWebsite($websiteCollection->getItemById($store->getWebsiteId())); $store->setGroup($groupCollection->getItemById($store->getGroupId())); @@ -611,7 +691,7 @@ public function isSingleStoreMode() } /** - * Retrive store code or null by store group + * Retrieve store code or null by store group * * @param int $group * @return string|null @@ -628,7 +708,7 @@ protected function _getStoreByGroup($group) } /** - * Retrive store code or null by website + * Retrieve store code or null by website * * @param int|string $website * @return string|null @@ -725,9 +805,11 @@ public function getArea($code) /** * Retrieve application store object * + * @param null|string|bool|int|Mage_Core_Model_Store $id * @return Mage_Core_Model_Store + * @throws Mage_Core_Model_Store_Exception */ - public function getStore($id=null) + public function getStore($id = null) { if (!Mage::isInstalled() || $this->getUpdateMode()) { return $this->_getDefaultStore(); @@ -737,13 +819,13 @@ public function getStore($id=null) return $this->_store; } - if (is_null($id) || ''===$id || $id === true) { + if (!isset($id) || ''===$id || $id === true) { $id = $this->_currentStore; } if ($id instanceof Mage_Core_Model_Store) { return $id; } - if (is_null($id)) { + if (!isset($id)) { $this->throwStoreException(); } @@ -987,7 +1069,8 @@ public function getHelper($name) public function getBaseCurrencyCode() { //return Mage::getStoreConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE, 0); - return (string) Mage::app()->getConfig()->getNode('default/'.Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE); + return (string) Mage::app()->getConfig() + ->getNode('default/' . Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE); } /** @@ -1138,6 +1221,18 @@ public function getRequest() return $this->_request; } + /** + * Request setter + * + * @param Mage_Core_Controller_Request_Http $request + * @return Mage_Core_Model_App + */ + public function setRequest(Mage_Core_Controller_Request_Http $request) + { + $this->_request = $request; + return $this; + } + /** * Retrieve response object * @@ -1153,6 +1248,18 @@ public function getResponse() return $this->_response; } + /** + * Response setter + * + * @param Mage_Core_Controller_Response_Http $response + * @return Mage_Core_Model_App + */ + public function setResponse(Mage_Core_Controller_Response_Http $response) + { + $this->_response = $response; + return $this; + } + public function addEventArea($area) { if (!isset($this->_events[$area])) { @@ -1196,7 +1303,8 @@ public function dispatchEvent($eventName, $args) switch ($obs['type']) { case 'disabled': break; - case 'object': case 'model': + case 'object': + case 'model': $method = $obs['method']; $observer->addData($args); $object = Mage::getModel($obs['model']); @@ -1216,11 +1324,13 @@ public function dispatchEvent($eventName, $args) } /** - * Added not existin observers methods calls protection + * Performs non-existent observer method calls protection * * @param object $object * @param string $method * @param Varien_Event_Observer $observer + * @return Mage_Core_Model_App + * @throws Mage_Core_Exception */ protected function _callObserverMethod($object, $method, $observer) { @@ -1417,4 +1527,38 @@ public function prepareCacheId($id) $id = preg_replace('/([^a-zA-Z0-9_]{1,1})/', '_', $id); return $id; } + + /** + * Get is cache locked + * + * @return bool + */ + public function getIsCacheLocked() + { + return (bool)$this->_isCacheLocked; + } + + /** + * Unset website by id from app cache + * + * @param null|bool|int|string|Mage_Core_Model_Website $id + * @return void + */ + public function clearWebsiteCache($id = null) + { + if (is_null($id)) { + $id = $this->getStore()->getWebsiteId(); + } elseif ($id instanceof Mage_Core_Model_Website) { + $id = $id->getId(); + } elseif ($id === true) { + $id = $this->_website->getId(); + } + + if (!empty($this->_websites[$id])) { + $website = $this->_websites[$id]; + + unset($this->_websites[$website->getWebsiteId()]); + unset($this->_websites[$website->getCode()]); + } + } } diff --git a/app/code/core/Mage/Core/Model/App/Area.php b/app/code/core/Mage/Core/Model/App/Area.php index 98748de8..1545fdeb 100644 --- a/app/code/core/Mage/Core/Model/App/Area.php +++ b/app/code/core/Mage/Core/Model/App/Area.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,10 +34,11 @@ class Mage_Core_Model_App_Area const AREA_GLOBAL = 'global'; const AREA_FRONTEND = 'frontend'; const AREA_ADMIN = 'admin'; + const AREA_ADMINHTML = 'adminhtml'; const PART_CONFIG = 'config'; const PART_EVENTS = 'events'; - const PART_TRANSLATE= 'translate'; + const PART_TRANSLATE = 'translate'; const PART_DESIGN = 'design'; /** diff --git a/app/code/core/Mage/Core/Model/App/Emulation.php b/app/code/core/Mage/Core/Model/App/Emulation.php new file mode 100644 index 00000000..ac1adda9 --- /dev/null +++ b/app/code/core/Mage/Core/Model/App/Emulation.php @@ -0,0 +1,197 @@ + + */ +class Mage_Core_Model_App_Emulation extends Varien_Object +{ + /** + * Start enviromment emulation of the specified store + * + * Function returns information about initial store environment and emulates environment of another store + * + * @param integer $storeId + * @param string $area + * @param boolean $emulateSroreInlineTranslation emulate inline translation of the specified store or just disable it + * + * @return Varien_Object information about environment of the initial store + */ + public function startEnvironmentEmulation($storeId, $area = Mage_Core_Model_App_Area::AREA_FRONTEND, $emulateSroreInlineTranslation = false) + { + if (is_null($area)) { + $area = Mage_Core_Model_App_Area::AREA_FRONTEND; + } + if ($emulateSroreInlineTranslation) { + $initialTranslateInline = $this->_emulateInlineTranslation($storeId, $area); + } else { + $initialTranslateInline = $this->_emulateInlineTranslation(); + } + $initialDesign = $this->_emulateDesign($storeId, $area); + // Current store needs to be changed right before locale change and after design change + Mage::app()->setCurrentStore($storeId); + $initialLocaleCode = $this->_emulateLocale($storeId, $area); + + $initialEnvironmentInfo = new Varien_Object(); + $initialEnvironmentInfo->setInitialTranslateInline($initialTranslateInline) + ->setInitialDesign($initialDesign) + ->setInitialLocaleCode($initialLocaleCode); + + return $initialEnvironmentInfo; + } + + /** + * Stop enviromment emulation + * + * Function restores initial store environment + * + * @param Varien_Object $initialEnvironmentInfo information about environment of the initial store + * + * @return Mage_Core_Model_App_Emulation + */ + public function stopEnvironmentEmulation(Varien_Object $initialEnvironmentInfo) + { + $this->_restoreInitialInlineTranslation($initialEnvironmentInfo->getInitialTranslateInline()); + $initialDesign = $initialEnvironmentInfo->getInitialDesign(); + $this->_restoreInitialDesign($initialDesign); + // Current store needs to be changed right before locale change and after design change + Mage::app()->setCurrentStore($initialDesign['store']); + $this->_restoreInitialLocale($initialEnvironmentInfo->getInitialLocaleCode(), $initialDesign['area']); + return $this; + } + + /** + * Emulate inline translation of the specified store + * + * Function disables inline translation if $storeId is null + * + * @param integer|null $storeId + * @param string $area + * + * @return boolean initial inline translation state + */ + protected function _emulateInlineTranslation($storeId = null, $area = Mage_Core_Model_App_Area::AREA_FRONTEND) + { + if (is_null($storeId)) { + $newTranslateInline = false; + } else { + if ($area == Mage_Core_Model_App_Area::AREA_ADMINHTML) { + $newTranslateInline = Mage::getStoreConfigFlag('dev/translate_inline/active_admin', $storeId); + } else { + $newTranslateInline = Mage::getStoreConfigFlag('dev/translate_inline/active', $storeId); + } + } + $translateModel = Mage::getSingleton('core/translate'); + $initialTranslateInline = $translateModel->getTranslateInline(); + $translateModel->setTranslateInline($newTranslateInline); + return $initialTranslateInline; + } + + /** + * Apply design of the specified store + * + * @param integer $storeId + * @param string $area + * + * @return array initial design parameters(package, store, area) + */ + protected function _emulateDesign($storeId, $area = Mage_Core_Model_App_Area::AREA_FRONTEND) + { + $initialDesign = Mage::getDesign()->setAllGetOld(array( + 'package' => Mage::getStoreConfig('design/package/name', $storeId), + 'store' => $storeId, + 'area' => $area + )); + Mage::getDesign()->setTheme(''); + Mage::getDesign()->setPackageName(''); + return $initialDesign; + } + + /** + * Apply locale of the specified store + * + * @param integer $storeId + * @param string $area + * + * @return string initial locale code + */ + protected function _emulateLocale($storeId, $area = Mage_Core_Model_App_Area::AREA_FRONTEND) + { + $initialLocaleCode = Mage::app()->getLocale()->getLocaleCode(); + $newLocaleCode = Mage::getStoreConfig(Mage_Core_Model_Locale::XML_PATH_DEFAULT_LOCALE, $storeId); + Mage::app()->getLocale()->setLocaleCode($newLocaleCode); + Mage::getSingleton('core/translate')->setLocale($newLocaleCode)->init($area, true); + return $initialLocaleCode; + } + + /** + * Restore initial inline translation state + * + * @param boolean $initialTranslateInline + * + * @return Mage_Core_Model_App_Emulation + */ + protected function _restoreInitialInlineTranslation($initialTranslateInline) + { + $translateModel = Mage::getSingleton('core/translate'); + $translateModel->setTranslateInline($initialTranslateInline); + return $this; + } + + /** + * Restore design of the initial store + * + * @param array $initialDesign + * + * @return Mage_Core_Model_App_Emulation + */ + protected function _restoreInitialDesign(array $initialDesign) + { + Mage::getDesign()->setAllGetOld($initialDesign); + Mage::getDesign()->setTheme(''); + Mage::getDesign()->setPackageName(''); + return $this; + } + + /** + * Restore locale of the initial store + * + * @param string $initialLocaleCode + * @param string $initialArea + * + * @return Mage_Core_Model_App_Emulation + */ + protected function _restoreInitialLocale($initialLocaleCode, $initialArea = Mage_Core_Model_App_Area::AREA_ADMINHTML) + { + Mage::app()->getLocale()->setLocaleCode($initialLocaleCode); + Mage::getSingleton('core/translate')->setLocale($initialLocaleCode)->init($initialArea, true); + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Cache.php b/app/code/core/Mage/Core/Model/Cache.php index f581ae7d..acecd2cc 100644 --- a/app/code/core/Mage/Core/Model/Cache.php +++ b/app/code/core/Mage/Core/Model/Cache.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -83,6 +83,13 @@ class Mage_Core_Model_Cache */ protected $_requestProcessors = array(); + /** + * Disallow cache saving + * + * @var bool + */ + protected $_disallowSave = false; + /** * List of allowed cache options * @@ -119,6 +126,10 @@ public function __construct(array $options = array()) if (isset($options['request_processors'])) { $this->_requestProcessors = $options['request_processors']; } + + if (isset($options['disallow_save'])) { + $this->_disallowSave = $options['disallow_save']; + } } /** @@ -145,7 +156,13 @@ protected function _getBackendOptions(array $cacheOptions) } break; case 'memcached': - if (extension_loaded('memcache')) { + if (extension_loaded('memcached')) { + if (isset($cacheOptions['memcached'])) { + $options = $cacheOptions['memcached']; + } + $enable2levels = true; + $backendType = 'Libmemcached'; + } elseif (extension_loaded('memcache')) { if (isset($cacheOptions['memcached'])) { $options = $cacheOptions['memcached']; } @@ -236,6 +253,11 @@ protected function _getTwoLevelsBackendOptions($fastOptions, $cacheOptions) $options['slow_backend_custom_naming'] = true; $options['slow_backend_autoload'] = true; + if (isset($cacheOptions['auto_refresh_fast_cache'])) { + $options['auto_refresh_fast_cache'] = (bool)$cacheOptions['auto_refresh_fast_cache']; + } else { + $options['auto_refresh_fast_cache'] = false; + } if (isset($cacheOptions['slow_backend'])) { $options['slow_backend'] = $cacheOptions['slow_backend']; } else { @@ -249,6 +271,11 @@ protected function _getTwoLevelsBackendOptions($fastOptions, $cacheOptions) if ($options['slow_backend'] == 'database') { $options['slow_backend'] = 'Varien_Cache_Backend_Database'; $options['slow_backend_options'] = $this->getDbAdapterOptions(); + if (isset($cacheOptions['slow_backend_store_data'])) { + $options['slow_backend_options']['store_data'] = (bool)$cacheOptions['slow_backend_store_data']; + } else { + $options['slow_backend_options']['store_data'] = false; + } } $backend = array( @@ -271,7 +298,8 @@ protected function _getFrontendOptions(array $cacheOptions) $options['caching'] = true; } if (!array_key_exists('lifetime', $options)) { - $options['lifetime'] = isset($cacheOptions['lifetime']) ? $cacheOptions['lifetime'] : self::DEFAULT_LIFETIME; + $options['lifetime'] = isset($cacheOptions['lifetime']) ? $cacheOptions['lifetime'] + : self::DEFAULT_LIFETIME; } if (!array_key_exists('automatic_cleaning_factor', $options)) { $options['automatic_cleaning_factor'] = 0; @@ -346,6 +374,9 @@ public function save($data, $id, $tags=array(), $lifeTime=null) if (!in_array(Mage_Core_Model_Config::CACHE_TAG, $tags)) { $tags[] = Mage_Core_Model_App::CACHE_TAG; } + if ($this->_disallowSave) { + return true; + } return $this->_frontend->save((string)$data, $this->_id($id), $this->_tags($tags), $lifeTime); } @@ -431,6 +462,13 @@ protected function _initOptions() } else { $this->_allowedCacheOptions = unserialize($options); } + + if (Mage::getConfig()->getOptions()->getData('global_ban_use_cache')) { + foreach ($this->_allowedCacheOptions as $key => $val) { + $this->_allowedCacheOptions[$key] = false; + } + } + return $this; } @@ -540,7 +578,7 @@ protected function _getInvalidatedTypes() } /** - * Save invalicated cache types + * Save invalidated cache types * * @param array $types * @return Mage_Core_Model_Cache @@ -575,7 +613,7 @@ public function getInvalidatedTypes() * Mark specific cache type(s) as invalidated * * @param string|array $typeCode - * @return + * @return Mage_Core_Model_Cache */ public function invalidateType($typeCode) { @@ -593,7 +631,8 @@ public function invalidateType($typeCode) /** * Clean cached data for specific cache type * - * @param $typeCode + * @param string $typeCode + * @return Mage_Core_Model_Cache */ public function cleanType($typeCode) { diff --git a/app/code/core/Mage/Core/Model/Calculator.php b/app/code/core/Mage/Core/Model/Calculator.php new file mode 100644 index 00000000..cd5ba6f8 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Calculator.php @@ -0,0 +1,87 @@ + + */ +class Mage_Core_Model_Calculator +{ + /** + * Delta collected during rounding steps + * + * @var float + */ + protected $_delta = 0.0; + + /** + * Store instance + * + * @var Mage_Core_Model_Store|null + */ + protected $_store = null; + + /** + * Initialize calculator + * + * @param Mage_Core_Model_Store|int $store + */ + public function __construct($store) + { + if (!($store instanceof Mage_Core_Model_Store)) { + $store = Mage::app()->getStore($store); + } + $this->_store = $store; + } + + /** + * Round price considering delta + * + * @param float $price + * @param bool $negative Indicates if we perform addition (true) or subtraction (false) of rounded value + * @return float + */ + public function deltaRound($price, $negative = false) + { + $roundedPrice = $price; + if ($roundedPrice) { + if ($negative) { + $this->_delta = -$this->_delta; + } + $price += $this->_delta; + $roundedPrice = $this->_store->roundPrice($price); + $this->_delta = $price - $roundedPrice; + if ($negative) { + $this->_delta = -$this->_delta; + } + } + return $roundedPrice; + } +} diff --git a/app/code/core/Mage/Core/Model/Config.php b/app/code/core/Mage/Core/Model/Config.php index e1ad6d12..45ac03c4 100644 --- a/app/code/core/Mage/Core/Model/Config.php +++ b/app/code/core/Mage/Core/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -181,6 +181,14 @@ class Mage_Core_Model_Config extends Mage_Core_Model_Config_Base */ private $_moduleNamespaces = null; + /** + * Modules allowed to load + * If empty - all modules are allowed + * + * @var array + */ + protected $_allowedModules = array(); + /** * Class construct * @@ -189,7 +197,7 @@ class Mage_Core_Model_Config extends Mage_Core_Model_Config_Base public function __construct($sourceData=null) { $this->setCacheId('config_global'); - $this->_options = new Mage_Core_Model_Config_Options(); + $this->_options = new Mage_Core_Model_Config_Options($sourceData); $this->_prototype = new Mage_Core_Model_Config_Base(); $this->_cacheChecksum = null; parent::__construct($sourceData); @@ -282,7 +290,7 @@ public function loadBase() */ public function loadModulesCache() { - if (Mage::isInstalled()) { + if (Mage::isInstalled(array('etc_dir' => $this->getOptions()->getEtcDir()))) { if ($this->_canUseCacheForInit()) { Varien_Profiler::start('mage::app::init::config::load_cache'); $loaded = $this->loadCache(); @@ -305,7 +313,9 @@ public function loadModules() { Varien_Profiler::start('config/load-modules'); $this->_loadDeclaredModules(); - $this->loadModulesConfiguration('config.xml', $this); + + $resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core')); + $this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this); /** * Prevent local.xml directives overwriting @@ -480,7 +490,9 @@ protected function _saveSectionCache($idPrefix, $sectionName, $source, $recursio $cacheId = $idPrefix . '_' . $sectionName; if ($recursionLevel > 0) { foreach ($source->$sectionName->children() as $subSectionName => $node) { - $this->_saveSectionCache($cacheId, $subSectionName, $source->$sectionName, $recursionLevel-1, $tags); + $this->_saveSectionCache( + $cacheId, $subSectionName, $source->$sectionName, $recursionLevel-1, $tags + ); } } $this->_cachePartsForSave[$cacheId] = $source->$sectionName->asNiceXml('', false); @@ -570,29 +582,47 @@ public function cleanCache() } /** - * Get node value from cached section data + * Getter for section configuration object * - * @param array $path - * @return Mage_Core_Model_Config + * @param array $path + * @return Mage_Core_Model_Config_Element */ - public function getSectionNode($path) + protected function _getSectionConfig($path) { - $section = $path[0]; - $recursion = $this->_cacheSections[$section]; - $sectioPath = array_slice($path, 0, $recursion+1); - $path = array_slice($path, $recursion+1); - $sectionKey = implode('_', $sectioPath); + $section = $path[0]; + if (!isset($this->_cacheSections[$section])) { + return false; + } + $sectionPath = array_slice($path, 0, $this->_cacheSections[$section]+1); + $sectionKey = implode('_', $sectionPath); if (!isset($this->_cacheLoadedSections[$sectionKey])) { - Varien_Profiler::start('mage::app::init::config::section::'.$sectionKey); + Varien_Profiler::start('init_config_section:' . $sectionKey); $this->_cacheLoadedSections[$sectionKey] = $this->_loadSectionCache($sectionKey); - Varien_Profiler::stop('mage::app::init::config::section::'.$sectionKey); + Varien_Profiler::stop('init_config_section:' . $sectionKey); } if ($this->_cacheLoadedSections[$sectionKey] === false) { return false; } - return $this->_cacheLoadedSections[$sectionKey]->descend($path); + return $this->_cacheLoadedSections[$sectionKey]; + } + + /** + * Get node value from cached section data + * + * @param array $path + * @return Mage_Core_Model_Config + */ + public function getSectionNode($path) + { + $section = $path[0]; + $config = $this->_getSectionConfig($path); + $path = array_slice($path, $this->_cacheSections[$section] + 1); + if ($config) { + return $config->descend($path); + } + return false; } /** @@ -600,7 +630,7 @@ public function getSectionNode($path) * * @param string $path * @param string $scope - * @param string $scopeCode + * @param string|int $scopeCode * @return Mage_Core_Model_Config_Element */ public function getNode($path=null, $scope='', $scopeCode=null) @@ -634,9 +664,32 @@ public function getNode($path=null, $scope='', $scopeCode=null) } } } - return parent::getNode($path); + return parent::getNode($path); } + /** + * Create node by $path and set its value. + * + * @param string $path separated by slashes + * @param string $value + * @param bool $overwrite + * @return Varien_Simplexml_Config + */ + public function setNode($path, $value, $overwrite = true) + { + if ($this->_useCache && ($path !== null)) { + $sectionPath = explode('/', $path); + $config = $this->_getSectionConfig($sectionPath); + if ($config) { + $sectionPath = array_slice($sectionPath, $this->_cacheSections[$sectionPath[0]]+1); + $sectionPath = implode('/', $sectionPath); + $config->setNode($sectionPath, $value, $overwrite); + } + } + return parent::setNode($path, $value, $overwrite); + } + + /** * Retrive Declared Module file list * @@ -663,11 +716,9 @@ protected function _getDeclaredModuleFiles() if ($name == 'Mage_All') { $collectModuleFiles['base'][] = $v; - } - elseif (substr($name, 0, 5) == 'Mage_') { + } else if (substr($name, 0, 5) == 'Mage_') { $collectModuleFiles['mage'][] = $v; - } - else { + } else { $collectModuleFiles['custom'][] = $v; } } @@ -679,6 +730,40 @@ protected function _getDeclaredModuleFiles() ); } + /** + * Add module(s) to allowed list + * + * @param strung|array $module + * @return Mage_Core_Model_Config + */ + public function addAllowedModules($module) + { + if (is_array($module)) { + foreach ($module as $moduleName) { + $this->addAllowedModules($moduleName); + } + } elseif (!in_array($module, $this->_allowedModules)) { + $this->_allowedModules[] = $module; + } + + return $this; + } + + /** + * Define if module is allowed + * + * @param string $moduleName + * @return bool + */ + protected function _isAllowedModule($moduleName) + { + if (empty($this->_allowedModules)) { + return true; + } else { + return in_array($moduleName, $this->_allowedModules); + } + } + /** * Load declared modules configuration * @@ -706,6 +791,10 @@ protected function _loadDeclaredModules($mergeConfig = null) $moduleDepends = array(); foreach ($unsortedConfig->getNode('modules')->children() as $moduleName => $moduleNode) { + if (!$this->_isAllowedModule($moduleName)) { + continue; + } + $depends = array(); if ($moduleNode->depends) { foreach ($moduleNode->depends->children() as $depend) { @@ -719,7 +808,7 @@ protected function _loadDeclaredModules($mergeConfig = null) ); } - // check and sort module dependens + // check and sort module dependence $moduleDepends = $this->_sortModuleDepends($moduleDepends); // create sorted config @@ -755,7 +844,9 @@ protected function _sortModuleDepends($modules) $depends = $moduleProps['depends']; foreach ($moduleProps['depends'] as $depend => $true) { if ($moduleProps['active'] && ((!isset($modules[$depend])) || empty($modules[$depend]['active']))) { - Mage::throwException(Mage::helper('core')->__('Module "%1$s" requires module "%2$s".', $moduleName, $depend)); + Mage::throwException( + Mage::helper('core')->__('Module "%1$s" requires module "%2$s".', $moduleName, $depend) + ); } $depends = array_merge($depends, $modules[$depend]['depends']); } @@ -812,21 +903,23 @@ public function determineOmittedNamespace($name, $asFullModuleName = false) $name = explode('_', strtolower($name)); $partsNum = count($name); - $i = 0; + $defaultNamespaceFlag = false; foreach ($this->_moduleNamespaces as $namespaceName => $namespace) { // assume the namespace is omitted (default namespace only, which comes first) - if (0 === $i) { + if ($defaultNamespaceFlag === false) { + $defaultNamespaceFlag = true; $defaultNS = $namespaceName . '_' . $name[0]; if (isset($namespace[$defaultNS])) { return $asFullModuleName ? $namespace[$defaultNS] : $name[0]; // return omitted as well } } // assume namespace is qualified - $fullNS = $name[0] . '_' . $name[1]; - if (2 <= $partsNum && isset($namespace[$fullNS])) { - return $asFullModuleName ? $namespace[$fullNS] : $fullNS; + if(isset($name[1])) { + $fullNS = $name[0] . '_' . $name[1]; + if (2 <= $partsNum && isset($namespace[$fullNS])) { + return $asFullModuleName ? $namespace[$fullNS] : $fullNS; + } } - $i++; } return ''; } @@ -841,7 +934,7 @@ public function determineOmittedNamespace($name, $asFullModuleName = false) */ public function loadModulesConfiguration($fileName, $mergeToObject = null, $mergeModel=null) { - $disableLocalModules = !$this->_canUseLocalModules(); + $disableLocalModules = !$this->_canUseLocalModules(); if ($mergeToObject === null) { $mergeToObject = clone $this->_prototype; @@ -856,9 +949,15 @@ public function loadModulesConfiguration($fileName, $mergeToObject = null, $merg if ($disableLocalModules && ('local' === (string)$module->codePool)) { continue; } - $configFile = $this->getModuleDir('etc', $modName).DS.$fileName; - if ($mergeModel->loadFile($configFile)) { - $mergeToObject->extend($mergeModel, true); + if (!is_array($fileName)) { + $fileName = array($fileName); + } + + foreach ($fileName as $configFile) { + $configFile = $this->getModuleDir('etc', $modName).DS.$configFile; + if ($mergeModel->loadFile($configFile)) { + $mergeToObject->extend($mergeModel, true); + } } } } @@ -890,7 +989,9 @@ public function getDistroServerVars() $hostArr = explode(':', $_SERVER['HTTP_HOST']); $host = $hostArr[0]; - $port = isset($hostArr[1]) && (!$secure && $hostArr[1]!=80 || $secure && $hostArr[1]!=443) ? ':'.$hostArr[1] : ''; + $port = isset( + $hostArr[1]) && (!$secure && $hostArr[1]!=80 || $secure && $hostArr[1]!=443 + ) ? ':'.$hostArr[1] : ''; $path = Mage::app()->getRequest()->getBasePath(); $baseUrl = $scheme.$host.$port.rtrim($path, '/').'/'; @@ -1024,6 +1125,9 @@ public function getModuleDir($type, $moduleName) case 'sql': $dir .= DS.'sql'; break; + case 'data': + $dir .= DS.'data'; + break; case 'locale': $dir .= DS.'locale'; @@ -1045,8 +1149,7 @@ public function loadEventObservers($area) $events = $this->getNode("$area/events"); if ($events) { $events = $events->children(); - } - else { + } else { return false; } @@ -1121,12 +1224,29 @@ public function getGroupedClassName($groupType, $classId, $groupRootNode=null) return $this->_classNameCache[$groupRootNode][$group][$class]; } - //$config = $this->getNode($groupRootNode.'/'.$group); $config = $this->_xml->global->{$groupType.'s'}->{$group}; + // First - check maybe the entity class was rewritten + $className = null; if (isset($config->rewrite->$class)) { $className = (string)$config->rewrite->$class; } else { + /** + * Backwards compatibility for pre-MMDB extensions. + * In MMDB release resource nodes <..._mysql4> were renamed to <..._resource>. So is left + * to keep name of previously used nodes, that still may be used by non-updated extensions. + */ + if ($config->deprecatedNode) { + $deprecatedNode = $config->deprecatedNode; + $configOld = $this->_xml->global->{$groupType.'s'}->$deprecatedNode; + if (isset($configOld->rewrite->$class)) { + $className = (string) $configOld->rewrite->$class; + } + } + } + + // Second - if entity is not rewritten then use class prefix to form class name + if (empty($className)) { if (!empty($config)) { $className = $config->getClassName(); } @@ -1165,14 +1285,36 @@ public function getBlockClassName($blockType) */ public function getHelperClassName($helperName) { - if (strpos($helperName, '/')===false) { + if (strpos($helperName, '/') === false) { $helperName .= '/data'; } return $this->getGroupedClassName('helper', $helperName); } /** - * Retrieve modele class name + * Retreive resource helper instance + * + * Example: + * $config->getResourceHelper('cms') + * will instantiate Mage_Cms_Model_Resource_Helper_ + * + * @param string $moduleName + * @return Mage_Core_Model_Resource_Helper_Abstract|false + */ + public function getResourceHelper($moduleName) + { + $connectionModel = $this->_getResourceConnectionModel($moduleName); + $helperClass = sprintf('%s/helper_%s', $moduleName, $connectionModel); + $helperClassName = $this->_getResourceModelFactoryClassName($helperClass); + if ($helperClassName) { + return $this->getModelInstance($helperClassName, $moduleName); + } + + return false; + } + + /** + * Retrieve module class name * * @param sting $modelClass * @return string @@ -1196,7 +1338,7 @@ public function getModelClassName($modelClass) * * @param string $modelClass * @param array|object $constructArguments - * @return Mage_Core_Model_Abstract + * @return Mage_Core_Model_Abstract|false */ public function getModelInstance($modelClass='', $constructArguments=array()) { @@ -1207,7 +1349,6 @@ public function getModelInstance($modelClass='', $constructArguments=array()) Varien_Profiler::stop('CORE::create_object_of::'.$className); return $obj; } else { - #throw Mage::exception('Mage_Core', Mage::helper('core')->__('Model class does not exist: %s.', $modelClass)); return false; } } @@ -1319,8 +1460,7 @@ public function getStoresConfigByPath($path, $allowValues = array(), $useAsKey = if (empty($allowValues)) { $storeValues[$key] = $pathValue; - } - elseif(in_array($pathValue, $allowValues)) { + } else if (in_array($pathValue, $allowValues)) { $storeValues[$key] = $pathValue; } } @@ -1328,18 +1468,23 @@ public function getStoresConfigByPath($path, $allowValues = array(), $useAsKey = } /** - * Check security requirements for url + * Check whether given path should be secure according to configuration security requirements for URL + * "Secure" should not be confused with https protocol, it is about web/secure/*_url settings usage only * - * @param string $url - * @return bool + * @param string $url + * @return bool */ public function shouldUrlBeSecure($url) { + if (!Mage::getStoreConfigFlag(Mage_Core_Model_Store::XML_PATH_SECURE_IN_FRONTEND)) { + return false; + } + if (!isset($this->_secureUrlCache[$url])) { $this->_secureUrlCache[$url] = false; $secureUrls = $this->getNode('frontend/secure_url'); foreach ($secureUrls->children() as $match) { - if (strpos($url, (string)$match)===0) { + if (strpos($url, (string)$match) === 0) { $this->_secureUrlCache[$url] = true; break; } @@ -1425,36 +1570,51 @@ public function getFieldset($name, $root = 'global') } /** - * Get factory class name for for a resource + * Retrieve resource connection model name + * + * @param string $moduleName + * @return string + */ + protected function _getResourceConnectionModel($moduleName = null) + { + $config = null; + if (!is_null($moduleName)) { + $setupResource = $moduleName . '_setup'; + $config = $this->getResourceConnectionConfig($setupResource); + } + if (!$config) { + $config = $this->getResourceConnectionConfig(Mage_Core_Model_Resource::DEFAULT_SETUP_RESOURCE); + } + + return (string)$config->model; + } + + /** + * Get factory class name for a resource * * @param string $modelClass * @return string|false */ protected function _getResourceModelFactoryClassName($modelClass) { - $classArr = explode('/', $modelClass); - - $resourceModel = false; - - if (!isset($this->_xml->global->models->{$classArr[0]})) { + $classArray = explode('/', $modelClass); + if (count($classArray) != 2) { return false; } - $module = $this->_xml->global->models->{$classArr[0]}; - - if ((count($classArr)==2) - && isset($module->{$classArr[1]}->resourceModel) - && $resourceInfo = $module->{$classArr[1]}->resourceModel) { - $resourceModel = (string) $resourceInfo; - } - elseif (isset($module->resourceModel) && $resourceInfo = $module->resourceModel) { - $resourceModel = (string) $resourceInfo; + list($module, $model) = $classArray; + if (!isset($this->_xml->global->models->{$module})) { + return false; } - if (!$resourceModel) { + $moduleNode = $this->_xml->global->models->{$module}; + if (!empty($moduleNode->resourceModel)) { + $resourceModel = (string)$moduleNode->resourceModel; + } else { return false; } - return $resourceModel . '/' . $classArr[1]; + + return $resourceModel . '/' . $model; } /** diff --git a/app/code/core/Mage/Core/Model/Config/Base.php b/app/code/core/Mage/Core/Model/Config/Base.php index 77b43dd0..bde7a680 100644 --- a/app/code/core/Mage/Core/Model/Config/Base.php +++ b/app/code/core/Mage/Core/Model/Config/Base.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Config/Data.php b/app/code/core/Mage/Core/Model/Config/Data.php index 28273cb3..48944c10 100644 --- a/app/code/core/Mage/Core/Model/Config/Data.php +++ b/app/code/core/Mage/Core/Model/Config/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,20 @@ /** * Config data model * - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Config_Data _getResource() + * @method Mage_Core_Model_Resource_Config_Data getResource() + * @method string getScope() + * @method Mage_Core_Model_Config_Data setScope(string $value) + * @method int getScopeId() + * @method Mage_Core_Model_Config_Data setScopeId(int $value) + * @method string getPath() + * @method Mage_Core_Model_Config_Data setPath(string $value) + * @method string getValue() + * @method Mage_Core_Model_Config_Data setValue(string $value) + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team */ class Mage_Core_Model_Config_Data extends Mage_Core_Model_Abstract { diff --git a/app/code/core/Mage/Core/Model/Config/Element.php b/app/code/core/Mage/Core/Model/Config/Element.php index 553c6796..336bcbf5 100644 --- a/app/code/core/Mage/Core/Model/Config/Element.php +++ b/app/code/core/Mage/Core/Model/Config/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Config/Options.php b/app/code/core/Mage/Core/Model/Config/Options.php index 03a47b11..31fbd4b7 100644 --- a/app/code/core/Mage/Core/Model/Config/Options.php +++ b/app/code/core/Mage/Core/Model/Config/Options.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,6 +33,12 @@ */ class Mage_Core_Model_Config_Options extends Varien_Object { + /** + * Var directory + * + * @var string + */ + const VAR_DIRECTORY = 'var'; /** * Flag cache for existing or already created directories * @@ -138,7 +144,8 @@ public function getSysTmpDir() public function getVarDir() { //$dir = $this->getDataSetDefault('var_dir', $this->getBaseDir().DS.'var'); - $dir = isset($this->_data['var_dir']) ? $this->_data['var_dir'] : $this->_data['base_dir'].DS.'var'; + $dir = isset($this->_data['var_dir']) ? $this->_data['var_dir'] + : $this->_data['base_dir'] . DS . self::VAR_DIRECTORY; if (!$this->createDirIfNotExists($dir)) { $dir = $this->getSysTmpDir().DS.'magento'.DS.'var'; if (!$this->createDirIfNotExists($dir)) { @@ -214,9 +221,11 @@ public function createDirIfNotExists($dir) return false; } } else { + $oldUmask = umask(0); if (!@mkdir($dir, 0777, true)) { return false; } + umask($oldUmask); } $this->_dirExists[$dir] = true; return true; diff --git a/app/code/core/Mage/Core/Model/Config/System.php b/app/code/core/Mage/Core/Model/Config/System.php index 9d9a0a72..31ed4824 100644 --- a/app/code/core/Mage/Core/Model/Config/System.php +++ b/app/code/core/Mage/Core/Model/Config/System.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Convert.php b/app/code/core/Mage/Core/Model/Convert.php index a793b346..a671fc15 100644 --- a/app/code/core/Mage/Core/Model/Convert.php +++ b/app/code/core/Mage/Core/Model/Convert.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Cookie.php b/app/code/core/Mage/Core/Model/Cookie.php index 83c8e90e..4f82c30f 100644 --- a/app/code/core/Mage/Core/Model/Cookie.php +++ b/app/code/core/Mage/Core/Model/Cookie.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -198,6 +198,7 @@ public function isSecure() * @param string $path * @param string $domain * @param int|bool $secure + * @param bool $httponly * @return Mage_Core_Model_Cookie */ public function set($name, $value, $period = null, $path = null, $domain = null, $secure = null, $httponly = null) diff --git a/app/code/core/Mage/Core/Model/Date.php b/app/code/core/Mage/Core/Model/Date.php index b93d6a40..dca47c55 100644 --- a/app/code/core/Mage/Core/Model/Date.php +++ b/app/code/core/Mage/Core/Model/Date.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -69,7 +69,7 @@ protected function _getConfigTimezone() /** * Calculates timezone offset * - * @var string $timezone + * @param string $timezone * @return int offset between timezone and gmt */ public function calculateOffset($timezone = null) @@ -96,8 +96,8 @@ public function calculateOffset($timezone = null) /** * Forms GMT date * - * @param string $format - * @param int || string $input date in current timezone + * @param string $format + * @param int|string $input date in current timezone * @return string */ public function gmtDate($format = null, $input = null) @@ -106,7 +106,13 @@ public function gmtDate($format = null, $input = null) $format = 'Y-m-d H:i:s'; } - $result = date($format, $this->gmtTimestamp($input)); + $date = $this->gmtTimestamp($input); + + if ($date === false) { + return false; + } + + $result = date($format, $date); return $result; } @@ -114,8 +120,9 @@ public function gmtDate($format = null, $input = null) * Converts input date into date with timezone offset * Input date must be in GMT timezone * - * @param string $format - * @param int || string $input date in GMT timezone + * @param string $format + * @param int|string $input date in GMT timezone + * @return string */ public function date($format = null, $input = null) { @@ -130,7 +137,8 @@ public function date($format = null, $input = null) /** * Forms GMT timestamp * - * @param int || string $input date in current timezone + * @param int|string $input date in current timezone + * @return int */ public function gmtTimestamp($input = null) { @@ -142,6 +150,11 @@ public function gmtTimestamp($input = null) $result = strtotime($input); } + if ($result === false) { + // strtotime() unable to parse string (it's not a date or has incorrect format) + return false; + } + $date = Mage::app()->getLocale()->date($result); $timestamp = $date->get(Zend_Date::TIMESTAMP) - $date->get(Zend_Date::TIMEZONE_SECS); @@ -154,7 +167,8 @@ public function gmtTimestamp($input = null) * Converts input date into timestamp with timezone offset * Input date must be in GMT timezone * - * @param int || string $input date in GMT timezone + * @param int|string $input date in GMT timezone + * @return int */ public function timestamp($input = null) { @@ -176,7 +190,7 @@ public function timestamp($input = null) /** * Get current timezone offset in seconds/minutes/hours * - * @param string $type + * @param string $type * @return int */ public function getGmtOffset($type = 'seconds') @@ -222,13 +236,22 @@ public function parseDateTime($dateTimeString, $dateTimeFormat) { // look for supported format $isSupportedFormatFound = false; - foreach (array( + + $formats = array( // priority is important! - '%m/%d/%y %I:%M' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5)), - 'm/d/y h:i' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5)), - '%m/%d/%y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), - 'm/d/y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), - ) as $supportedFormat => $regRule) { + '%m/%d/%y %I:%M' => array( + '/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', + array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5) + ), + 'm/d/y h:i' => array( + '/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2}) ([0-9]{1,2}):([0-9]{1,2})/', + array('y' => 3, 'm' => 1, 'd' => 2, 'h' => 4, 'i' => 5) + ), + '%m/%d/%y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), + 'm/d/y' => array('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,2})/', array('y' => 3, 'm' => 1, 'd' => 2)), + ); + + foreach ($formats as $supportedFormat => $regRule) { if (false !== strpos($dateTimeFormat, $supportedFormat, 0)) { $isSupportedFormatFound = true; break; diff --git a/app/code/core/Mage/Core/Model/Design.php b/app/code/core/Mage/Core/Model/Design.php index 37957d5a..74a8ba5c 100644 --- a/app/code/core/Mage/Core/Model/Design.php +++ b/app/code/core/Mage/Core/Model/Design.php @@ -20,11 +20,29 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Core_Model_Resource_Design _getResource() + * @method Mage_Core_Model_Resource_Design getResource() + * @method int getStoreId() + * @method Mage_Core_Model_Design setStoreId(int $value) + * @method string getDesign() + * @method Mage_Core_Model_Design setDesign(string $value) + * @method string getDateFrom() + * @method Mage_Core_Model_Design setDateFrom(string $value) + * @method string getDateTo() + * @method Mage_Core_Model_Design setDateTo(string $value) + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team + */ class Mage_Core_Model_Design extends Mage_Core_Model_Abstract { protected function _construct() @@ -43,7 +61,7 @@ public function loadChange($storeId, $date = null) $result = $this->getResource() ->loadChange($storeId, $date); - if (count($result)){ + if (!empty($result)) { if (!empty($result['design'])) { $tmp = explode('/', $result['design']); $result['package'] = $tmp[0]; diff --git a/app/code/core/Mage/Core/Model/Design/Package.php b/app/code/core/Mage/Core/Model/Design/Package.php index 2f3cfda3..dffa4c6f 100644 --- a/app/code/core/Mage/Core/Model/Design/Package.php +++ b/app/code/core/Mage/Core/Model/Design/Package.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -186,14 +186,14 @@ public function setAllGetOld($storePackageArea) $oldValues['store'] = $this->getStore(); $this->setStore($storePackageArea['store']); } - if (array_key_exists('package', $storePackageArea)) { - $oldValues['package'] = $this->getPackageName(); - $this->setPackageName($storePackageArea['package']); - } if (array_key_exists('area', $storePackageArea)) { $oldValues['area'] = $this->getArea(); $this->setArea($storePackageArea['area']); } + if (array_key_exists('package', $storePackageArea)) { + $oldValues['package'] = $this->getPackageName(); + $this->setPackageName($storePackageArea['package']); + } return $oldValues; } @@ -518,7 +518,7 @@ public function getThemeList($package = null) * Directories lister utility method * * @param string $path - * @param string|false $fullPath + * @param string|bool $fullPath * @return array */ private function _listDirectories($path, $fullPath = false) @@ -553,32 +553,53 @@ private function _listDirectories($path, $fullPath = false) */ protected function _checkUserAgentAgainstRegexps($regexpsConfigPath) { - if (!empty($_SERVER['HTTP_USER_AGENT'])) { - if (!empty(self::$_customThemeTypeCache[$regexpsConfigPath])) { - return self::$_customThemeTypeCache[$regexpsConfigPath]; + if (empty($_SERVER['HTTP_USER_AGENT'])) { + return false; + } + + if (!empty(self::$_customThemeTypeCache[$regexpsConfigPath])) { + return self::$_customThemeTypeCache[$regexpsConfigPath]; + } + + $configValueSerialized = Mage::getStoreConfig($regexpsConfigPath, $this->getStore()); + + if (!$configValueSerialized) { + return false; + } + + $regexps = @unserialize($configValueSerialized); + + if (empty($regexps)) { + return false; + } + + return self::getPackageByUserAgent($regexps, $regexpsConfigPath); + } + + /** + * Return package name based on design exception rules + * + * @param array $rules - design exception rules + * @param string $regexpsConfigPath + * @return bool|string + */ + public static function getPackageByUserAgent(array $rules, $regexpsConfigPath = 'path_mock') + { + foreach ($rules as $rule) { + if (!empty(self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']])) { + self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; + return $rule['value']; } - $configValueSerialized = Mage::getStoreConfig($regexpsConfigPath, $this->getStore()); - if ($configValueSerialized) { - $regexps = @unserialize($configValueSerialized); - if (!empty($regexps)) { - foreach ($regexps as $rule) { - if (!empty(self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']])) { - self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; - return $rule['value']; - } - $regexp = $rule['regexp']; - if (false === strpos($regexp, '/', 0)) { - $regexp = '/' . $regexp . '/'; - } - if (@preg_match($regexp, $_SERVER['HTTP_USER_AGENT'])) { - self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']] = true; - self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; - return $rule['value']; - } - } - } + + $regexp = '/' . trim($rule['regexp'], '/') . '/'; + + if (@preg_match($regexp, $_SERVER['HTTP_USER_AGENT'])) { + self::$_regexMatchCache[$rule['regexp']][$_SERVER['HTTP_USER_AGENT']] = true; + self::$_customThemeTypeCache[$regexpsConfigPath] = $rule['value']; + return $rule['value']; } } + return false; } @@ -595,8 +616,8 @@ public function getMergedJsUrl($files) if (!$targetDir) { return ''; } - if (Mage::helper('core')->mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) { - return Mage::getBaseUrl('media') . 'js/' . $targetFilename; + if ($this->_mergeFiles($files, $targetDir . DS . $targetFilename, false, null, 'js')) { + return Mage::getBaseUrl('media', Mage::app()->getRequest()->isSecure()) . 'js/' . $targetFilename; } return ''; } @@ -607,18 +628,83 @@ public function getMergedJsUrl($files) * @param $files * @return string */ - public function getMergedCssUrl($files) - { - $targetFilename = md5(implode(',', $files)) . '.css'; - $targetDir = $this->_initMergerDir('css'); + public function getMergedCssUrl($files) + { + // secure or unsecure + $isSecure = Mage::app()->getRequest()->isSecure(); + $mergerDir = $isSecure ? 'css_secure' : 'css'; + $targetDir = $this->_initMergerDir($mergerDir); if (!$targetDir) { return ''; } - if (Mage::helper('core')->mergeFiles($files, $targetDir . DS . $targetFilename, false, array($this, 'beforeMergeCss'), 'css')) { - return Mage::getBaseUrl('media') . 'css/' . $targetFilename; + + // base hostname & port + $baseMediaUrl = Mage::getBaseUrl('media', $isSecure); + $hostname = parse_url($baseMediaUrl, PHP_URL_HOST); + $port = parse_url($baseMediaUrl, PHP_URL_PORT); + if (false === $port) { + $port = $isSecure ? 443 : 80; + } + + // merge into target file + $targetFilename = md5(implode(',', $files) . "|{$hostname}|{$port}") . '.css'; + $mergeFilesResult = $this->_mergeFiles( + $files, $targetDir . DS . $targetFilename, + false, + array($this, 'beforeMergeCss'), + 'css' + ); + if ($mergeFilesResult) { + return $baseMediaUrl . $mergerDir . '/' . $targetFilename; } return ''; - } + } + + /** + * Merges files into one and saves it into DB (if DB file storage is on) + * + * @see Mage_Core_Helper_Data::mergeFiles() + * @param array $srcFiles + * @param string|bool $targetFile - file path to be written + * @param bool $mustMerge + * @param callback $beforeMergeCallback + * @param array|string $extensionsFilter + * @return bool|string + */ + protected function _mergeFiles(array $srcFiles, $targetFile = false, + $mustMerge = false, $beforeMergeCallback = null, $extensionsFilter = array()) + { + if (Mage::helper('core/file_storage_database')->checkDbUsage()) { + if (!file_exists($targetFile)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($targetFile); + } + if (file_exists($targetFile)) { + $filemtime = filemtime($targetFile); + } else { + $filemtime = null; + } + $result = Mage::helper('core')->mergeFiles( + $srcFiles, + $targetFile, + $mustMerge, + $beforeMergeCallback, + $extensionsFilter + ); + if ($result && (filemtime($targetFile) > $filemtime)) { + Mage::helper('core/file_storage_database')->saveFile($targetFile); + } + return $result; + + } else { + return Mage::helper('core')->mergeFiles( + $srcFiles, + $targetFile, + $mustMerge, + $beforeMergeCallback, + $extensionsFilter + ); + } + } /** * Remove all merged js/css files @@ -628,7 +714,8 @@ public function getMergedCssUrl($files) public function cleanMergedJsCss() { $result = (bool)$this->_initMergerDir('js', true); - return (bool)$this->_initMergerDir('css', true) && $result; + $result = (bool)$this->_initMergerDir('css', true) && $result; + return (bool)$this->_initMergerDir('css_secure', true) && $result; } /** @@ -637,6 +724,7 @@ public function cleanMergedJsCss() * * @param string $dirRelativeName * @param bool $cleanup + * @return bool */ protected function _initMergerDir($dirRelativeName, $cleanup = false) { @@ -645,6 +733,7 @@ protected function _initMergerDir($dirRelativeName, $cleanup = false) $dir = Mage::getBaseDir('media') . DS . $dirRelativeName; if ($cleanup) { Varien_Io_File::rmdirRecursive($dir); + Mage::helper('core/file_storage_database')->deleteFolder($dir); } if (!is_dir($dir)) { mkdir($dir); @@ -670,7 +759,7 @@ public function beforeMergeCss($file, $contents) $cssImport = '/@import\\s+([\'"])(.*?)[\'"]/'; $contents = preg_replace_callback($cssImport, array($this, '_cssMergerImportCallback'), $contents); - $cssUrl = '/url\\(\\s*([^\\)\\s]+)\\s*\\)?/'; + $cssUrl = '/url\\(\\s*(?!data:)([^\\)\\s]+)\\s*\\)?/'; $contents = preg_replace_callback($cssUrl, array($this, '_cssMergerUrlCallback'), $contents); return $contents; @@ -725,18 +814,31 @@ protected function _cssMergerUrlCallback($match) protected function _prepareUrl($uri) { // check absolute or relative url - if (!preg_match('/^[http|https]/i', $uri) && !preg_match('/^\//i', $uri)) { - + if (!preg_match('/^https?:/i', $uri) && !preg_match('/^\//i', $uri)) { $fileDir = ''; $pathParts = explode(DS, $uri); $fileDirParts = explode(DS, $this->_callbackFileDir); - $baseUrl = Mage::getBaseUrl('web'); + $store = $this->getStore(); + if ($store->isAdmin()) { + $secure = $store->isAdminUrlSecure(); + } else { + $secure = $store->isFrontUrlSecure() && Mage::app()->getRequest()->isSecure(); + } + + if ('skin' == $fileDirParts[0]) { + $baseUrl = Mage::getBaseUrl('skin', $secure); + $fileDirParts = array_slice($fileDirParts, 1); + } elseif ('media' == $fileDirParts[0]) { + $baseUrl = Mage::getBaseUrl('media', $secure); + $fileDirParts = array_slice($fileDirParts, 1); + } else { + $baseUrl = Mage::getBaseUrl('web', $secure); + } foreach ($pathParts as $key=>$part) { if ($part == '.' || $part == '..') { unset($pathParts[$key]); } - if ($part == '..' && count($fileDirParts)) { $fileDirParts = array_slice($fileDirParts, 0, count($fileDirParts) - 1); } diff --git a/app/code/core/Mage/Core/Model/Design/Source/Apply.php b/app/code/core/Mage/Core/Model/Design/Source/Apply.php index 87ba9e88..6016718f 100644 --- a/app/code/core/Mage/Core/Model/Design/Source/Apply.php +++ b/app/code/core/Mage/Core/Model/Design/Source/Apply.php @@ -20,11 +20,13 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - +/** + * @deprecated after 1.4.1.0. + */ class Mage_Core_Model_Design_Source_Apply extends Mage_Eav_Model_Entity_Attribute_Source_Abstract { public function getAllOptions() diff --git a/app/code/core/Mage/Core/Model/Design/Source/Design.php b/app/code/core/Mage/Core/Model/Design/Source/Design.php index 61bf0fdd..f4d3f218 100644 --- a/app/code/core/Mage/Core/Model/Design/Source/Design.php +++ b/app/code/core/Mage/Core/Model/Design/Source/Design.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Email.php b/app/code/core/Mage/Core/Model/Email.php index 32040d8c..b3228ac7 100644 --- a/app/code/core/Mage/Core/Model/Email.php +++ b/app/code/core/Mage/Core/Model/Email.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Email/Info.php b/app/code/core/Mage/Core/Model/Email/Info.php new file mode 100644 index 00000000..3daa151a --- /dev/null +++ b/app/code/core/Mage/Core/Model/Email/Info.php @@ -0,0 +1,137 @@ + + */ +class Mage_Core_Model_Email_Info extends Varien_Object +{ + /** + * Name list of "Bcc" recipients + * + * @var array + */ + protected $_bccNames = array(); + + /** + * Email list of "Bcc" recipients + * + * @var array + */ + protected $_bccEmails = array(); + + /** + * Name list of "To" recipients + * + * @var array + */ + protected $_toNames = array(); + + /** + * Email list of "To" recipients + * + * @var array + */ + protected $_toEmails = array(); + + + /** + * Add new "Bcc" recipient to current email + * + * @param string $email + * @param string|null $name + * @return Mage_Core_Model_Email_Info + */ + public function addBcc($email, $name = null) + { + array_push($this->_bccNames, $name); + array_push($this->_bccEmails, $email); + return $this; + } + + /** + * Add new "To" recipient to current email + * + * @param string $email + * @param string|null $name + * @return Mage_Core_Model_Email_Info + */ + public function addTo($email, $name = null) + { + array_push($this->_toNames, $name); + array_push($this->_toEmails, $email); + return $this; + } + + /** + * Get the name list of "Bcc" recipients + * + * @return array + */ + public function getBccNames() + { + return $this->_bccNames; + } + + /** + * Get the email list of "Bcc" recipients + * + * @return array + */ + public function getBccEmails() + { + return $this->_bccEmails; + } + + /** + * Get the name list of "To" recipients + * + * @return array + */ + public function getToNames() + { + return $this->_toNames; + } + + /** + * Get the email list of "To" recipients + * + * @return array + */ + public function getToEmails() + { + return $this->_toEmails; + } +} diff --git a/app/code/core/Mage/Core/Model/Email/Template.php b/app/code/core/Mage/Core/Model/Email/Template.php index 087404a2..214a469b 100644 --- a/app/code/core/Mage/Core/Model/Email/Template.php +++ b/app/code/core/Mage/Core/Model/Email/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,25 +38,45 @@ * ); * $emailTemplate->send('some@domain.com', 'Name Of User', $variables); * - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Email_Template _getResource() + * @method Mage_Core_Model_Resource_Email_Template getResource() + * @method string getTemplateCode() + * @method Mage_Core_Model_Email_Template setTemplateCode(string $value) + * @method string getTemplateText() + * @method Mage_Core_Model_Email_Template setTemplateText(string $value) + * @method string getTemplateStyles() + * @method Mage_Core_Model_Email_Template setTemplateStyles(string $value) + * @method int getTemplateType() + * @method Mage_Core_Model_Email_Template setTemplateType(int $value) + * @method string getTemplateSubject() + * @method Mage_Core_Model_Email_Template setTemplateSubject(string $value) + * @method string getTemplateSenderName() + * @method Mage_Core_Model_Email_Template setTemplateSenderName(string $value) + * @method string getTemplateSenderEmail() + * @method Mage_Core_Model_Email_Template setTemplateSenderEmail(string $value) + * @method string getAddedAt() + * @method Mage_Core_Model_Email_Template setAddedAt(string $value) + * @method string getModifiedAt() + * @method Mage_Core_Model_Email_Template setModifiedAt(string $value) + * @method string getOrigTemplateCode() + * @method Mage_Core_Model_Email_Template setOrigTemplateCode(string $value) + * @method string getOrigTemplateVariables() + * @method Mage_Core_Model_Email_Template setOrigTemplateVariables(string $value) + * + * @category Mage + * @package Mage_Core * @author Magento Core Team */ -class Mage_Core_Model_Email_Template extends Mage_Core_Model_Abstract +class Mage_Core_Model_Email_Template extends Mage_Core_Model_Template { - /** - * Types of template - */ - const TYPE_TEXT = 1; - const TYPE_HTML = 2; - /** * Configuration path for default email templates - * */ - const XML_PATH_TEMPLATE_EMAIL = 'global/template/email'; - const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path'; - const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email'; + const XML_PATH_TEMPLATE_EMAIL = 'global/template/email'; + const XML_PATH_SENDING_SET_RETURN_PATH = 'system/smtp/set_return_path'; + const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email'; + const XML_PATH_DESIGN_EMAIL_LOGO = 'design/email/logo'; + const XML_PATH_DESIGN_EMAIL_LOGO_ALT = 'design/email/logo_alt'; protected $_templateFilter; protected $_preprocessFlag = false; @@ -65,19 +85,49 @@ class Mage_Core_Model_Email_Template extends Mage_Core_Model_Abstract static protected $_defaultTemplates; /** - * Configuration of desing package for template + * Initialize email template model * - * @var Varien_Object */ - protected $_designConfig; + protected function _construct() + { + $this->_init('core/email_template'); + } /** - * Initialize email template model + * Return logo URL for emails + * Take logo from skin if custom logo is undefined * + * @param Mage_Core_Model_Store|int|string $store + * @return string */ - protected function _construct() + protected function _getLogoUrl($store) { - $this->_init('core/email_template'); + $store = Mage::app()->getStore($store); + $fileName = $store->getConfig(self::XML_PATH_DESIGN_EMAIL_LOGO); + if ($fileName) { + $uploadDir = Mage_Adminhtml_Model_System_Config_Backend_Email_Logo::UPLOAD_DIR; + $fullFileName = Mage::getBaseDir('media') . DS . $uploadDir . DS . $fileName; + if (file_exists($fullFileName)) { + return Mage::getBaseUrl('media') . $uploadDir . '/' . $fileName; + } + } + return Mage::getDesign()->getSkinUrl('images/logo_email.gif'); + } + + /** + * Return logo alt for emails + * + * @param Mage_Core_Model_Store|int|string $store + * @return string + */ + protected function _getLogoAlt($store) + { + $store = Mage::app()->getStore($store); + $alt = $store->getConfig(self::XML_PATH_DESIGN_EMAIL_LOGO_ALT); + if ($alt) { + return $alt; + } + return $store->getFrontendName(); } /** @@ -152,17 +202,17 @@ public function loadDefault($templateId, $locale=null) $data['file'], 'email', $locale ); - if (preg_match('//', $templateText, $matches)) { + if (preg_match('//u', $templateText, $matches)) { $this->setTemplateSubject($matches[1]); $templateText = str_replace($matches[0], '', $templateText); } - if (preg_match('//us', $templateText, $matches)) { + if (preg_match('//us', $templateText, $matches)) { $this->setData('orig_template_variables', str_replace("\n", '', $matches[1])); $templateText = str_replace($matches[0], '', $templateText); } - if (preg_match('//sm', $templateText, $matches)) { + if (preg_match('//s', $templateText, $matches)) { $this->setTemplateStyles($matches[1]); $templateText = str_replace($matches[0], '', $templateText); } @@ -252,13 +302,12 @@ public function isValidForSend() } /** - * Return true if template type eq text + * Getter for template type * - * @return boolean + * @return int|string */ - public function isPlain() - { - return $this->getTemplateType() == self::TYPE_TEXT; + public function getType(){ + return $this->getTemplateType(); } /** @@ -273,10 +322,21 @@ public function getProcessedTemplate(array $variables = array()) $processor->setUseSessionInUrl(false) ->setPlainTemplateMode($this->isPlain()); - if(!$this->_preprocessFlag) { + if (!$this->_preprocessFlag) { $variables['this'] = $this; } + if (isset($variables['subscriber']) && ($variables['subscriber'] instanceof Mage_Newsletter_Model_Subscriber)) { + $processor->setStoreId($variables['subscriber']->getStoreId()); + } + + if (!isset($variables['logo_url'])) { + $variables['logo_url'] = $this->_getLogoUrl($processor->getStoreId()); + } + if (!isset($variables['logo_alt'])) { + $variables['logo_alt'] = $this->_getLogoAlt($processor->getStoreId()); + } + $processor->setIncludeProcessor(array($this, 'getInclude')) ->setVariables($variables); @@ -327,9 +387,9 @@ public function getInclude($template, array $variables) /** * Send mail to recipient * - * @param string $email E-mail - * @param string|null $name receiver name - * @param array $variables template variables + * @param array|string $email E-mail(s) + * @param array|string|null $name receiver name(s) + * @param array $variables template variables * @return boolean **/ public function send($email, $name = null, array $variables = array()) @@ -339,12 +399,17 @@ public function send($email, $name = null, array $variables = array()) return false; } - if (is_null($name)) { - $name = substr($email, 0, strpos($email, '@')); + $emails = array_values((array)$email); + $names = is_array($name) ? $name : (array)$name; + $names = array_values($names); + foreach ($emails as $key => $email) { + if (!isset($names[$key])) { + $names[$key] = substr($email, 0, strpos($email, '@')); + } } - $variables['email'] = $email; - $variables['name'] = $name; + $variables['email'] = reset($emails); + $variables['name'] = reset($names); ini_set('SMTP', Mage::getStoreConfig('system/smtp/host')); ini_set('smtp_port', Mage::getStoreConfig('system/smtp/port')); @@ -365,15 +430,12 @@ public function send($email, $name = null, array $variables = array()) } if ($returnPathEmail !== null) { - $mail->setReturnPath($returnPathEmail); + $mailTransport = new Zend_Mail_Transport_Sendmail("-f".$returnPathEmail); + Zend_Mail::setDefaultTransport($mailTransport); } - if (is_array($email)) { - foreach ($email as $emailOne) { - $mail->addTo($emailOne, $name); - } - } else { - $mail->addTo($email, '=?utf-8?B?'.base64_encode($name).'?='); + foreach ($emails as $key => $email) { + $mail->addTo($email, '=?utf-8?B?' . base64_encode($names[$key]) . '?='); } $this->setUseAbsoluteLinks(true); @@ -385,7 +447,7 @@ public function send($email, $name = null, array $variables = array()) $mail->setBodyHTML($text); } - $mail->setSubject('=?utf-8?B?'.base64_encode($this->getProcessedTemplateSubject($variables)).'?='); + $mail->setSubject('=?utf-8?B?' . base64_encode($this->getProcessedTemplateSubject($variables)) . '?='); $mail->setFrom($this->getSenderEmail(), $this->getSenderName()); try { @@ -427,12 +489,12 @@ public function sendTransactional($templateId, $sender, $email, $name, $vars=arr } if (!$this->getId()) { - throw Mage::exception('Mage_Core', Mage::helper('core')->__('Invalid transactional email code: '.$templateId)); + throw Mage::exception('Mage_Core', Mage::helper('core')->__('Invalid transactional email code: ' . $templateId)); } if (!is_array($sender)) { - $this->setSenderName(Mage::getStoreConfig('trans_email/ident_'.$sender.'/name', $storeId)); - $this->setSenderEmail(Mage::getStoreConfig('trans_email/ident_'.$sender.'/email', $storeId)); + $this->setSenderName(Mage::getStoreConfig('trans_email/ident_' . $sender . '/name', $storeId)); + $this->setSenderEmail(Mage::getStoreConfig('trans_email/ident_' . $sender . '/email', $storeId)); } else { $this->setSenderName($sender['name']); $this->setSenderEmail($sender['email']); @@ -441,7 +503,6 @@ public function sendTransactional($templateId, $sender, $email, $name, $vars=arr if (!isset($vars['store'])) { $vars['store'] = Mage::app()->getStore($storeId); } - $this->setSentSuccess($this->send($email, $name, $vars)); return $this; } @@ -474,81 +535,6 @@ public function getProcessedTemplateSubject(array $variables) return $processedResult; } - /** - * Initialize design information for email template and subject processing - * - * @param array $config - * @return Mage_Core_Model_Email_Template - */ - public function setDesignConfig(array $config) - { - $this->getDesignConfig()->setData($config); - return $this; - } - - /** - * Get design configuration data - * - * @return Varien_Object - */ - public function getDesignConfig() - { - if(is_null($this->_designConfig)) { - $this->_designConfig = new Varien_Object(); - } - return $this->_designConfig; - } - - /** - * Apply declared configuration for design - * - * @return Mage_Core_Model_Email_Template - */ - protected function _applyDesignConfig() - { - if ($this->getDesignConfig()) { - $design = Mage::getDesign(); - $this->getDesignConfig() - ->setOldArea($design->getArea()) - ->setOldStore($design->getStore()); - - if ($this->getDesignConfig()->getArea()) { - Mage::getDesign()->setArea($this->getDesignConfig()->getArea()); - } - - if ($this->getDesignConfig()->getStore()) { - Mage::app()->getLocale()->emulate($this->getDesignConfig()->getStore()); - $design->setStore($this->getDesignConfig()->getStore()); - $design->setTheme(''); - $design->setPackageName(''); - } - - } - return $this; - } - - /** - * Revert design settings to previous - * - * @return Mage_Core_Model_Email_Template - */ - protected function _cancelDesignConfig() - { - if ($this->getDesignConfig()) { - if ($this->getDesignConfig()->getOldArea()) { - Mage::getDesign()->setArea($this->getDesignConfig()->getOldArea()); - } - - if ($this->getDesignConfig()->getOldStore()) { - Mage::getDesign()->setStore($this->getDesignConfig()->getOldStore()); - Mage::getDesign()->setTheme(''); - Mage::getDesign()->setPackageName(''); - } - } - Mage::app()->getLocale()->revert(); - return $this; - } - public function addBcc($bcc) { if (is_array($bcc)) { @@ -582,7 +568,7 @@ public function setReturnPath($email) */ public function setReplyTo($email) { - $this->getMail()->addHeader('Reply-To', $email); + $this->getMail()->setReplyTo($email); return $this; } diff --git a/app/code/core/Mage/Core/Model/Email/Template/Filter.php b/app/code/core/Mage/Core/Model/Email/Template/Filter.php index 214d5eb6..7a71a12f 100644 --- a/app/code/core/Mage/Core/Model/Email/Template/Filter.php +++ b/app/code/core/Mage/Core/Model/Email/Template/Filter.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Email/Template/Mailer.php b/app/code/core/Mage/Core/Model/Email/Template/Mailer.php new file mode 100644 index 00000000..803ca307 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Email/Template/Mailer.php @@ -0,0 +1,167 @@ + + */ +class Mage_Core_Model_Email_Template_Mailer extends Varien_Object +{ + /** + * List of email infos + * @see Mage_Core_Model_Email_Info + * + * @var array + */ + protected $_emailInfos = array(); + + /** + * Add new email info to corresponding list + * + * @param Mage_Core_Model_Email_Info $emailInfo + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function addEmailInfo(Mage_Core_Model_Email_Info $emailInfo) + { + array_push($this->_emailInfos, $emailInfo); + return $this; + } + + /** + * Send all emails from email list + * @see self::$_emailInfos + * + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function send() + { + $emailTemplate = Mage::getModel('core/email_template'); + // Send all emails from corresponding list + while (!empty($this->_emailInfos)) { + $emailInfo = array_pop($this->_emailInfos); + // Handle "Bcc" recepients of the current email + $emailTemplate->addBcc($emailInfo->getBccEmails()); + // Set required design parameters and delegate email sending to Mage_Core_Model_Email_Template + $emailTemplate->setDesignConfig(array('area' => 'frontend', 'store' => $this->getStoreId())) + ->sendTransactional( + $this->getTemplateId(), + $this->getSender(), + $emailInfo->getToEmails(), + $emailInfo->getToNames(), + $this->getTemplateParams(), + $this->getStoreId() + ); + } + return $this; + } + + /** + * Set email sender + * + * @param string|array $sender + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function setSender($sender) + { + return $this->setData('sender', $sender); + } + + /** + * Get email sender + * + * @return string|array|null + */ + public function getSender() + { + return $this->_getData('sender'); + } + + /** + * Set store id + * + * @param int $storeId + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function setStoreId($storeId) + { + return $this->setData('store_id', $storeId); + } + + /** + * Get store id + * + * @return int|null + */ + public function getStoreId() + { + return $this->_getData('store_id'); + } + + /** + * Set template id + * + * @param int $templateId + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function setTemplateId($templateId) + { + return $this->setData('template_id', $templateId); + } + + /** + * Get template id + * + * @return int|null + */ + public function getTemplateId() + { + return $this->_getData('template_id'); + } + + /** + * Set tempate parameters + * + * @param array $templateParams + * @return Mage_Core_Model_Email_Template_Mailer + */ + public function setTemplateParams(array $templateParams) + { + return $this->setData('template_params', $templateParams); + } + + /** + * Get template parameters + * + * @return array|null + */ + public function getTemplateParams() + { + return $this->_getData('template_params'); + } +} diff --git a/app/code/core/Mage/Core/Model/Email/Transport.php b/app/code/core/Mage/Core/Model/Email/Transport.php index a491eab9..08ff648a 100644 --- a/app/code/core/Mage/Core/Model/Email/Transport.php +++ b/app/code/core/Mage/Core/Model/Email/Transport.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Encryption.php b/app/code/core/Mage/Core/Model/Encryption.php index 17b0ca11..4de64a2c 100644 --- a/app/code/core/Mage/Core/Model/Encryption.php +++ b/app/code/core/Mage/Core/Model/Encryption.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/File/Storage.php b/app/code/core/Mage/Core/Model/File/Storage.php new file mode 100644 index 00000000..862117f7 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage.php @@ -0,0 +1,232 @@ + + */ +class Mage_Core_Model_File_Storage extends Mage_Core_Model_Abstract +{ + /** + * Storage systems ids + */ + const STORAGE_MEDIA_FILE_SYSTEM = 0; + const STORAGE_MEDIA_DATABASE = 1; + + /** + * Config pathes for storing storage configuration + */ + const XML_PATH_STORAGE_MEDIA = 'default/system/media_storage_configuration/media_storage'; + const XML_PATH_STORAGE_MEDIA_DATABASE = 'default/system/media_storage_configuration/media_database'; + const XML_PATH_MEDIA_RESOURCE_WHITELIST = 'default/system/media_storage_configuration/allowed_resources'; + const XML_PATH_MEDIA_UPDATE_TIME = 'system/media_storage_configuration/configuration_update_time'; + + + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage'; + + /** + * Show if there were errors while synchronize process + * + * @param Mage_Core_Model_Abstract $sourceModel + * @param Mage_Core_Model_Abstract $destinationModel + * @return bool + */ + protected function _synchronizeHasErrors(Mage_Core_Model_Abstract $sourceModel, + Mage_Core_Model_Abstract $destinationModel + ) { + if (!$sourceModel || !$destinationModel) { + return true; + } + + return $sourceModel->hasErrors() || $destinationModel->hasErrors(); + } + + /** + * Return synchronize process status flag + * + * @return Mage_Core_Model_File_Storage_Flag + */ + public function getSyncFlag() + { + return Mage::getSingleton('core/file_storage_flag')->loadSelf(); + } + + /** + * Retrieve storage model + * If storage not defined - retrieve current storage + * + * params = array( + * connection => string, - define connection for model if needed + * init => bool - force initialization process for storage model + * ) + * + * @param int|null $storage + * @param array $params + * @return Mage_Core_Model_Abstract|bool + */ + public function getStorageModel($storage = null, $params = array()) + { + if (is_null($storage)) { + $storage = Mage::helper('core/file_storage')->getCurrentStorageCode(); + } + + switch ($storage) { + case self::STORAGE_MEDIA_FILE_SYSTEM: + $model = Mage::getModel('core/file_storage_file'); + break; + case self::STORAGE_MEDIA_DATABASE: + $connection = (isset($params['connection'])) ? $params['connection'] : null; + $model = Mage::getModel('core/file_storage_database', array('connection' => $connection)); + break; + default: + return false; + } + + if (isset($params['init']) && $params['init']) { + $model->init(); + } + + return $model; + } + + /** + * Synchronize current media storage with defined + * $storage = array( + * type => int + * connection => string + * ) + * + * @param array $storage + * @return Mage_Core_Model_File_Storage + */ + public function synchronize($storage) + { + if (is_array($storage) && isset($storage['type'])) { + $storageDest = (int) $storage['type']; + $connection = (isset($storage['connection'])) ? $storage['connection'] : null; + $helper = Mage::helper('core/file_storage'); + + // if unable to sync to internal storage from itself + if ($storageDest == $helper->getCurrentStorageCode() && $helper->isInternalStorage()) { + return $this; + } + + $sourceModel = $this->getStorageModel(); + $destinationModel = $this->getStorageModel( + $storageDest, + array( + 'connection' => $connection, + 'init' => true + ) + ); + + if (!$sourceModel || !$destinationModel) { + return $this; + } + + $hasErrors = false; + $flag = $this->getSyncFlag(); + $flagData = array( + 'source' => $sourceModel->getStorageName(), + 'destination' => $destinationModel->getStorageName(), + 'destination_storage_type' => $storageDest, + 'destination_connection_name' => (string) $destinationModel->getConfigConnectionName(), + 'has_errors' => false, + 'timeout_reached' => false + ); + $flag->setFlagData($flagData); + + $destinationModel->clear(); + + $offset = 0; + while (($dirs = $sourceModel->exportDirectories($offset)) !== false) { + $flagData['timeout_reached'] = false; + if (!$hasErrors) { + $hasErrors = $this->_synchronizeHasErrors($sourceModel, $destinationModel); + if ($hasErrors) { + $flagData['has_errors'] = true; + } + } + + $flag->setFlagData($flagData) + ->save(); + + $destinationModel->importDirectories($dirs); + $offset += count($dirs); + } + unset($dirs); + + $offset = 0; + while (($files = $sourceModel->exportFiles($offset, 1)) !== false) { + $flagData['timeout_reached'] = false; + if (!$hasErrors) { + $hasErrors = $this->_synchronizeHasErrors($sourceModel, $destinationModel); + if ($hasErrors) { + $flagData['has_errors'] = true; + } + } + + $flag->setFlagData($flagData) + ->save(); + + $destinationModel->importFiles($files); + $offset += count($files); + } + unset($files); + } + + return $this; + } + + /** + * Return current media directory, allowed resources for get.php script, etc. + * + * @return array + */ + public static function getScriptConfig() + { + $config = array(); + $config['media_directory'] = Mage::getBaseDir('media'); + + $allowedResources = (array) Mage::app()->getConfig()->getNode(self::XML_PATH_MEDIA_RESOURCE_WHITELIST); + foreach ($allowedResources as $key => $allowedResource) { + $config['allowed_resources'][] = $allowedResource; + } + + $config['update_time'] = Mage::getStoreConfig(self::XML_PATH_MEDIA_UPDATE_TIME); + + return $config; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Abstract.php b/app/code/core/Mage/Core/Model/File/Storage/Abstract.php new file mode 100644 index 00000000..aec3a1d0 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Abstract.php @@ -0,0 +1,98 @@ + + */ +abstract class Mage_Core_Model_File_Storage_Abstract extends Mage_Core_Model_Abstract +{ + /** + * Store media base directory path + * + * @var string + */ + protected $_mediaBaseDirectory = null; + + /** + * Retrieve media base directory path + * + * @return string + */ + public function getMediaBaseDirectory() + { + if (null === $this->_mediaBaseDirectory) { + /** @var $helper Mage_Core_Helper_File_Storage_Database */ + $helper = Mage::helper('core/file_storage_database'); + $this->_mediaBaseDirectory = $helper->getMediaBaseDir(); + } + + return $this->_mediaBaseDirectory; + } + + /** + * Collect file info + * + * Return array( + * filename => string + * content => string|bool + * update_time => string + * directory => string + * ) + * + * @param string $path + * @return array + */ + public function collectFileInfo($path) + { + $path = ltrim($path, '\\/'); + $fullPath = $this->getMediaBaseDirectory() . DS . $path; + + if (!file_exists($fullPath) || !is_file($fullPath)) { + Mage::throwException(Mage::helper('core')->__('File %s does not exist', $fullPath)); + } + if (!is_readable($fullPath)) { + Mage::throwException(Mage::helper('core')->__('File %s is not readable', $fullPath)); + } + + $path = str_replace(array('/', '\\'), '/', $path); + $directory = dirname($path); + if ($directory == '.') { + $directory = null; + } + + return array( + 'filename' => basename($path), + 'content' => @file_get_contents($fullPath), + 'update_time' => Mage::getSingleton('core/date')->date(), + 'directory' => $directory + ); + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Database.php b/app/code/core/Mage/Core/Model/File/Storage/Database.php new file mode 100644 index 00000000..415ebeae --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Database.php @@ -0,0 +1,333 @@ + + */ +class Mage_Core_Model_File_Storage_Database extends Mage_Core_Model_File_Storage_Database_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_database'; + + /** + * Directory singleton + * + * @var Mage_Core_Model_File_Storage_Directory_Database + */ + protected $_directoryModel = null; + + /** + * Collect errors during sync process + * + * @var array + */ + protected $_errors = array(); + + /** + * Class construct + * + * @param string $connectionName + */ + public function __construct($connectionName = null) + { + $this->_init('core/file_storage_database'); + + parent::__construct($connectionName); + } + + /** + * Retrieve directory model + * + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function getDirectoryModel() + { + if (is_null($this->_directoryModel)) { + $this->_directoryModel = Mage::getModel( + 'core/file_storage_directory_database', + array('connection' => $this->getConnectionName())); + } + + return $this->_directoryModel; + } + + /** + * Create tables for file and directory storages + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function init() + { + $this->getDirectoryModel()->prepareStorage(); + $this->prepareStorage(); + + return $this; + } + + /** + * Return storage name + * + * @return string + */ + public function getStorageName() + { + return Mage::helper('core')->__('database "%s"', $this->getConnectionName()); + } + + /** + * Load object data by filename + * + * @param string $filePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function loadByFilename($filePath) + { + $filename = basename($filePath); + $path = dirname($filePath); + $this->_getResource()->loadByFilename($this, $filename, $path); + return $this; + } + + /** + * Check if there was errors during sync process + * + * @return bool + */ + public function hasErrors() + { + return (!empty($this->_errors) || $this->getDirectoryModel()->hasErrors()); + } + + /** + * Clear files and directories in storage + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function clear() + { + $this->getDirectoryModel()->clearDirectories(); + $this->_getResource()->clearFiles(); + return $this; + } + + /** + * Export directories from storage + * + * @param int $offset + * @param int $count + * @return bool|array + */ + public function exportDirectories($offset = 0, $count = 100) { + return $this->getDirectoryModel()->exportDirectories($offset, $count); + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function importDirectories($dirs) { + return $this->getDirectoryModel()->importDirectories($dirs); + } + + /** + * Export files list in defined range + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportFiles($offset = 0, $count = 100) + { + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + $result = $this->_getResource()->getFiles($offset, $count); + if (empty($result)) { + return false; + } + + return $result; + } + + /** + * Import files list + * + * @param array $files + * @return Mage_Core_Model_File_Storage_Database + */ + public function importFiles($files) + { + if (!is_array($files)) { + return $this; + } + + $dateSingleton = Mage::getSingleton('core/date'); + foreach ($files as $file) { + if (!isset($file['filename']) || !strlen($file['filename']) || !isset($file['content'])) { + continue; + } + + try { + $file['update_time'] = $dateSingleton->date(); + $file['directory_id'] = (isset($file['directory']) && strlen($file['directory'])) + ? Mage::getModel( + 'core/file_storage_directory_database', + array('connection' => $this->getConnectionName())) + ->loadByPath($file['directory'])->getId() + : null; + + $this->_getResource()->saveFile($file); + } catch (Exception $e) { + $this->_errors[] = $e->getMessage(); + Mage::logException($e); + } + } + + return $this; + } + + /** + * Store file into database + * + * @param string $filename + * @return Mage_Core_Model_File_Storage_Database + */ + public function saveFile($filename) + { + $fileInfo = $this->collectFileInfo($filename); + $filePath = $fileInfo['directory']; + + $directory = Mage::getModel('core/file_storage_directory_database')->loadByPath($filePath); + + if (!$directory->getId()) { + $directory = $this->getDirectoryModel()->createRecursive($filePath); + } + + $fileInfo['directory_id'] = $directory->getId(); + $this->_getResource()->saveFile($fileInfo); + + return $this; + } + + /** + * Check whether file exists in DB + * + * @param string $filePath + * @return bool + */ + public function fileExists($filePath) + { + return $this->_getResource()->fileExists(basename($filePath), dirname($filePath)); + } + + /** + * Copy files + * + * @param string $oldFilePath + * @param string $newFilePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function copyFile($oldFilePath, $newFilePath) + { + $this->_getResource()->copyFile( + basename($oldFilePath), + dirname($oldFilePath), + basename($newFilePath), + dirname($newFilePath) + ); + + return $this; + } + + /** + * Rename files in database + * + * @param string $oldFilePath + * @param string $newFilePath + * @return Mage_Core_Model_File_Storage_Database + */ + public function renameFile($oldFilePath, $newFilePath) + { + $this->_getResource()->renameFile( + basename($oldFilePath), + dirname($oldFilePath), + basename($newFilePath), + dirname($newFilePath) + ); + + $newPath = dirname($newFilePath); + $directory = Mage::getModel('core/file_storage_directory_database')->loadByPath($newPath); + + if (!$directory->getId()) { + $directory = $this->getDirectoryModel()->createRecursive($newPath); + } + + $this->loadByFilename($newFilePath); + if ($this->getId()) { + $this->setDirectoryId($directory->getId())->save(); + } + + return $this; + } + + /** + * Return directory listing + * + * @param string $directory + * @return mixed + */ + public function getDirectoryFiles($directory) + { + $directory = Mage::helper('core/file_storage_database')->getMediaRelativePath($directory); + return $this->_getResource()->getDirectoryFiles($directory); + } + + /** + * Delete file from database + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Database + */ + public function deleteFile($path) + { + $filename = basename($path); + $directory = dirname($path); + $this->_getResource()->deleteFile($filename, $directory); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php b/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php new file mode 100644 index 00000000..945789a0 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Database/Abstract.php @@ -0,0 +1,108 @@ + + */ +abstract class Mage_Core_Model_File_Storage_Database_Abstract extends Mage_Core_Model_File_Storage_Abstract +{ + /** + * Class construct + * + * @param string $databaseConnection + */ + public function __construct($params = array()) + { + $connectionName = (isset($params['connection'])) ? $params['connection'] : null; + if (empty($connectionName)) { + $connectionName = $this->getConfigConnectionName(); + } + + $this->setConnectionName($connectionName); + } + + /** + * Retrieve connection name saved at config + * + * @return string + */ + public function getConfigConnectionName() + { + $connectionName = (string) Mage::app()->getConfig() + ->getNode(Mage_Core_Model_File_Storage::XML_PATH_STORAGE_MEDIA_DATABASE); + if (empty($connectionName)) { + $connectionName = 'default_setup'; + } + + return $connectionName; + } + + /** + * Get resource instance + * + * @return Mage_Core_Model_Mysql4_Abstract + */ + protected function _getResource() + { + $resource = parent::_getResource(); + $resource->setConnectionName($this->getConnectionName()); + + return $resource; + } + + /** + * Prepare data storage + * + * @return Mage_Core_Model_File_Storage_Database + */ + public function prepareStorage() + { + $this->_getResource()->createDatabaseScheme(); + + return $this; + } + + /** + * Specify connection name + * + * @param $connectionName + * @return Mage_Core_Model_File_Storage_Database + */ + public function setConnectionName($connectionName) + { + if (!empty($connectionName)) { + $this->setData('connection_name', $connectionName); + $this->_getResource()->setConnectionName($connectionName); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php b/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php new file mode 100644 index 00000000..6f15823b --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Directory/Database.php @@ -0,0 +1,255 @@ + + */ +class Mage_Core_Model_File_Storage_Directory_Database extends Mage_Core_Model_File_Storage_Database_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_directory_database'; + + /** + * Collect errors during sync process + * + * @var array + */ + protected $_errors = array(); + + /** + * Class construct + * + * @param string $databaseConnection + */ + public function __construct($connectionName = null) + { + $this->_init('core/file_storage_directory_database'); + + parent::__construct($connectionName); + } + + /** + * Load object data by path + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function loadByPath($path) + { + /** + * Clear model data + * addData() is used because it's needed to clear only db storaged data + */ + $this->addData( + array( + 'directory_id' => null, + 'name' => null, + 'path' => null, + 'upload_time' => null, + 'parent_id' => null + ) + ); + + $this->_getResource()->loadByPath($this, $path); + return $this; + } + + /** + * Check if there was errors during sync process + * + * @return bool + */ + public function hasErrors() + { + return !empty($this->_errors); + } + + /** + * Retrieve directory parent id + * + * @return int + */ + public function getParentId() + { + if (!$this->getData('parent_id')) { + $parentId = $this->_getResource()->getParentId($this->getPath()); + if (empty($parentId)) { + $parentId = null; + } + + $this->setData('parent_id', $parentId); + } + + return $parentId; + } + + /** + * Create directories recursively + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function createRecursive($path) + { + $directory = Mage::getModel('core/file_storage_directory_database')->loadByPath($path); + + if (!$directory->getId()) { + $dirName = basename($path); + $dirPath = dirname($path); + + if ($dirPath != '.') { + $parentDir = $this->createRecursive($dirPath); + $parentId = $parentDir->getId(); + } else { + $dirPath = ''; + $parentId = null; + } + + $directory->setName($dirName); + $directory->setPath($dirPath); + $directory->setParentId($parentId); + $directory->save(); + } + + return $directory; + } + + /** + * Export directories from storage + * + * @param int $offset + * @param int $count + * @return bool + */ + public function exportDirectories($offset = 0, $count = 100) + { + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + $result = $this->_getResource()->exportDirectories($offset, $count); + + if (empty($result)) { + return false; + } + + return $result; + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function importDirectories($dirs) + { + if (!is_array($dirs)) { + return $this; + } + + $dateSingleton = Mage::getSingleton('core/date'); + foreach ($dirs as $dir) { + if (!is_array($dir) || !isset($dir['name']) || !strlen($dir['name'])) { + continue; + } + + try { + $directory = Mage::getModel( + 'core/file_storage_directory_database', + array('connection' => $this->getConnectionName()) + ); + $directory->setPath($dir['path']); + + $parentId = $directory->getParentId(); + if ($parentId || $dir['path'] == '') { + $directory->setName($dir['name']); + $directory->setUploadTime($dateSingleton->date()); + $directory->save(); + } else { + Mage::throwException(Mage::helper('core')->__('Parent directory does not exist: %s', $dir['path'])); + } + } catch (Exception $e) { + Mage::logException($e); + } + } + + return $this; + } + + /** + * Clean directories at storage + * + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function clearDirectories() + { + $this->_getResource()->clearDirectories(); + return $this; + } + + /** + * Return subdirectories + * + * @param string $directory + * @return mixed + */ + public function getSubdirectories($directory) + { + $directory = Mage::helper('core/file_storage_database')->getMediaRelativePath($directory); + + return $this->_getResource()->getSubdirectories($directory); + } + + /** + * Delete directory from database + * + * @param string $path + * @return Mage_Core_Model_File_Storage_Directory_Database + */ + public function deleteDirectory($dirPath) + { + $dirPath = Mage::helper('core/file_storage_database')->getMediaRelativePath($dirPath); + $name = basename($dirPath); + $path = dirname($dirPath); + + if ('.' == $path) { + $path = ''; + } + + $this->_getResource()->deleteDirectory($name, $path); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/File.php b/app/code/core/Mage/Core/Model/File/Storage/File.php new file mode 100644 index 00000000..5d63b384 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/File.php @@ -0,0 +1,275 @@ + + */ +class Mage_Core_Model_File_Storage_File extends Mage_Core_Model_File_Storage_Abstract +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'core_file_storage_file'; + + /** + * Data at storage + * + * @var array + */ + protected $_data = null; + + /** + * Collect errors during sync process + * + * @var array + */ + protected $_errors = array(); + + /** + * Class construct + */ + public function __construct() + { + $this->_init('core/file_storage_file'); + } + + /** + * Initialization + * + * @return Mage_Core_Model_File_Storage_File + */ + public function init() + { + return $this; + } + + /** + * Return storage name + * + * @return string + */ + public function getStorageName() + { + return Mage::helper('core')->__('File system'); + } + + /** + * Get files and directories from storage + * + * @return array + */ + public function getStorageData() + { + return $this->_getResource()->getStorageData(); + } + + /** + * Check if there was errors during sync process + * + * @return bool + */ + public function hasErrors() + { + return !empty($this->_errors); + } + + /** + * Clear files and directories in storage + * + * @return Mage_Core_Model_File_Storage_File + */ + public function clear() + { + $this->_getResource()->clear(); + return $this; + } + + /** + * Collect files and directories from storage + * + * @param int $offset + * @param int $count + * @param string $type + * @return array|bool + */ + public function collectData($offset = 0, $count = 100, $type = 'files') + { + if (!in_array($type, array('files', 'directories'))) { + return false; + } + + $offset = ((int) $offset >= 0) ? (int) $offset : 0; + $count = ((int) $count >= 1) ? (int) $count : 1; + + if (is_null($this->_data)) { + $this->_data = $this->getStorageData(); + } + + $slice = array_slice($this->_data[$type], $offset, $count); + if (empty($slice)) { + return false; + } + + return $slice; + } + + /** + * Export directories list from storage + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportDirectories($offset = 0, $count = 100) + { + return $this->collectData($offset, $count, 'directories'); + } + + /** + * Export files list in defined range + * + * @param int $offset + * @param int $count + * @return array|bool + */ + public function exportFiles($offset = 0, $count = 1) + { + $slice = $this->collectData($offset, $count, 'files'); + + if (!$slice) { + return false; + } + + $result = array(); + foreach ($slice as $fileName) { + try { + $fileInfo = $this->collectFileInfo($fileName); + } catch (Exception $e) { + Mage::logException($e); + continue; + } + + $result[] = $fileInfo; + } + + return $result; + } + + /** + * Import entities to storage + * + * @param array $data + * @param string $callback + * @return Mage_Core_Model_File_Storage_File + */ + public function import($data, $callback) + { + if (!is_array($data) || !method_exists($this, $callback)) { + return $this; + } + + foreach ($data as $part) { + try { + $this->$callback($part); + } catch (Exception $e) { + $this->_errors[] = $e->getMessage(); + Mage::logException($e); + } + } + + return $this; + } + + /** + * Import directories to storage + * + * @param array $dirs + * @return Mage_Core_Model_File_Storage_File + */ + public function importDirectories($dirs) + { + return $this->import($dirs, 'saveDir'); + } + + /** + * Import files list + * + * @param array $files + * @return Mage_Core_Model_File_Storage_File + */ + public function importFiles($files) + { + return $this->import($files, 'saveFile'); + } + + /** + * Save directory to storage + * + * @param array $dir + * @return bool + */ + public function saveDir($dir) + { + return $this->_getResource()->saveDir($dir); + } + + /** + * Save file to storage + * + * @param array|Mage_Core_Model_File_Storage_Database $file + * @param bool $overwrite + * @return bool|int + */ + public function saveFile($file, $overwrite = true) + { + if (isset($file['filename']) && !empty($file['filename']) + && isset($file['content']) && !empty($file['content']) + ) { + try { + $filename = (isset($file['directory']) && !empty($file['directory'])) + ? $file['directory'] . DS . $file['filename'] + : $file['filename']; + + return $this->_getResource() + ->saveFile($filename, $file['content'], $overwrite); + } catch (Exception $e) { + Mage::logException($e); + Mage::throwException(Mage::helper('core')->__('Unable to save file "%s" at "%s"', $file['filename'], $file['directory'])); + } + } else { + Mage::throwException(Mage::helper('core')->__('Wrong file info format')); + } + + return false; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Storage/Flag.php b/app/code/core/Mage/Core/Model/File/Storage/Flag.php new file mode 100644 index 00000000..e9418815 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Storage/Flag.php @@ -0,0 +1,82 @@ + + */ +class Mage_Core_Model_File_Storage_Flag extends Mage_Core_Model_Flag +{ + /** + * There was no synchronization + */ + const STATE_INACTIVE = 0; + /** + * Synchronize process is active + */ + const STATE_RUNNING = 1; + /** + * Synchronization finished + */ + const STATE_FINISHED = 2; + /** + * Synchronization finished and notify message was formed + */ + const STATE_NOTIFIED = 3; + + /** + * Flag time to life in seconds + */ + const FLAG_TTL = 300; + + /** + * Synchronize flag code + * + * @var string + */ + protected $_flagCode = 'synchronize'; + + /** + * Pass error to flag + * + * @param Exception $e + * @return Mage_Core_Model_File_Storage_Flag + */ + public function passError(Exception $e) + { + $data = $this->getFlagData(); + if (!is_array($data)) { + $data = array(); + } + $data['has_errors'] = true; + $this->setFlagData($data); + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/File/Uploader.php b/app/code/core/Mage/Core/Model/File/Uploader.php new file mode 100644 index 00000000..e99cb5ae --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Uploader.php @@ -0,0 +1,102 @@ + + */ +class Mage_Core_Model_File_Uploader extends Varien_File_Uploader +{ + /** + * Flag, that defines should DB processing be skipped + * + * @var bool + */ + protected $_skipDbProcessing = false; + + /** + * Save file to storage + * + * @param array $result + * @return Mage_Core_Model_File_Uploader + */ + protected function _afterSave($result) + { + if (empty($result['path']) || empty($result['file'])) { + return $this; + } + + /** @var $helper Mage_Core_Helper_File_Storage */ + $helper = Mage::helper('core/file_storage'); + + if ($helper->isInternalStorage() || $this->skipDbProcessing()) { + return $this; + } + + /** @var $dbHelper Mage_Core_Helper_File_Storage_Database */ + $dbHelper = Mage::helper('core/file_storage_database'); + $this->_result['file'] = $dbHelper->saveUploadedFile($result); + + return $this; + } + + /** + * Getter/Setter for _skipDbProcessing flag + * + * @param null|bool $flag + * @return bool|Mage_Core_Model_File_Uploader + */ + public function skipDbProcessing($flag = null) + { + if (is_null($flag)) { + return $this->_skipDbProcessing; + } + $this->_skipDbProcessing = (bool)$flag; + return $this; + } + + /** + * Check protected/allowed extension + * + * @param string $extension + * @return boolean + */ + public function checkAllowedExtension($extension) + { + //validate with protected file types + /** @var $validator Mage_Core_Model_File_Validator_NotProtectedExtension */ + $validator = Mage::getSingleton('core/file_validator_notProtectedExtension'); + if (!$validator->isValid($extension)) { + return false; + } + + return parent::checkAllowedExtension($extension); + } +} diff --git a/app/code/core/Mage/Core/Model/File/Validator/AvailablePath.php b/app/code/core/Mage/Core/Model/File/Validator/AvailablePath.php new file mode 100644 index 00000000..8525ffe0 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Validator/AvailablePath.php @@ -0,0 +1,324 @@ + + * //set available path + * $validator->setAvailablePath(array('/path/to/?/*fileMask.xml')); + * $validator->isValid('/path/to/MyDir/Some-fileMask.xml'); //return true + * $validator->setAvailablePath(array('/path/to/{@*}*.xml')); + * $validator->isValid('/path/to/my.xml'); //return true, because directory structure can't exist + * + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team + */ +class Mage_Core_Model_File_Validator_AvailablePath extends Zend_Validate_Abstract +{ + const PROTECTED_PATH = 'protectedPath'; + const NOT_AVAILABLE_PATH = 'notAvailablePath'; + const PROTECTED_LFI = 'protectedLfi'; + + /** + * The path + * + * @var string + */ + protected $_value; + + /** + * Protected paths + * + * @var array + */ + protected $_protectedPaths = array(); + + /** + * Available paths + * + * @var array + */ + protected $_availablePaths = array(); + + /** + * Cache of made regular expressions from path masks + * + * @var array + */ + protected $_pathsData; + + /** + * Construct + */ + public function __construct() + { + $this->_initMessageTemplates(); + } + + /** + * Initialize message templates with translating + * + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + protected function _initMessageTemplates() + { + if (!$this->_messageTemplates) { + $this->_messageTemplates = array( + self::PROTECTED_PATH => + Mage::helper('core')->__('Path "%value%" is protected and cannot be used.'), + self::NOT_AVAILABLE_PATH => + Mage::helper('core')->__('Path "%value%" is not available and cannot be used.'), + self::PROTECTED_LFI => + Mage::helper('core')->__('Path "%value%" may not include parent directory traversal ("../", "..\\").'), + ); + } + return $this; + } + + /** + * Set paths masks + * + * @param array $paths All paths masks types. + * E.g.: array('available' => array(...), 'protected' => array(...)) + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + public function setPaths(array $paths) + { + if (isset($paths['available']) && is_array($paths['available'])) { + $this->_availablePaths = $paths['available']; + } + if (isset($paths['protected']) && is_array($paths['protected'])) { + $this->_protectedPaths = $paths['protected']; + } + return $this; + } + + /** + * Set protected paths masks + * + * @param array $paths + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + public function setProtectedPaths(array $paths) + { + $this->_protectedPaths = $paths; + return $this; + } + + /** + * Add protected paths masks + * + * @param string|array $path + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + public function addProtectedPath($path) + { + if (is_array($path)) { + $this->_protectedPaths = array_merge($this->_protectedPaths, $path); + } else { + $this->_protectedPaths[] = $path; + } + return $this; + } + + /** + * Get protected paths masks + * + * @return array + */ + public function getProtectedPaths() + { + return $this->_protectedPaths; + } + + /** + * Set available paths masks + * + * @param array $paths + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + public function setAvailablePaths(array $paths) + { + $this->_availablePaths = $paths; + return $this; + } + + /** + * Add available paths mask + * + * @param string|array $path + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + public function addAvailablePath($path) + { + if (is_array($path)) { + $this->_availablePaths = array_merge($this->_availablePaths, $path); + } else { + $this->_availablePaths[] = $path; + } + return $this; + } + + /** + * Get available paths masks + * + * @return array + */ + public function getAvailablePaths() + { + return $this->_availablePaths; + } + + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @throws Exception Throw exception on empty both paths masks types + * @param string $value File/dir path + * @return bool + */ + public function isValid($value) + { + $value = trim($value); + $this->_setValue($value); + + if (!$this->_availablePaths && !$this->_protectedPaths) { + throw new Exception(Mage::helper('core')->__('Please set available and/or protected paths list(s) before validation.')); + } + + if (preg_match('#\.\.[\\\/]#', $this->_value)) { + $this->_error(self::PROTECTED_LFI, $this->_value); + return false; + } + + //validation + $value = str_replace(array('/', '\\'), DS, $this->_value); + $valuePathInfo = pathinfo(ltrim($value, '\\/')); + if ($valuePathInfo['dirname'] == '.' || $valuePathInfo['dirname'] == DS) { + $valuePathInfo['dirname'] = ''; + } + + if ($this->_protectedPaths && !$this->_isValidByPaths($valuePathInfo, $this->_protectedPaths, true)) { + $this->_error(self::PROTECTED_PATH, $this->_value); + return false; + } + if ($this->_availablePaths && !$this->_isValidByPaths($valuePathInfo, $this->_availablePaths, false)) { + $this->_error(self::NOT_AVAILABLE_PATH, $this->_value); + return false; + } + + return true; + } + + /** + * Validate value by path masks + * + * @param array $valuePathInfo Path info from value path + * @param array $paths Protected/available paths masks + * @param bool $protected Paths masks is protected? + * @return bool + */ + protected function _isValidByPaths($valuePathInfo, $paths, $protected) + { + foreach ($paths as $path) { + $path = ltrim($path, '\\/'); + if (!isset($this->_pathsData[$path]['regFilename'])) { + $pathInfo = pathinfo($path); + $options['file_mask'] = $pathInfo['basename']; + if ($pathInfo['dirname'] == '.' || $pathInfo['dirname'] == DS) { + $pathInfo['dirname'] = ''; + } else { + $pathInfo['dirname'] = str_replace(array('/', '\\'), DS, $pathInfo['dirname']); + } + $options['dir_mask'] = $pathInfo['dirname']; + $this->_pathsData[$path]['options'] = $options; + } else { + $options = $this->_pathsData[$path]['options']; + } + + //file mask + if (false !== (strpos($options['file_mask'], '*'))) { + if (!isset($this->_pathsData[$path]['regFilename'])) { + //make regular + $reg = $options['file_mask']; + $reg = str_replace('.', '\.', $reg); + $reg = str_replace('*', '.*?', $reg); + $reg = "/^($reg)$/"; + } else { + $reg = $this->_pathsData[$path]['regFilename']; + } + $resultFile = preg_match($reg, $valuePathInfo['basename']); + } else { + $resultFile = ($options['file_mask'] == $valuePathInfo['basename']); + } + + //directory mask + $reg = $options['dir_mask'] . DS; + if (!isset($this->_pathsData[$path]['regDir'])) { + //make regular + $reg = str_replace('.', '\.', $reg); + $reg = str_replace('*\\', '||', $reg); + $reg = str_replace('*/', '||', $reg); + //$reg = str_replace('*', '||', $reg); + $reg = str_replace(DS, '[\\' . DS . ']', $reg); + $reg = str_replace('?', '([^\\' . DS . ']+)', $reg); + $reg = str_replace('||', '(.*[\\' . DS . '])?', $reg); + $reg = "/^$reg$/"; + } else { + $reg = $this->_pathsData[$path]['regDir']; + } + $resultDir = preg_match($reg, $valuePathInfo['dirname'] . DS); + + if ($protected && ($resultDir && $resultFile)) { + return false; + } elseif (!$protected && ($resultDir && $resultFile)) { + //return true because one match with available path mask + return true; + } + } + if ($protected) { + return true; + } else { + //return false because no one match with available path mask + return false; + } + } +} diff --git a/app/code/core/Mage/Core/Model/File/Validator/NotProtectedExtension.php b/app/code/core/Mage/Core/Model/File/Validator/NotProtectedExtension.php new file mode 100644 index 00000000..ded259b9 --- /dev/null +++ b/app/code/core/Mage/Core/Model/File/Validator/NotProtectedExtension.php @@ -0,0 +1,122 @@ + + */ +class Mage_Core_Model_File_Validator_NotProtectedExtension extends Zend_Validate_Abstract +{ + const PROTECTED_EXTENSION = 'protectedExtension'; + + /** + * The file extension + * + * @var string + */ + protected $_value; + + /** + * Protected file types + * + * @var array + */ + protected $_protectedFileExtensions = array(); + + /** + * Construct + */ + public function __construct() + { + $this->_initMessageTemplates(); + $this->_initProtectedFileExtensions(); + } + + /** + * Initialize message templates with translating + * + * @return Mage_Core_Model_File_Validator_NotProtectedExtension + */ + protected function _initMessageTemplates() + { + if (!$this->_messageTemplates) { + $this->_messageTemplates = array( + self::PROTECTED_EXTENSION => Mage::helper('core')->__('File with an extension "%value%" is protected and cannot be uploaded'), + ); + } + return $this; + } + + /** + * Initialize protected file extensions + * + * @return Mage_Core_Model_File_Validator_NotProtectedExtension + */ + protected function _initProtectedFileExtensions() + { + if (!$this->_protectedFileExtensions) { + /** @var $helper Mage_Core_Helper_Data */ + $helper = Mage::helper('core'); + $extensions = $helper->getProtectedFileExtensions(); + if (is_string($extensions)) { + $extensions = explode(',', $extensions); + } + foreach ($extensions as &$ext) { + $ext = strtolower(trim($ext)); + } + $this->_protectedFileExtensions = (array) $extensions; + } + return $this; + } + + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param string $value Extension of file + * @return bool + */ + public function isValid($value) + { + $value = strtolower(trim($value)); + $this->_setValue($value); + + if (in_array($this->_value, $this->_protectedFileExtensions)) { + $this->_error(self::PROTECTED_EXTENSION, $this->_value); + return false; + } + + return true; + } +} diff --git a/app/code/core/Mage/Core/Model/Flag.php b/app/code/core/Mage/Core/Model/Flag.php index b095976a..8914ec71 100644 --- a/app/code/core/Mage/Core/Model/Flag.php +++ b/app/code/core/Mage/Core/Model/Flag.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,9 +28,18 @@ /** * Core Flag model * - * @category Mage - * @package Mage_Core - * @author Magento Core Team + * @method Mage_Core_Model_Resource_Flag _getResource() + * @method Mage_Core_Model_Resource_Flag getResource() + * @method string getFlagCode() + * @method Mage_Core_Model_Flag setFlagCode(string $value) + * @method int getState() + * @method Mage_Core_Model_Flag setState(int $value) + * @method string getLastUpdate() + * @method Mage_Core_Model_Flag setLastUpdate(string $value) + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team */ class Mage_Core_Model_Flag extends Mage_Core_Model_Abstract { diff --git a/app/code/core/Mage/Core/Model/Input/Filter.php b/app/code/core/Mage/Core/Model/Input/Filter.php new file mode 100644 index 00000000..2ceebf9e --- /dev/null +++ b/app/code/core/Mage/Core/Model/Input/Filter.php @@ -0,0 +1,366 @@ + + * /** @var $filter Mage_Core_Model_Input_Filter {@*} + * $filter = Mage::getModel('core/input_filter'); + * $filter->setFilters(array( + * 'list_values' => array( + * 'children_filters' => array( //filters will applied to all children + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8')), + * array('zend' => 'StripTags') + * ) + * ), + * 'list_values_with_name' => array( + * 'children_filters' => array( + * 'item1' => array( + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8'))), + * 'item2' => array( + * array('model' => 'core/input_filter_maliciousCode') + * ), + * 'item3' => array( + * array( + * 'helper' => 'core', + * 'method' => 'stripTags', + * 'args' => array('

    ', true)) + * ) + * ) + * ) + * )); + * $filter->addFilter('name2', new Zend_Filter_Alnum()); + * $filter->addFilter('name1', + * array( + * 'zend' => 'StringToUpper', + * 'args' => array('encoding' => 'utf-8'))); + * $filter->addFilter('name1', array('zend' => 'StripTags'), Zend_Filter::CHAIN_PREPEND); + * $filter->addFilters(protected $_filtersToAdd = array( + * 'list_values_with_name' => array( + * 'children_filters' => array( + * 'deep_list' => array( + * 'children_filters' => array( + * 'sub1' => array( + * array( + * 'zend' => 'StringToLower', + * 'args' => array('encoding' => 'utf-8'))), + * 'sub2' => array(array('zend' => 'Int')) + * ) + * ) + * ) + * ) + * )); + * $filter->filter(array( + * 'name1' => 'some string', + * 'name2' => '888 555', + * 'list_values' => array( + * 'some string2', + * 'some

    string3

    ', + * ), + * 'list_values_with_name' => array( + * 'item1' => 'some string4', + * 'item2' => 'some string5', + * 'item3' => 'some

    string5

    bold
    div
    ', + * 'deep_list' => array( + * 'sub1' => 'toLowString', + * 'sub2' => '5 TO INT', + * ) + * ) + * )); + * + * + * @see Mage_Core_Model_Input_FilterTest See this class for manual + * @category Mage + * @package Mage_Core + * @author Magento Api Team + */ +class Mage_Core_Model_Input_Filter implements Zend_Filter_Interface +{ + /** + * Filters data collectors + * + * @var array + */ + protected $_filters = array(); + + /** + * Add filter + * + * @param string $name + * @param array|Zend_Filter_Interface $filter + * @param string $placement + * @return Mage_Core_Model_Input_Filter + */ + public function addFilter($name, $filter, $placement = Zend_Filter::CHAIN_APPEND) + { + if ($placement == Zend_Filter::CHAIN_PREPEND) { + array_unshift($this->_filters[$name], $filter); + } else { + $this->_filters[$name][] = $filter; + } + return $this; + } + + /** + * Add a filter to the end of the chain + * + * @param array|Zend_Filter_Interface $filter + * @return Mage_Core_Model_Input_Filter + */ + public function appendFilter(Zend_Filter_Interface $filter) + { + return $this->addFilter($filter, Zend_Filter::CHAIN_APPEND); + } + + /** + * Add a filter to the start of the chain + * + * @param array|Zend_Filter_Interface $filter + * @return Mage_Core_Model_Input_Filter + */ + public function prependFilter($filter) + { + return $this->addFilter($filter, Zend_Filter::CHAIN_PREPEND); + } + + /** + * Add filters + * + * Filters data must be has view as + * array( + * 'key1' => $filters, + * 'key2' => array( ... ), //array filters data + * 'key2' => $filters + * ) + * + * @param array $filters + * @return Mage_Core_Model_Input_Filter + */ + public function addFilters(array $filters) + { + $this->_filters = array_merge_recursive($this->_filters, $filters); + return $this; + } + + /** + * Set filters + * + * @param array $filters + * @return Mage_Core_Model_Input_Filter + */ + public function setFilters(array $filters) + { + $this->_filters = $filters; + return $this; + } + + /** + * Get filters + * + * @param string|null $name Get filter for selected name + * @return array + */ + public function getFilters($name = null) + { + if (null === $name) { + return $this->_filters; + } else { + return isset($this->_filters[$name]) ? $this->_filters[$name] : null; + } + } + + /** + * Filter data + * + * @param array $data + * @return array Return filtered data + */ + public function filter($data) + { + return $this->_filter($data); + } + + /** + * Recursive filtering + * + * @param array $data + * @param array|null $filters + * @param bool $isFilterListSimple + * @return array + * @throws Exception Exception when filter is not found or not instance of defined instances + */ + protected function _filter(array $data, &$filters = null, $isFilterListSimple = false) + { + if (null === $filters) { + $filters = &$this->_filters; + } + foreach ($data as $key => $value) { + if (!$isFilterListSimple && !empty($filters[$key])) { + $itemFilters = $filters[$key]; + } elseif ($isFilterListSimple && !empty($filters)) { + $itemFilters = $filters; + } else { + continue; + } + + if (!$isFilterListSimple && is_array($value) && isset($filters[$key]['children_filters'])) { + $isChildrenFilterListSimple = is_numeric(implode('', array_keys($filters[$key]['children_filters']))); + $value = $this->_filter($value, $filters[$key]['children_filters'], $isChildrenFilterListSimple); + } else { + foreach ($itemFilters as $filterData) { + if ($zendFilter = $this->_getZendFilter($filterData)) { + $value = $zendFilter->filter($value); + } elseif ($filtrationHelper = $this->_getFiltrationHelper($filterData)) { + $value = $this->_applyFiltrationWithHelper($value, $filtrationHelper, $filterData); + } + } + } + $data[$key] = $value; + } + return $data; + } + + /** + * Call specified helper method for $value filtration + * + * @param mixed $value + * @param Mage_Core_Helper_Abstract $helper + * @param array $filterData + * @return mixed + */ + protected function _applyFiltrationWithHelper($value, Mage_Core_Helper_Abstract $helper, array $filterData) + { + if (!isset($filterData['method']) || empty($filterData['method'])) { + throw new Exception("Helper filtration method is not set"); + } + if (!isset($filterData['args']) || empty($filterData['args'])) { + $filterData['args'] = array(); + } + $filterData['args'] = array(-100 => $value) + $filterData['args']; + // apply filter + $value = call_user_func_array(array($helper, $filterData['method']), $filterData['args']); + return $value; + } + + /** + * Try to create Magento helper for filtration based on $filterData. Return false on failure + * + * @param $filterData + * @return bool|Mage_Core_Helper_Abstract + * @throws Exception + */ + protected function _getFiltrationHelper($filterData) + { + $helper = false; + if (isset($filterData['helper'])) { + $helper = $filterData['helper']; + if (is_string($helper)) { + $helper = Mage::helper($helper); + } + if (!($helper instanceof Mage_Core_Helper_Abstract)) { + throw new Exception("Filter '{$filterData['helper']}' not found"); + } + } + return $helper; + } + + /** + * Try to create Zend filter based on $filterData. Return false on failure + * + * @param $filterData + * @return bool|Zend_Filter_Interface + */ + protected function _getZendFilter($filterData) + { + $zendFilter = false; + if (is_object($filterData) && $filterData instanceof Zend_Filter_Interface) { + /** @var $zendFilter Zend_Filter_Interface */ + $zendFilter = $filterData; + } elseif (isset($filterData['model'])) { + $zendFilter = $this->_createCustomZendFilter($filterData); + } elseif (isset($filterData['zend'])) { + $zendFilter = $this->_createNativeZendFilter($filterData); + } + return $zendFilter; + } + + /** + * Get Magento filters + * + * @param $filterData + * @return Zend_Filter_Interface + * @throws Exception + */ + protected function _createCustomZendFilter($filterData) + { + $filter = $filterData['model']; + if (!isset($filterData['args'])) { + $filterData['args'] = null; + } else { + //use only first element because Mage factory cannot get more + $filterData['args'] = $filterData['args'][0]; + } + if (is_string($filterData['model'])) { + $filter = Mage::getModel($filterData['model'], $filterData['args']); + } + if (!($filter instanceof Zend_Filter_Interface)) { + throw new Exception('Filter is not instance of Zend_Filter_Interface'); + } + return $filter; + } + + /** + * Get native Zend_Filter + * + * @param $filterData + * @return Zend_Filter_Interface + * @throws Exception + */ + protected function _createNativeZendFilter($filterData) + { + $filter = $filterData['zend']; + if (is_string($filter)) { + $class = new ReflectionClass('Zend_Filter_' . $filter); + if ($class->implementsInterface('Zend_Filter_Interface')) { + if (isset($filterData['args']) && $class->hasMethod('__construct')) { + $filter = $class->newInstanceArgs($filterData['args']); + } else { + $filter = $class->newInstance(); + } + } else { + throw new Exception('Filter is not instance of Zend_Filter_Interface'); + } + } + return $filter; + } +} diff --git a/app/code/core/Mage/Core/Model/Input/Filter/MaliciousCode.php b/app/code/core/Mage/Core/Model/Input/Filter/MaliciousCode.php new file mode 100644 index 00000000..808a4db7 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Input/Filter/MaliciousCode.php @@ -0,0 +1,96 @@ + + */ +class Mage_Core_Model_Input_Filter_MaliciousCode implements Zend_Filter_Interface +{ + /** + * Regular expressions for cutting malicious code + * + * @var array + */ + protected $_expressions = array( + //comments, must be first + '/(\/\*.*\*\/)/Us', + //tabs + '/(\t)/', + //javasript prefix + '/(javascript\s*:)/Usi', + //import styles + '/(@import)/Usi', + //js in the style attribute + '/style=[^<]*((expression\s*?\([^<]*?\))|(behavior\s*:))[^<]*(?=\>)/Uis', + //js attributes + '/(ondblclick|onclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onload|onunload|onerror)=[^<]*(?=\>)/Uis', + //tags + '/<\/?(script|meta|link|frame|iframe).*>/Uis', + //base64 usage + '/src=[^<]*base64[^<]*(?=\>)/Uis', + ); + + /** + * Filter value + * + * @param string|array $value + * @return string|array Filtered value + */ + public function filter($value) + { + return preg_replace($this->_expressions, '', $value); + } + + /** + * Add expression + * + * @param string $expression + * @return Mage_Core_Model_Input_Filter_MaliciousCode + */ + public function addExpression($expression) + { + if (!in_array($expression, $this->_expressions)) { + $this->_expressions[] = $expression; + } + return $this; + } + + /** + * Set expressions + * + * @param array $expressions + * @return Mage_Core_Model_Input_Filter_MaliciousCode + */ + public function setExpressions(array $expressions) + { + $this->_expressions = $expressions; + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Language.php b/app/code/core/Mage/Core/Model/Language.php index 0275cf8b..77b0c653 100644 --- a/app/code/core/Mage/Core/Model/Language.php +++ b/app/code/core/Mage/Core/Model/Language.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Layout.php b/app/code/core/Mage/Core/Model/Layout.php index c9eb72e9..b22de782 100644 --- a/app/code/core/Mage/Core/Model/Layout.php +++ b/app/code/core/Mage/Core/Model/Layout.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -37,7 +37,7 @@ class Mage_Core_Model_Layout extends Varien_Simplexml_Config /** * Layout Update module * - * @var Mage_Core_Layout_Update + * @var Mage_Core_Model_Layout_Update */ protected $_update; @@ -170,7 +170,8 @@ public function generateXml() if ($block->getAttribute('ignore') !== null) { continue; } - if (($acl = (string)$attributes->acl) && Mage::getSingleton('admin/session')->isAllowed($acl)) { + $acl = (string)$attributes->acl; + if ($acl && Mage::getSingleton('admin/session')->isAllowed($acl)) { continue; } if (!isset($block->attributes()->ignore)) { @@ -361,13 +362,38 @@ protected function _generateAction($node, $parent) protected function _translateLayoutNode($node, &$args) { if (isset($node['translate'])) { - $items = explode(' ', (string)$node['translate']); - foreach ($items as $arg) { - if (isset($node['module'])) { - $args[$arg] = Mage::helper((string)$node['module'])->__($args[$arg]); + // Translate value by core module if module attribute was not set + $moduleName = (isset($node['module'])) ? (string)$node['module'] : 'core'; + + // Handle translations in arrays if needed + $translatableArguments = explode(' ', (string)$node['translate']); + foreach ($translatableArguments as $translatableArgumentName) { + /* + * .(dot) character is used as a path separator in nodes hierarchy + * e.g. info.title means that Magento needs to translate value of node + * that is a child of <info> node + */ + // @var $argumentHierarhy array - path to translatable item in $args array + $argumentHierarchy = explode('.', $translatableArgumentName); + $argumentStack = &$args; + $canTranslate = true; + while (is_array($argumentStack) && count($argumentStack) > 0) { + $argumentName = array_shift($argumentHierarchy); + if (isset($argumentStack[$argumentName])) { + /* + * Move to the next element in arguments hieracrhy + * in order to find target translatable argument + */ + $argumentStack = &$argumentStack[$argumentName]; + } else { + // Target argument cannot be found + $canTranslate = false; + break; + } } - else { - $args[$arg] = Mage::helper('core')->__($args[$arg]); + if ($canTranslate && is_string($argumentStack)) { + // $argumentStack is now a reference to target translatable argument so it can be translated + $argumentStack = Mage::helper($moduleName)->__($argumentStack); } } } @@ -401,7 +427,7 @@ public function unsetBlock($name) * Block Factory * * @param string $type - * @param string $blockName + * @param string $name * @param array $attributes * @return Mage_Core_Block_Abstract */ diff --git a/app/code/core/Mage/Core/Model/Layout/Data.php b/app/code/core/Mage/Core/Model/Layout/Data.php index 1b20c2b3..a674e3d7 100644 --- a/app/code/core/Mage/Core/Model/Layout/Data.php +++ b/app/code/core/Mage/Core/Model/Layout/Data.php @@ -20,11 +20,27 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Core_Model_Resource_Layout _getResource() + * @method Mage_Core_Model_Resource_Layout getResource() + * @method string getHandle() + * @method Mage_Core_Model_Layout_Data setHandle(string $value) + * @method string getXml() + * @method Mage_Core_Model_Layout_Data setXml(string $value) + * @method int getSortOrder() + * @method Mage_Core_Model_Layout_Data setSortOrder(int $value) + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ class Mage_Core_Model_Layout_Data extends Mage_Core_Model_Abstract { protected function _construct() diff --git a/app/code/core/Mage/Core/Model/Layout/Element.php b/app/code/core/Mage/Core/Model/Layout/Element.php index ae6095a5..d9c727ab 100644 --- a/app/code/core/Mage/Core/Model/Layout/Element.php +++ b/app/code/core/Mage/Core/Model/Layout/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Layout/Update.php b/app/code/core/Mage/Core/Model/Layout/Update.php index 034b270b..ae168d42 100644 --- a/app/code/core/Mage/Core/Model/Layout/Update.php +++ b/app/code/core/Mage/Core/Model/Layout/Update.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -259,7 +259,9 @@ public function fetchFileLayoutUpdates() $storeId = Mage::app()->getStore()->getId(); $elementClass = $this->getElementClass(); $design = Mage::getSingleton('core/design_package'); - $cacheKey = 'LAYOUT_'.$design->getArea().'_STORE'.$storeId.'_'.$design->getPackageName().'_'.$design->getTheme('layout'); + $cacheKey = 'LAYOUT_' . $design->getArea() . '_STORE' . $storeId . '_' . $design->getPackageName() . '_' + . $design->getTheme('layout'); + $cacheTags = array(self::LAYOUT_GENERAL_CACHE_TAG); if (Mage::app()->useCache('layout') && ($layoutStr = Mage::app()->loadCache($cacheKey))) { $this->_packageLayout = simplexml_load_string($layoutStr, $elementClass); @@ -358,7 +360,7 @@ public function fetchDbLayoutUpdates($handle) { $_profilerKey = 'layout/db_update: '.$handle; Varien_Profiler::start($_profilerKey); - $updateStr = Mage::getResourceModel('core/layout')->fetchUpdatesByHandle($handle); + $updateStr = $this->_getUpdateString($handle); if (!$updateStr) { return false; } @@ -372,6 +374,17 @@ public function fetchDbLayoutUpdates($handle) return true; } + /** + * Get update string + * + * @param string $handle + * @return mixed + */ + protected function _getUpdateString($handle) + { + return Mage::getResourceModel('core/layout')->fetchUpdatesByHandle($handle); + } + public function fetchRecursiveUpdates($updateXml) { foreach ($updateXml->children() as $child) { diff --git a/app/code/core/Mage/Core/Model/Locale.php b/app/code/core/Mage/Core/Model/Locale.php index 2c70899f..a3cf908a 100644 --- a/app/code/core/Mage/Core/Model/Locale.php +++ b/app/code/core/Mage/Core/Model/Locale.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -43,6 +43,9 @@ class Mage_Core_Model_Locale */ const XML_PATH_DEFAULT_LOCALE = 'general/locale/code'; const XML_PATH_DEFAULT_TIMEZONE = 'general/locale/timezone'; + /** + * @deprecated since 1.4.1.0 + */ const XML_PATH_DEFAULT_COUNTRY = 'general/country/default'; const XML_PATH_ALLOW_CODES = 'global/locale/allow/codes'; const XML_PATH_ALLOW_CURRENCIES = 'global/locale/allow/currencies'; @@ -196,6 +199,7 @@ public function getLocaleCode() public function setLocaleCode($code) { $this->_localeCode = $code; + $this->_locale = null; return $this; } @@ -412,6 +416,18 @@ public function getDateFormat($type=null) return $this->getTranslation($type, 'date'); } + /** + * Retrieve short date format with 4-digit year + * + * @return string + */ + public function getDateFormatWithLongYear() + { + return preg_replace('/(?<!y)yy(?!y)/', 'yyyy', + $this->getTranslation(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT, 'date')); + } + + /** * Retrieve ISO time format * @@ -459,25 +475,28 @@ public function getTimeStrFormat($type) /** * Create Zend_Date object for current locale * - * @param mixed $date - * @param string $part - * @return Zend_Date - * @exception Zend_Date_Exception + * @param mixed $date + * @param string $part + * @param string|Zend_Locale $locale + * @param bool $useTimezone + * @return Zend_Date */ - public function date($date=null, $part=null, $locale=null, $useTimezone=true) + public function date($date = null, $part = null, $locale = null, $useTimezone = true) { if (is_null($locale)) { $locale = $this->getLocale(); } - // try-catch block was here + if (empty($date)) { + // $date may be false, but Zend_Date uses strict compare + $date = null; + } $date = new Zend_Date($date, $part, $locale); if ($useTimezone) { if ($timezone = Mage::app()->getStore()->getConfig(self::XML_PATH_DEFAULT_TIMEZONE)) { $date->setTimezone($timezone); } } - //$date->add(-(substr($date->get(Zend_Date::GMT_DIFF), 0,3)), Zend_Date::HOUR); return $date; } @@ -549,18 +568,23 @@ public function currency($currency) { Varien_Profiler::start('locale/currency'); if (!isset(self::$_currencyCache[$this->getLocaleCode()][$currency])) { + $options = array(); try { $currencyObject = new Zend_Currency($currency, $this->getLocale()); } catch (Exception $e) { $currencyObject = new Zend_Currency($this->getCurrency(), $this->getLocale()); - $options = array( - 'name' => $currency, - 'currency' => $currency, - 'symbol' => $currency - ); - $currencyObject->setFormat($options); + $options['name'] = $currency; + $options['currency'] = $currency; + $options['symbol'] = $currency; } + $options = new Varien_Object($options); + Mage::dispatchEvent('currency_display_options_forming', array( + 'currency_options' => $options, + 'base_code' => $currency + )); + + $currencyObject->setFormat($options->toArray()); self::$_currencyCache[$this->getLocaleCode()][$currency] = $currencyObject; } Varien_Profiler::stop('locale/currency'); @@ -581,8 +605,8 @@ public function currency($currency) * '2'054.52' = 2054.52 * '2,46 GB' = 2.46 * - * @param string|int $value - * @return float + * @param string|float|int $value + * @return float|null */ public function getNumber($value) { @@ -594,9 +618,8 @@ public function getNumber($value) return floatval($value); } - //trim space and apos - $value = str_replace('\'', '', $value); - $value = str_replace(' ', '', $value); + //trim spaces and apostrophes + $value = str_replace(array('\'', ' '), '', $value); $separatorComa = strpos($value, ','); $separatorDot = strpos($value, '.'); @@ -615,11 +638,10 @@ public function getNumber($value) } return floatval($value); - //return Zend_Locale_Format::getNumber($value, array('locale' => $this->getLocaleCode())); } /** - * Functions returns array with price formating info for js function + * Functions returns array with price formatting info for js function * formatCurrency in js/varien/js.js * * @return array diff --git a/app/code/core/Mage/Core/Model/Locale/Config.php b/app/code/core/Mage/Core/Model/Locale/Config.php index 5a104484..56ee499b 100644 --- a/app/code/core/Mage/Core/Model/Locale/Config.php +++ b/app/code/core/Mage/Core/Model/Locale/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Core_Model_Locale_Config @@ -58,7 +58,7 @@ class Mage_Core_Model_Locale_Config 'th_TH' /*Thai (Thailand)*/, 'tr_TR' /*Turkish (Turkey)*/, 'uk_UA' /*Ukrainian (Ukraine)*/, 'vi_VN' /*Vietnamese (Vietnam)*/, 'zh_CN' /*Chinese (China)*/, 'zh_HK' /*Chinese (Hong Kong SAR)*/, 'zh_TW' /*Chinese (Taiwan)*/, 'es_CL' /*Spanich (Chile)*/, 'lo_LA' /*Laotian*/, - 'es_VE' /*Spanish (Venezuela)*/, + 'es_VE' /*Spanish (Venezuela)*/, 'en_IE' /*English (Ireland)*/, ); /** @@ -108,7 +108,8 @@ class Mage_Core_Model_Locale_Config 'UYU' /*Uruguay Peso Uruguayo*/,'UZS' /*Uzbekistan Sum*/,'VUV' /*Vanuatu Vatu*/, 'VEB' /*Venezuelan Bolivar*/, 'VEF' /*Venezuelan bolívar fuerte*/,'VND' /*Vietnamese Dong*/, 'CHE' /*WIR Euro*/, 'CHW' /*WIR Franc*/, 'XOF' /*West African CFA franc*/,'WST' /*Western Samoa Tala*/,'YER' /*Yemeni Rial*/, 'ZMK' /*Zambian Kwacha*/, - 'ZWD' /*Zimbabwe Dollar*/, + 'ZWD' /*Zimbabwe Dollar*/,'TRY' /*Turkish Lira*/,'AZM' /*Azerbaijani Manat (1993-2006)*/, 'ROL' /*Old Romanian Leu*/, + 'TRL' /*Old Turkish Lira*/,'XPF' /*CFP Franc*/ ); /** diff --git a/app/code/core/Mage/Core/Model/Log/Adapter.php b/app/code/core/Mage/Core/Model/Log/Adapter.php index 77916e9c..599b53ae 100644 --- a/app/code/core/Mage/Core/Model/Log/Adapter.php +++ b/app/code/core/Mage/Core/Model/Log/Adapter.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Magento/Api.php b/app/code/core/Mage/Core/Model/Magento/Api.php new file mode 100644 index 00000000..ba292f3c --- /dev/null +++ b/app/code/core/Mage/Core/Model/Magento/Api.php @@ -0,0 +1,49 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Magento info API + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Magento_Api extends Mage_Api_Model_Resource_Abstract +{ + /** + * Retrieve information about current Magento installation + * + * @return array + */ + public function info() + { + $result = array(); + $result['magento_edition'] = Mage::getEdition(); + $result['magento_version'] = Mage::getVersion(); + + return $result; + } +} diff --git a/app/code/core/Mage/Core/Model/Magento/Api/V2.php b/app/code/core/Mage/Core/Model/Magento/Api/V2.php new file mode 100644 index 00000000..a5ff04ab --- /dev/null +++ b/app/code/core/Mage/Core/Model/Magento/Api/V2.php @@ -0,0 +1,36 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Magento info API V2 + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Magento_Api_V2 extends Mage_Core_Model_Magento_Api +{ +} diff --git a/app/code/core/Mage/Core/Model/Message.php b/app/code/core/Mage/Core/Model/Message.php index 88bd810f..6597b1d9 100644 --- a/app/code/core/Mage/Core/Model/Message.php +++ b/app/code/core/Mage/Core/Model/Message.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Message/Abstract.php b/app/code/core/Mage/Core/Model/Message/Abstract.php index 1347bbfa..2b009cd0 100644 --- a/app/code/core/Mage/Core/Model/Message/Abstract.php +++ b/app/code/core/Mage/Core/Model/Message/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -120,4 +120,16 @@ public function getIsSticky() { return $this->_isSticky; } + + /** + * Set code + * + * @param string $code + * @return Mage_Core_Model_Message_Abstract + */ + public function setCode($code) + { + $this->_code = $code; + return $this; + } } diff --git a/app/code/core/Mage/Core/Model/Message/Collection.php b/app/code/core/Mage/Core/Model/Message/Collection.php index a830014c..898900f7 100644 --- a/app/code/core/Mage/Core/Model/Message/Collection.php +++ b/app/code/core/Mage/Core/Model/Message/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Message/Error.php b/app/code/core/Mage/Core/Model/Message/Error.php index bd6525a2..ea618749 100644 --- a/app/code/core/Mage/Core/Model/Message/Error.php +++ b/app/code/core/Mage/Core/Model/Message/Error.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Message/Notice.php b/app/code/core/Mage/Core/Model/Message/Notice.php index 54460939..527c5305 100644 --- a/app/code/core/Mage/Core/Model/Message/Notice.php +++ b/app/code/core/Mage/Core/Model/Message/Notice.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Message/Success.php b/app/code/core/Mage/Core/Model/Message/Success.php index 2e8722b9..16b1039c 100644 --- a/app/code/core/Mage/Core/Model/Message/Success.php +++ b/app/code/core/Mage/Core/Model/Message/Success.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Message/Warning.php b/app/code/core/Mage/Core/Model/Message/Warning.php index 234fd68f..ef1ac3c1 100644 --- a/app/code/core/Mage/Core/Model/Message/Warning.php +++ b/app/code/core/Mage/Core/Model/Message/Warning.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Mysql4/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/Abstract.php index 6f2aeaf9..67b5d37c 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Abstract.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,711 +28,10 @@ /** * Abstract resource model class * - * * @category Mage * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -abstract class Mage_Core_Model_Mysql4_Abstract extends Mage_Core_Model_Resource_Abstract +abstract class Mage_Core_Model_Mysql4_Abstract extends Mage_Core_Model_Resource_Db_Abstract { - const CHECKSUM_KEY_NAME = 'Checksum'; - - /** - * Cached resources singleton - * - * @var Mage_Core_Model_Resource - */ - protected $_resources; - - /** - * Prefix for resources that will be used in this resource model - * - * @var string - */ - protected $_resourcePrefix; - - /** - * Connections cache for this resource model - * - * @var array - */ - protected $_connections = array(); - - /** - * Resource model name that contains entities (names of tables) - * - * @var string - */ - protected $_resourceModel; - - /** - * Tables used in this resource model - * - * @var array - */ - protected $_tables = array(); - - /** - * Main table name - * - * @var string - */ - protected $_mainTable; - - /** - * Main table primary key field name - * - * @var string - */ - protected $_idFieldName; - - /** - * Primery key auto increment flag - * - * @var bool - */ - protected $_isPkAutoIncrement = true; - - /** - * Use is object new method for save of object - * - * @var boolean - */ - protected $_useIsObjectNew = false; - - /** - * Fields List for update in forsedSave - * - * @var array - */ - protected $_fieldsForUpdate = array(); - - protected $_mainTableFields; - - /** - * Main table unique keys field names - * - * could array( - * array('field' => 'db_field_name1', 'title' => 'Field 1 should be unique') - * array('field' => 'db_field_name2', 'title' => 'Field 2 should be unique') - * array( - * 'field' => array('db_field_name3', 'db_field_name3'), - * 'title' => 'Field 3 and Field 4 combination should be unique' - * ) - * ) - * - * or string 'my_field_name' - will be autoconverted to - * array( array( 'field' => 'my_field_name', 'title' => 'my_field_name' ) ) - * - * @var array - */ - protected $_uniqueFields = null; - - /** - * Serializable fields declaration - * - * Structure: array( - * <field_name> => array( - * <default_value_for_serialization>, - * <default_for_unserialization>, - * <whether_to_unset_empty_when serializing> // optional parameter - * ), - * ) - * - * @var array - */ - protected $_serializableFields = array(); - - /** - * Standard resource model initialization - * - * @param string $mainTable - * @param string $idFieldName - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _init($mainTable, $idFieldName) - { - $this->_setMainTable($mainTable, $idFieldName); - } - - /** - * Initialize connections and tables for this resource model - * - * If one or both arguments are string, will be used as prefix - * If $tables is null and $connections is string, $tables will be the same - * - * @param string|array $connections - * @param string|array|null $tables - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _setResource($connections, $tables=null) - { - $this->_resources = Mage::getSingleton('core/resource'); - - if (is_array($connections)) { - foreach ($connections as $k=>$v) { - $this->_connections[$k] = $this->_resources->getConnection($v); - } - } - elseif (is_string($connections)) { - $this->_resourcePrefix = $connections; - } - - if (is_null($tables) && is_string($connections)) { - $this->_resourceModel = $this->_resourcePrefix; - } - elseif (is_array($tables)) { - foreach ($tables as $k=>$v) { - $this->_tables[$k] = $this->_resources->getTableName($v); - } - } - elseif (is_string($tables)) { - $this->_resourceModel = $tables; - } - return $this; - } - - /** - * Set main entity table name and primary key field name - * - * If field name is ommited {table_name}_id will be used - * - * @param string $mainTable - * @param string|null $idFieldName - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _setMainTable($mainTable, $idFieldName=null) - { - $mainTableArr = explode('/', $mainTable); - - if (!empty($mainTableArr[1])) { - if (empty($this->_resourceModel)) { - $this->_setResource($mainTableArr[0]); - } - $this->_setMainTable($mainTableArr[1], $idFieldName); - } else { - $this->_mainTable = $mainTable; - if (is_null($idFieldName)) { - $idFieldName = $mainTable.'_id'; - } - $this->_idFieldName = $idFieldName; - } - - return $this; - } - - /** - * Get primary key field name - * - * @return string - */ - public function getIdFieldName() - { - if (empty($this->_idFieldName)) { - Mage::throwException(Mage::helper('core')->__('Empty identifier field name')); - } - return $this->_idFieldName; - } - - /** - * Get main table name - * - * @return string - */ - public function getMainTable() - { - if (empty($this->_mainTable)) { - Mage::throwException(Mage::helper('core')->__('Empty main table name')); - } - return $this->getTable($this->_mainTable); - } - - /** - * Get table name for the entity - * - * @param string $entityName - * @return string - */ - public function getTable($entityName) - { - if (isset($this->_tables[$entityName])) { - return $this->_tables[$entityName]; - } - if (strpos($entityName, '/')) { - $this->_tables[$entityName] = $this->_resources->getTableName($entityName); - } elseif (!empty($this->_resourceModel)) { - $this->_tables[$entityName] = $this->_resources->getTableName( - $this->_resourceModel.'/'.$entityName); - } else { - $this->_tables[$entityName] = $entityName; - } - return $this->_tables[$entityName]; - } - - /** - * Retrieve table name for the entity separated value - * - * @param string $entityName - * @param string $valueType - * @return string - */ - public function getValueTable($entityName, $valueType) - { - return $this->getTable($entityName) . '_' . $valueType; - } - - /** - * Get connection by name or type - * - * @param string $connectionName - * @return Zend_Db_Adapter_Abstract - */ - protected function _getConnection($connectionName) - { - if (isset($this->_connections[$connectionName])) { - return $this->_connections[$connectionName]; - } - if (!empty($this->_resourcePrefix)) { - $this->_connections[$connectionName] = $this->_resources->getConnection( - $this->_resourcePrefix.'_'.$connectionName); - } else { - $this->_connections[$connectionName] = $this->_resources->getConnection($connectionName); - } - - return $this->_connections[$connectionName]; - } - - /** - * Retrieve connection for read data - * - * @return Varien_Db_Adapter_Pdo_Mysql - */ - protected function _getReadAdapter() - { - return $this->_getConnection('read'); - } - - /** - * Retrieve connection for write data - * - * @return Varien_Db_Adapter_Pdo_Mysql - */ - protected function _getWriteAdapter() - { - return $this->_getConnection('write'); - } - - /** - * Temporary resolving collection compatibility - * - * @return Varien_Db_Adapter_Pdo_Mysql - */ - public function getReadConnection() - { - return $this->_getReadAdapter(); - } - - /** - * Load an object - * - * @param Mage_Core_Model_Abstract $object - * @param mixed $value - * @param string $field field to load by (defaults to model id) - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - if (is_null($field)) { - $field = $this->getIdFieldName(); - } - - $read = $this->_getReadAdapter(); - if ($read && !is_null($value)) { - $select = $this->_getLoadSelect($field, $value, $object); - $data = $read->fetchRow($select); - - if ($data) { - $object->setData($data); - } - } - - $this->unserializeFields($object); - $this->_afterLoad($object); - - return $this; - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @return Zend_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where($this->getMainTable().'.'.$field.'=?', $value); - return $select; - } - - /** - * Save object object data - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function save(Mage_Core_Model_Abstract $object) - { - if ($object->isDeleted()) { - return $this->delete($object); - } - - $this->_serializeFields($object); - $this->_beforeSave($object); - $this->_checkUnique($object); - - if (!is_null($object->getId()) && (!$this->_useIsObjectNew || !$object->isObjectNew())) { - $condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId()); - /** - * Not auto increment primary key support - */ - if ($this->_isPkAutoIncrement) { - $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition); - } else { - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainTable(), array($this->getIdFieldName())) - ->where($condition); - if ($this->_getWriteAdapter()->fetchOne($select) !== false) { - $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition); - } else { - $this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object)); - } - } - } else { - $this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object)); - $object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable())); - if ($this->_useIsObjectNew) { - $object->isObjectNew(false); - } - } - - $this->unserializeFields($object); - $this->_afterSave($object); - - return $this; - } - - /** - * Forsed save object data - * forsed update If duplicate unique key data - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function forsedSave(Mage_Core_Model_Abstract $object) - { - $this->_beforeSave($object); - - // update - if (!is_null($object->getId()) && $this->_isPkAutoIncrement) { - $condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId()); - $this->_getWriteAdapter()->update($this->getMainTable(), $this->_prepareDataForSave($object), $condition); - } - else { - $this->_getWriteAdapter()->insertOnDuplicate($this->getMainTable(), $this->_prepareDataForSave($object), $this->_fieldsForUpdate); - $object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable())); - } - - $this->_afterSave($object); - - return $this; - } - - /** - * Delete the object - * - * @param Varien_Object $object - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function delete(Mage_Core_Model_Abstract $object) - { - $this->_beforeDelete($object); - $this->_getWriteAdapter()->delete( - $this->getMainTable(), - $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId()) - ); - $this->_afterDelete($object); - return $this; - } - - /** - * Add unique field restriction - * - * @param array|string $field - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function addUniqueField($field) - { - if (is_null($this->_uniqueFields)) { - $this->_initUniqueFields(); - } - if(is_array($this->_uniqueFields) ) { - $this->_uniqueFields[] = $field; - } - return $this; - } - - /** - * Reset unique fields restrictions - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function resetUniqueField() - { - $this->_uniqueFields = array(); - return $this; - } - - /** - * Unserialize serializeable object fields - * - * @param Mage_Core_Model_Abstract $object - */ - public function unserializeFields(Mage_Core_Model_Abstract $object) - { - foreach ($this->_serializableFields as $field => $parameters) { - list($serializeDefault, $unserializeDefault) = $parameters; - $this->_unserializeField($object, $field, $unserializeDefault); - } - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array(); - return $this; - } - - /** - * Get configuration of all unique fields - * - * @return array - */ - public function getUniqueFields() - { - if (is_null($this->_uniqueFields)) { - $this->_initUniqueFields(); - } - return $this->_uniqueFields; - } - - /** - * Prepare data for save - * - * @param Mage_Core_Model_Abstract $object - * @return array - */ - protected function _prepareDataForSave(Mage_Core_Model_Abstract $object) - { - return $this->_prepareDataForTable($object, $this->getMainTable()); - } - - /** - * Prepare data for passed table - * - * @param Varien_Object $object - * @param string $table - * @return array - */ - protected function _prepareDataForTable(Varien_Object $object, $table) - { - $data = array(); - $fields = $this->_getWriteAdapter()->describeTable($table); - foreach (array_keys($fields) as $field) { - if ($object->hasData($field)) { - $fieldValue = $object->getData($field); - if ($fieldValue instanceof Zend_Db_Expr) { - $data[$field] = $fieldValue; - } else { - if (null !== $fieldValue) { - $data[$field] = $this->_prepareValueForSave($fieldValue, $fields[$field]['DATA_TYPE']); - } elseif (!empty($fields[$field]['NULLABLE'])) { - $data[$field] = null; - } - } - } - } - return $data; - } - - /** - * Prepare value for save - * - * @param mixed $value - * @param string $type - * @return mixed - */ - protected function _prepareValueForSave($value, $type) - { - if ($type == 'decimal') { - $value = Mage::app()->getLocale()->getNumber($value); - } - return $value; - } - - /** - * Check for unique values existence - * - * @param Varien_Object $object - * @return Mage_Core_Model_Mysql4_Abstract - * @throws Mage_Core_Exception - */ - protected function _checkUnique(Mage_Core_Model_Abstract $object) - { - $existent = array(); - $fields = $this->getUniqueFields(); - if (!empty($fields)) { - if (!is_array($fields)) { - $this->_uniqueFields = array( - array( - 'field' => $fields, - 'title' => $fields - )); - } - - $data = new Varien_Object($this->_prepareDataForSave($object)); - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainTable()); - - foreach ($fields as $unique) { - $select->reset(Zend_Db_Select::WHERE); - - if (is_array($unique['field'])) { - foreach ($unique['field'] as $field) { - $select->where($field.'=?', $data->getData($field)); - } - } - else { - $select->where( $unique['field'] . ' = ?', $data->getData($unique['field']) ); - } - - if ($object->getId()) { - $select->where($this->getIdFieldName().' != ?', $object->getId()); - } - - if ( $test = $this->_getWriteAdapter()->fetchRow($select) ) { - $existent[] = $unique['title']; - } - } - } - - if (!empty($existent)) { - if (count($existent) == 1 ) { - $error = Mage::helper('core')->__('%s already exists.', $existent[0]); - } - else { - $error = Mage::helper('core')->__('%s already exist.', implode(', ', $existent)); - } - Mage::throwException($error); - } - return $this; - } - - public function afterLoad(Mage_Core_Model_Abstract $object) - { - $this->_afterLoad($object); - } - - /** - * Perform actions after object load - * - * @param Varien_Object $object - */ - protected function _afterLoad(Mage_Core_Model_Abstract $object) - { - return $this; - } - - /** - * Perform actions before object save - * - * @param Varien_Object $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - return $this; - } - - /** - * Perform actions after object save - * - * @param Varien_Object $object - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - return $this; - } - - /** - * Perform actions before object delete - * - * @param Varien_Object $object - */ - protected function _beforeDelete(Mage_Core_Model_Abstract $object) - { - return $this; - } - - /** - * Perform actions after object delete - * - * @param Varien_Object $object - */ - protected function _afterDelete(Mage_Core_Model_Abstract $object) - { - return $this; - } - - /** - * Serialize serializeable fields of the object - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _serializeFields(Mage_Core_Model_Abstract $object) - { - foreach ($this->_serializableFields as $field => $parameters) { - list($serializeDefault, $unserializeDefault) = $parameters; - $this->_serializeField($object, $field, $serializeDefault, isset($parameters[2])); - } - } - - /** - * Retrieve table checksum - * - * @param string $table - * @return int - */ - public function getChecksum($table) - { - if (!$this->_getConnection('read')) { - return false; - } - - if (is_array($table)) { - $table = implode(',', $table); - } - - $data = $this->_getConnection('read')->fetchAll('checksum table '.$table); - $checksum = 0; - foreach ($data as $row) { - $checksum+= $row[self::CHECKSUM_KEY_NAME]; - } - return $checksum; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Cache.php b/app/code/core/Mage/Core/Model/Mysql4/Cache.php index 1137d9e9..3235a264 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Cache.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Cache.php @@ -20,56 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Cache extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Cache extends Mage_Core_Model_Resource_Cache { - protected function _construct() - { - $this->_init('core/cache_option', 'code'); - } - - /** - * Get all cache options - * @return array | false - */ - public function getAllOptions() - { - $adapter = $this->_getReadAdapter(); - if ($adapter) { - /** - * Check if table exist (it protect upgrades. cache settings checked before upgrades) - */ - if ($adapter->fetchOne('SHOW TABLES LIKE ?', $this->getMainTable())) { - $select = $adapter->select() - ->from($this->getMainTable(), array('code', 'value')); - return $adapter->fetchPairs($select); - } - } - return false; - } - - /** - * Save all options to option table - * - * @param array $options - * @return Mage_Core_Model_Mysql4_Cache - */ - public function saveAllOptions($options) - { - if (!$this->_getWriteAdapter()) { - return $this; - } - $this->_getWriteAdapter()->delete($this->getMainTable()); - foreach ($options as $code => $value) { - $this->_getWriteAdapter()->insert($this->getMainTable(), array( - 'code' => $code, - 'value' => $value - )); - } - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php index 3520981e..41655a1f 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Collection/Abstract.php @@ -20,533 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -abstract class Mage_Core_Model_Mysql4_Collection_Abstract extends Varien_Data_Collection_Db +/** + * Abstract Core Resource Collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Collection_Abstract extends Mage_Core_Model_Resource_Db_Collection_Abstract { - const CACHE_TAG = 'COLLECTION_DATA'; - /** - * Model name - * - * @var string - */ - protected $_model; - - /** - * Resource model name - * - * @var string - */ - protected $_resourceModel; - - /** - * Resource instance - * - * @var Mage_Core_Model_Mysql4_Abstract - */ - protected $_resource; - - /** - * Fields to select in query - * - * @var array|null - */ - protected $_fieldsToSelect = null; - - /** - * Fields initial fields to select like id_field - * - * @var array|null - */ - protected $_initialFieldsToSelect = null; - - /** - * Fields to select changed flag - * - * @var booleam - */ - protected $_fieldsToSelectChanged = false; - - /** - * Store joined tables here - * - * @var array - */ - protected $_joinedTables = array(); - - /** - * Collection main table - * - * @var string - */ - protected $_mainTable = null; - - /** - * Collection constructor - * - * @param Mage_Core_Model_Mysql4_Abstract $resource - */ - public function __construct($resource=null) - { - parent::__construct(); - $this->_construct(); - $this->_resource = $resource; - $this->setConnection($this->getResource()->getReadConnection()); - $this->_initSelect(); - } - - /** - * Initialization here - * - */ - protected function _construct() - { - - } - - /** - * Retrieve main table - * - * @return string - */ - public function getMainTable() - { - if ($this->_mainTable === null) { - $this->setMainTable($this->getResource()->getMainTable()); - } - - return $this->_mainTable; - } - - /** - * Set main collection table - * - * @param string $table - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function setMainTable($table) - { - if (strpos($table, '/') !== false) { - $table = $this->getTable($table); - } - - if ($this->_mainTable !== null && $table !== $this->_mainTable && $this->getSelect() !== null) { - $from = $this->getSelect()->getPart(Zend_Db_Select::FROM); - if (isset($from['main_table'])) { - $from['main_table']['tableName'] = $table; - } - $this->getSelect()->setPart(Zend_Db_Select::FROM, $from); - } - - $this->_mainTable = $table; - return $this; - } - - /** - * Init collection select - * - * @return unknown - */ - protected function _initSelect() - { - $this->getSelect()->from(array('main_table' => $this->getMainTable())); - return $this; - } - - /** - * Get Zend_Db_Select instance and applies fields to select if needed - * - * @return Varien_Db_Select - */ - public function getSelect() - { - if ($this->_select && $this->_fieldsToSelectChanged) { - $this->_fieldsToSelectChanged = false; - $this->_initSelectFields(); - } - return parent::getSelect(); - } - - /** - * Init fields for select - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - protected function _initSelectFields() - { - $columns = $this->_select->getPart(Zend_Db_Select::COLUMNS); - $columnsToSelect = array(); - foreach ($columns as $columnEntry) { - list($correlationName, $column, $alias) = $columnEntry; - if ($correlationName !== 'main_table') { // Add joined fields to select - if ($column instanceof Zend_Db_Expr) { - $column = $column->__toString(); - } - $key = ($alias !== null ? $alias : $column); - $columnsToSelect[$key] = $columnEntry; - } - } - - $columns = $columnsToSelect; - - $columnsToSelect = array_keys($columnsToSelect); - - if ($this->_fieldsToSelect !== null) { - $insertIndex = 0; - foreach ($this->_fieldsToSelect as $alias => $field) { - if (!is_string($alias)) { - $alias = null; - } - - if ($field instanceof Zend_Db_Expr) { - $column = $field->__toString(); - } else { - $column = $field; - } - - if (($alias !== null && in_array($alias, $columnsToSelect)) || // If field already joined fron another table - ($alias === null && isset($alias, $columnsToSelect))) { - continue; - } - - $columnEntry = array('main_table', $field, $alias); - array_splice($columns, $insertIndex, 0, array($columnEntry)); // Insert column - $insertIndex ++; - - } - } else { - array_unshift($columns, array('main_table', '*', null)); - } - - $this->_select->setPart(Zend_Db_Select::COLUMNS, $columns); - - return $this; - } - - /** - * Retrieve initial fields to select like id field - * - * @return array - */ - protected function _getInitialFieldsToSelect() - { - if ($this->_initialFieldsToSelect === null) { - $this->_initialFieldsToSelect = array(); - $this->_initInitialFieldsToSelect(); - } - - return $this->_initialFieldsToSelect; - } - - /** - * Initialize initial fields to select like id field - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - protected function _initInitialFieldsToSelect() - { - $idFieldName = $this->getResource()->getIdFieldName(); - if ($idFieldName) { - $this->_initialFieldsToSelect[] = $idFieldName; - } - return $this; - } - - /** - * Add field to select - * - * @param string|array $field - * @param string|null $alias - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function addFieldToSelect($field, $alias = null) - { - if ($field === '*') { // If we will select all fields - $this->_fieldsToSelect = null; - $this->_fieldsToSelectChanged = true; - return $this; - } - - if (is_array($field)) { - if ($this->_fieldsToSelect === null) { - $this->_fieldsToSelect = $this->_getInitialFieldsToSelect(); - } - - foreach ($field as $key => $value) { - $this->addFieldToSelect( - $value, - (is_string($key) ? $key : null), - false - ); - } - - $this->_fieldsToSelectChanged = true; - return $this; - } - - if ($alias === null) { - $this->_fieldsToSelect[] = $field; - } else { - $this->_fieldsToSelect[$alias] = $field; - } - - $this->_fieldsToSelectChanged = true; - return $this; - } - - /** - * Add attribute expression (SUM, COUNT, etc) - * - * Example: ('sub_total', 'SUM({{attribute}})', 'revenue') - * Example: ('sub_total', 'SUM({{revenue}})', 'revenue') - * - * For some functions like SUM use groupByAttribute. - * - * @param string $alias - * @param string $expression - * @param array $fields - * @return Mage_Eav_Model_Entity_Collection_Abstract - */ - public function addExpressionFieldToSelect($alias, $expression, $fields) - { - // validate alias - if(!is_array($fields)) { - $fields = array($fields=>$fields); - } - - $fullExpression = $expression; - foreach($fields as $fieldKey=>$fieldItem) { - $fullExpression = str_replace('{{' . $fieldKey . '}}', $fieldItem, $fullExpression); - } - - $this->getSelect()->columns(array($alias=>$fullExpression)); - - return $this; - } - - /** - * Removes field from select - * - * @param string|null $field - * @param boolean $isAlias Alias identifier - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function removeFieldFromSelect($field, $isAlias = false) - { - if ($isAlias) { - if (isset($this->_fieldsToSelect[$field])) { - unset($this->_fieldsToSelect[$field]); - } - } else { - foreach ($this->_fieldsToSelect as $key => $value) { - if ($value === $field) { - unset($this->_fieldsToSelect[$key]); - break; - } - } - } - - $this->_fieldsToSelectChanged = true; - return $this; - } - - /** - * Removes all fields from select - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function removeAllFieldsFromSelect() - { - $this->_fieldsToSelect = $this->_getInitialFieldsToSelect(); - $this->_fieldsToSelectChanged = true; - return $this; - } - - /** - * Standard resource collection initalization - * - * @param string $model - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - protected function _init($model, $resourceModel=null) - { - $this->setModel($model); - if (is_null($resourceModel)) { - $resourceModel = $model; - } - $this->setResourceModel($resourceModel); - return $this; - } - - /** - * Set model name for collection items - * - * @param string $model - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function setModel($model) - { - if (is_string($model)) { - $this->_model = $model; - $this->setItemObjectClass(Mage::getConfig()->getModelClassName($model)); - } - return $this; - } - - /** - * Get model instance - * - * @param array $args - * @return Varien_Object - */ - public function getModelName($args=array()) - { - return $this->_model; - } - - public function setResourceModel($model) - { - $this->_resourceModel = $model; - } - - public function getResourceModelName() - { - return $this->_resourceModel; - } - - /** - * Get resource instance - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - public function getResource() - { - if (empty($this->_resource)) { - $this->_resource = Mage::getResourceModel($this->getResourceModelName()); - } - return $this->_resource; - } - - public function getTable($table) - { - return $this->getResource()->getTable($table); - } - - /** - * Retrive all ids for collection - * - * @return array - */ - public function getAllIds() - { - $idsSelect = clone $this->getSelect(); - $idsSelect->reset(Zend_Db_Select::ORDER); - $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT); - $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET); - $idsSelect->reset(Zend_Db_Select::COLUMNS); - $idsSelect->columns( - 'main_table.' . $this->getResource()->getIdFieldName() - ); - return $this->getConnection()->fetchCol($idsSelect); - } - - public function join($table, $cond, $cols='*') - { - if (!isset($this->_joinedTables[$table])) { - $this->getSelect()->join(array($table=>$this->getTable($table)), $cond, $cols); - $this->_joinedTables[$table] = true; - } - return $this; - } - - /** - * Redeclare before load method for adding event - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - protected function _beforeLoad() - { - parent::_beforeLoad(); - Mage::dispatchEvent('core_collection_abstract_load_before', array('collection' => $this)); - return $this; - } - - /** - * Redeclare after load method for specifying collection items original data - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - protected function _afterLoad() - { - parent::_afterLoad(); - foreach ($this->_items as $item) { - $item->setOrigData(); - } - Mage::dispatchEvent('core_collection_abstract_load_after', array('collection' => $this)); - return $this; - } - - /** - * Save all the entities in the collection - * - * @return Mage_Core_Model_Mysql4_Collection_Abstract - */ - public function save() - { - foreach ($this->getItems() as $item) { - $item->save(); - } - return $this; - } - - /** - * Check if cache can be used for collection - * - * @return bool - */ - protected function _canUseCache() - { - return Mage::app()->useCache('collections') && !empty($this->_cacheConf); - } - - /** - * Load cached data for select - * - * @param Zend_Db_Select $select - * @return string | false - */ - protected function _loadCache($select) - { - $data = Mage::app()->loadCache($this->_getSelectCacheId($select)); - return $data; - } - - /** - * Save collection data to cache - * - * @param array $data - * @param Zend_Db_Select $select - * @return unknown_type - */ - protected function _saveCache($data, $select) - { - Mage::app()->saveCache(serialize($data), $this->_getSelectCacheId($select), $this->_getCacheTags()); - return $this; - } - - /** - * Redeclared for processing cache tags throw application object - * - * @return array - */ - protected function _getCacheTags() - { - $tags = parent::_getCacheTags(); - $tags[] = Mage_Core_Model_App::CACHE_TAG; - $tags[] = self::CACHE_TAG; - return $tags; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Config.php b/app/code/core/Mage/Core/Model/Mysql4/Config.php index 91fce817..10f6766a 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Config.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Config.php @@ -20,214 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Config extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Config extends Mage_Core_Model_Resource_Config { - protected function _construct() - { - $this->_init('core/config_data', 'config_id'); - } - - /** - * Get checksum for one or more tables - * - * @param string|array $tables string is separated by comma - * @return integer|boolean - */ - public function getChecksum($tables) - { - if (is_string($tables)) { - $tablesArr = explode(',', $tables); - $tables = array(); - foreach ($tablesArr as $table) { - $table = $this->getTable(trim($table)); - if (!empty($table)) { - $tables[] = $table; - } - } - } - if (empty($tables) || !$this->_getReadAdapter()) { - return false; - } - $checksumArr = $this->_getReadAdapter()->fetchAll('checksum table '.join(',', $tables)); - $checksum = 0; - foreach ($checksumArr as $r) { - $checksum += $r['Checksum']; - } - return $checksum; - } - - /** - * Load configuration values into xml config object - * - * @param Mage_Core_Model_Config $xmlConfig - * @param string $cond - * @return Mage_Core_Model_Mysql4_Config_Collection - */ - public function loadToXml(Mage_Core_Model_Config $xmlConfig, $cond=null) - { - $read = $this->_getReadAdapter(); - if (!$read) { - return $this; - } - - $websites = array(); - $rows = $read->fetchAssoc("select website_id, code, name from ".$this->getTable('website')); - foreach ($rows as $w) { - $xmlConfig->setNode('websites/'.$w['code'].'/system/website/id', $w['website_id']); - $xmlConfig->setNode('websites/'.$w['code'].'/system/website/name', $w['name']); - $websites[$w['website_id']] = array('code'=>$w['code']); - } - - $stores = array(); - $rows = $read->fetchAssoc("select store_id, code, name, website_id from ".$this->getTable('store')." order by sort_order asc"); - foreach ($rows as $s) { - $xmlConfig->setNode('stores/'.$s['code'].'/system/store/id', $s['store_id']); - $xmlConfig->setNode('stores/'.$s['code'].'/system/store/name', $s['name']); - $xmlConfig->setNode('stores/'.$s['code'].'/system/website/id', $s['website_id']); - $xmlConfig->setNode('websites/'.$websites[$s['website_id']]['code'].'/system/stores/'.$s['code'], $s['store_id']); - $stores[$s['store_id']] = array('code'=>$s['code']); - $websites[$s['website_id']]['stores'][$s['store_id']] = $s['code']; - } - - $subst_from = array(); - $subst_to = array(); - - // load all configuration records from database, which are not inherited - $rows = $read->fetchAll("select scope, scope_id, path, value from ".$this->getMainTable().($cond ? ' where '.$cond : '')); - - // set default config values from database - foreach ($rows as $r) { - if ($r['scope']!=='default') { - continue; - } - $value = str_replace($subst_from, $subst_to, $r['value']); - $xmlConfig->setNode('default/'.$r['path'], $value); - } - // inherit default config values to all websites - $extendSource = $xmlConfig->getNode('default'); - foreach ($websites as $id=>$w) { - $websiteNode = $xmlConfig->getNode('websites/'.$w['code']); - $websiteNode->extend($extendSource); - } - - $deleteWebsites = array(); - // set websites config values from database - foreach ($rows as $r) { - if ($r['scope']!=='websites') { - continue; - } - $value = str_replace($subst_from, $subst_to, $r['value']); - if (isset($websites[$r['scope_id']])) { - $xmlConfig->setNode('websites/'.$websites[$r['scope_id']]['code'].'/'.$r['path'], $value); - } - else { - $deleteWebsites[$r['scope_id']] = $r['scope_id']; - } - } - - // extend website config values to all associated stores - foreach ($websites as $website) { - $extendSource = $xmlConfig->getNode('websites/'.$website['code']); - if (isset($website['stores'])) { - foreach ($website['stores'] as $sCode) { - $storeNode = $xmlConfig->getNode('stores/'.$sCode); - /** - * $extendSource DO NOT need overwrite source - */ - $storeNode->extend($extendSource, false); - } - } - } - - $deleteStores = array(); - // set stores config values from database - foreach ($rows as $r) { - if ($r['scope']!=='stores') { - continue; - } - $value = str_replace($subst_from, $subst_to, $r['value']); - if (isset($stores[$r['scope_id']])) { - $xmlConfig->setNode('stores/'.$stores[$r['scope_id']]['code'].'/'.$r['path'], $value); - } - else { - $deleteStores[$r['scope_id']] = $r['scope_id']; - } - } - - if ($deleteWebsites) { - $this->_getWriteAdapter()->delete($this->getMainTable(), array( - $this->_getWriteAdapter()->quoteInto('scope=?', 'websites'), - $this->_getWriteAdapter()->quoteInto('scope_id IN(?)', $deleteWebsites), - )); - } - - if ($deleteStores) { - $this->_getWriteAdapter()->delete($this->getMainTable(), array( - $this->_getWriteAdapter()->quoteInto('scope=?', 'stores'), - $this->_getWriteAdapter()->quoteInto('scope_id IN(?)', $deleteStores), - )); - } - - return $this; - } - - /** - * Save config value - * - * @param string $path - * @param string $value - * @param string $scope - * @param int $scopeId - * @return Mage_Core_Store_Mysql4_Config - */ - public function saveConfig($path, $value, $scope, $scopeId) - { - $writeAdapter = $this->_getWriteAdapter(); - $select = $writeAdapter->select() - ->from($this->getMainTable()) - ->where('path=?', $path) - ->where('scope=?', $scope) - ->where('scope_id=?', $scopeId); - $row = $writeAdapter->fetchRow($select); - - $newData = array( - 'scope' => $scope, - 'scope_id' => $scopeId, - 'path' => $path, - 'value' => $value - ); - - if ($row) { - $whereCondition = $writeAdapter->quoteInto($this->getIdFieldName() . '=?', $row[$this->getIdFieldName()]); - $writeAdapter->update($this->getMainTable(), $newData, $whereCondition); - } - else { - $writeAdapter->insert($this->getMainTable(), $newData); - } - return $this; - } - - /** - * Delete config value - * - * @param string $path - * @param string $scope - * @param int $scopeId - * @return Mage_Core_Store_Mysql4_Config - */ - public function deleteConfig($path, $scope, $scopeId) - { - $writeAdapter = $this->_getWriteAdapter(); - $writeAdapter->delete($this->getMainTable(), array( - $writeAdapter->quoteInto('path=?', $path), - $writeAdapter->quoteInto('scope=?', $scope), - $writeAdapter->quoteInto('scope_id=?', $scopeId) - )); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Config/Data.php b/app/code/core/Mage/Core/Model/Mysql4/Config/Data.php index fef262eb..6885827b 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Config/Data.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Config/Data.php @@ -20,22 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Config_Data extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Config_Data extends Mage_Core_Model_Resource_Config_Data { - protected function _construct() - { - $this->_init('core/config_data', 'config_id'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (is_array($object->getValue())) { - $object->setValue(join(',', $object->getValue())); - } - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Config/Data/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Config/Data/Collection.php index 0c8db871..815c18e6 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Config/Data/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Config/Data/Collection.php @@ -20,43 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Config data collection * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Config_Data_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Config_Data_Collection extends Mage_Core_Model_Resource_Config_Data_Collection { - protected function _construct() - { - $this->_init('core/config_data'); - } - - public function addScopeFilter($scope, $scopeId, $section) - { - $this->_select - ->where('scope=?', $scope) - ->where('scope_id=?', $scopeId) - ->where('path like ?', $section . '/%'); - return $this; - } - - public function addPathFilter($section) - { - $this->_select - ->where('path like ?', $section . '/%'); - return $this; - } - - public function addValueFilter($value) - { - $this->getSelect()->where('value=?', $value); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Design.php b/app/code/core/Mage/Core/Model/Mysql4/Design.php index 51642cf2..1d471812 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Design.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Design.php @@ -20,146 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Design extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Design extends Mage_Core_Model_Resource_Design { - protected function _construct() - { - $this->_init('core/design_change', 'design_change_id'); - } - - public function _beforeSave(Mage_Core_Model_Abstract $object) - { - $format = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); - if ($date = $object->getDateFrom()) { - $date = Mage::app()->getLocale()->date($date, $format, null, false); - $object->setDateFrom($date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); - } else { - $object->setDateFrom(null); - } - - if ($date = $object->getDateTo()) { - $date = Mage::app()->getLocale()->date($date, $format, null, false); - $object->setDateTo($date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT)); - } else { - $object->setDateTo(null); - } - - if (!is_null($object->getDateFrom()) && !is_null($object->getDateTo()) && strtotime($object->getDateFrom()) > strtotime($object->getDateTo())){ - Mage::throwException(Mage::helper('core')->__('Start date cannot be greater than end date.')); - } - - $check = $this->_checkIntersection( - $object->getStoreId(), - $object->getDateFrom(), - $object->getDateTo(), - $object->getId() - ); - - if ($check){ - Mage::throwException(Mage::helper('core')->__('Your design change for the specified store intersects with another one, please specify another date range.')); - } - - if (is_null($object->getDateFrom())) - $object->setDateFrom(new Zend_Db_Expr('null')); - if (is_null($object->getDateTo())) - $object->setDateTo(new Zend_Db_Expr('null')); - - parent::_beforeSave($object); - } - - private function _checkIntersection($storeId, $dateFrom, $dateTo, $currentId) - { - $condition = '(date_to is null AND date_from is null)'; - if (!is_null($dateFrom)) { - $condition .= ' - OR - (? between date_from and date_to) - OR - (? >= date_from and date_to is null) - OR - (? <= date_to and date_from is null) - '; - } else { - $condition .= ' - OR - (date_from is null) - '; - } - - if (!is_null($dateTo)) { - $condition .= ' - OR - (# between date_from and date_to) - OR - (# >= date_from and date_to is null) - OR - (# <= date_to and date_from is null) - '; - } else { - $condition .= ' - OR - (date_to is null) - '; - } - - if (is_null($dateFrom) && !is_null($dateTo)) { - $condition .= ' - OR - (date_to <= # or date_from <= #) - '; - } - if (!is_null($dateFrom) && is_null($dateTo)) { - $condition .= ' - OR - (date_to >= ? or date_from >= ?) - '; - } - - if (!is_null($dateFrom) && !is_null($dateTo)) { - $condition .= ' - OR - (date_from between ? and #) - OR - (date_to between ? and #) - '; - } else if (is_null($dateFrom) && is_null($dateTo)) { - $condition = false; - } - - $select = $this->_getReadAdapter()->select() - ->from(array('main_table'=>$this->getTable('design_change'))) - ->where('main_table.store_id = ?', $storeId) - ->where('main_table.design_change_id <> ?', $currentId); - - if ($condition) { - $condition = $this->_getReadAdapter()->quoteInto($condition, $dateFrom); - $condition = str_replace('#', '?', $condition); - $condition = $this->_getReadAdapter()->quoteInto($condition, $dateTo); - - $select->where($condition); - } - - return $this->_getReadAdapter()->fetchOne($select); - } - - public function loadChange($storeId, $date = null) - { - if (is_null($date)) { - //$date = new Zend_Db_Expr('NOW()'); - $date = now(); - } - - $select = $this->_getReadAdapter()->select() - ->from(array('main_table'=>$this->getTable('design_change'))) - ->where('store_id = ?', $storeId) - ->where('(date_from <= ? or date_from is null)', $date) - ->where('(date_to >= ? or date_to is null)', $date); - - return $this->_getReadAdapter()->fetchRow($select); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Design/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Design/Collection.php index 45444356..221b60b2 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Design/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Design/Collection.php @@ -20,42 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Design_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Design_Collection extends Mage_Core_Model_Resource_Design_Collection { - protected function _construct() - { - $this->_init('core/design'); - } - - public function joinStore(){ - $this->getSelect() - ->join(array('s'=>$this->getTable('core/store')), 's.store_id = main_table.store_id', array('s.name')); - - return $this; - } - - public function addDateFilter($date = null) - { - if (is_null($date)) - $date = date("Y-m-d"); - - $this->getSelect() - ->where('main_table.date_from <= ?', $date) - ->where('main_table.date_to >= ?', $date); - - return $this; - } - - public function addStoreFilter($storeId) - { - $this->getSelect() - ->where('main_table.store_id IN(?)', $storeId); - - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Design/Package/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Design/Package/Collection.php index 1ea417c3..50ba1734 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Design/Package/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Design/Package/Collection.php @@ -20,33 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Design_Package_Collection extends Varien_Object +/** + * Design package collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Design_Package_Collection extends Mage_Core_Model_Resource_Design_Package_Collection { - public function load() - { - $packages = $this->getData('packages'); - if (is_null($packages)) { - $packages = Mage::getModel('core/design_package')->getPackageList(); - $this->setData('packages', $packages); - } - - return $this; - } - - public function toOptionArray() - { - $options = array(); - $packages = $this->getData('packages'); - foreach ($packages as $package) { - $options[] = array('value'=>$package, 'label'=>$package); - } - array_unshift($options, array('value'=>'', 'label'=>'')); - - return $options; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php index d06a82af..af2bb2aa 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Design/Theme/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php b/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php index 5c2ccc76..c0ac24b8 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Email/Template.php @@ -20,132 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Template db resource * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Email_Template extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Email_Template extends Mage_Core_Model_Resource_Email_Template { - - /** - * Templates table name - * @var string - */ - protected $_templateTable; - - /** - * DB write connection - */ - protected $_write; - - /** - * DB read connection - */ - protected $_read; - - /** - * Initialize email template resource model - * - */ - protected function _construct() - { - $this->_init('core/email_template', 'template_id'); - $this->_templateTable = $this->getTable('core/email_template'); - $this->_read = $this->_getReadAdapter(); - $this->_write = $this->_getWriteAdapter(); - } - - /** - * Load by template code from DB. - * - * @param string $templateCode - * @return array - */ - public function loadByCode($templateCode) - { - $select = $this->_read->select() - ->from($this->_templateTable) - ->where('template_code=?', $templateCode); - - - $result = $this->_read->fetchRow($select); - - if (!$result) { - return array(); - } - - return $result; - } - - - /** - * Check usage of template code in other templates - * - * @param Mage_Core_Model_Email_Template $template - * @return boolean - */ - public function checkCodeUsage(Mage_Core_Model_Email_Template $template) - { - if($template->getTemplateActual()!=0 || is_null($template->getTemplateActual())) { - - $select = $this->_read->select() - ->from($this->_templateTable, new Zend_Db_Expr('COUNT(template_id)')) - ->where('template_id!=?',$template->getId()) - ->where('template_code=?',$template->getTemplateCode()); - - $countOfCodes = $this->_read->fetchOne($select); - - return $countOfCodes > 0; - } else { - return false; - } - } - - /** - * Set template type, added at and modified at time - * - * @param Varien_Object $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if(!$object->getAddedAt()) { - $object->setAddedAt(Mage::getSingleton('core/date')->gmtDate()); - $object->setModifiedAt(Mage::getSingleton('core/date')->gmtDate()); - } - $object->setTemplateType((int)$object->getTemplateType()); - return parent::_beforeSave($object); - } - - /** - * Retrieve config scope and scope id of specified email template by email pathes - * - * @param array $paths - * @param int|string $templateId - * @return array - */ - public function getSystemConfigByPathsAndTemplateId($paths, $templateId) - { - $adapter = $this->_getReadAdapter(); - $orWhere = array(); - foreach ($paths as $path) { - $orWhere[] = $adapter->quoteInto('path = ?', $path); - } - $select = $this->_read->select() - ->from($this->getTable('core/config_data'), array('scope', 'scope_id', 'path')) - ->where('value=?', $templateId) - ->where(join(' OR ', $orWhere)); - - $result = $this->_read->fetchAll($select); - if (!$result) { - return array(); - } - return $result; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Email/Template/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Email/Template/Collection.php index 161f820f..384aff83 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Email/Template/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Email/Template/Collection.php @@ -20,42 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Templates collection - * - * @category Mage - * @package Mage_Core + * + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Email_Template_Collection extends Varien_Data_Collection_Db +class Mage_Core_Model_Mysql4_Email_Template_Collection extends Mage_Core_Model_Resource_Email_Template_Collection { - /** - * Template table name - * - * @var string - */ - protected $_templateTable; - - public function __construct() - { - parent::__construct(Mage::getSingleton('core/resource')->getConnection('core_read')); - $this->_templateTable = Mage::getSingleton('core/resource')->getTableName('core/email_template'); - $this->_select->from($this->_templateTable, array('template_id','template_code', - 'template_type', - 'template_subject','template_sender_name', - 'template_sender_email', - 'added_at', - 'modified_at')); - $this->setItemObjectClass(Mage::getConfig()->getModelClassName('core/email_template')); - } - - public function toOptionArray() - { - return $this->_toOptionArray('template_id', 'template_code'); - } - } diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php new file mode 100644 index 00000000..77be58c4 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Abstract.php @@ -0,0 +1,37 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Abstract storage resourse model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Mysql4_File_Storage_Abstract extends Mage_Core_Model_Resource_File_Storage_Abstract +{ +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php new file mode 100644 index 00000000..2d23459e --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Database.php @@ -0,0 +1,37 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * File storage database resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_File_Storage_Database extends Mage_Core_Model_Resource_File_Storage_Database +{ +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php new file mode 100644 index 00000000..ad6665f6 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/Directory/Database.php @@ -0,0 +1,37 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Directory storage database resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_File_Storage_Directory_Database extends Mage_Core_Model_Resource_File_Storage_Directory_Database +{ +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php new file mode 100644 index 00000000..6e9cc495 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Mysql4/File/Storage/File.php @@ -0,0 +1,37 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Model for synchronization from DB to filesystem + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_File_Storage_File extends Mage_Core_Model_Resource_File_Storage_File +{ +} diff --git a/app/code/core/Mage/Core/Model/Mysql4/Flag.php b/app/code/core/Mage/Core/Model/Mysql4/Flag.php index 87cceef0..c400aaea 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Flag.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Flag.php @@ -20,14 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Flag extends Mage_Core_Model_Mysql4_Abstract + +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Flag extends Mage_Core_Model_Resource_Flag { - protected function _construct() - { - $this->_init('core/flag', 'flag_id'); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Language.php b/app/code/core/Mage/Core/Model/Mysql4/Language.php index ea0bbc76..3283ddf0 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Language.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Language.php @@ -20,15 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Language extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Language extends Mage_Core_Model_Resource_Language { - protected function _construct() - { - $this->_init('core/language', 'language_code'); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Language/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Language/Collection.php index ff892aa2..fe329d3b 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Language/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Language/Collection.php @@ -20,25 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Language_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Language_Collection extends Mage_Core_Model_Resource_Language_Collection { - protected function _construct() - { - $this->_init('core/language'); - } - - public function toOptionArray() - { - return $this->_toOptionArray('language_code', 'language_title', array('title'=>'language_title')); - } - - public function toOptionHash() - { - return $this->_toOptionHash('language_code', 'language_title'); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Layout.php b/app/code/core/Mage/Core/Model/Mysql4/Layout.php index 6ca99032..a9ff2cb0 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Layout.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Layout.php @@ -20,49 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Layout extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Layout extends Mage_Core_Model_Resource_Layout { - protected function _construct() - { - $this->_init('core/layout_update', 'layout_update_id'); - } - - /** - * Retrieve layout updates by handle - * - * @param string $handle - * @param array $params - */ - public function fetchUpdatesByHandle($handle, $params = array()) - { - $storeId = isset($params['store_id']) ? $params['store_id'] : Mage::app()->getStore()->getId(); - $area = isset($params['area']) ? $params['area'] : Mage::getSingleton('core/design_package')->getArea(); - $package = isset($params['package']) ? $params['package'] : Mage::getSingleton('core/design_package')->getPackageName(); - $theme = isset($params['theme']) ? $params['theme'] : Mage::getSingleton('core/design_package')->getTheme('layout'); - - $updateStr = ''; - - $readAdapter = $this->_getReadAdapter(); - if ($readAdapter) { - $select = $readAdapter->select() - ->from(array('update'=>$this->getMainTable()), array('xml')) - ->join(array('link'=>$this->getTable('core/layout_link')), 'link.layout_update_id=update.layout_update_id', '') - ->where('link.store_id IN (0, ?)', $storeId) - ->where('link.area=?', $area) - ->where('link.package=?', $package) - ->where('link.theme=?', $theme) - ->where('update.handle = ?', $handle) - ->order('update.sort_order ASC'); - - foreach ($readAdapter->fetchAll($select) as $update) { - $updateStr .= $update['xml']; - } - } - return $updateStr; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Resource.php b/app/code/core/Mage/Core/Model/Mysql4/Resource.php index 28745830..7116a0ce 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Resource.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Resource.php @@ -20,120 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -#require_once 'Mage/Core/Model/Mysql4.php'; /** * Mysql Model for module + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Resource +class Mage_Core_Model_Mysql4_Resource extends Mage_Core_Model_Resource_Resource { - protected $_read = null; - protected $_write = null; - protected $_resTable = null; - protected static $_versions = null; - protected static $_dataVersions = null; - - /** - * Class constructor - */ - public function __construct() - { - $this->_resTable = Mage::getSingleton('core/resource')->getTableName('core/resource'); - $this->_read = Mage::getSingleton('core/resource')->getConnection('core_read'); - $this->_write = Mage::getSingleton('core/resource')->getConnection('core_write'); - } - - /** - * Get Module version from DB - * - * @param string $moduleName - * @return string - */ - function getDbVersion($resName) - { - if (!$this->_read) { - return false; - } - - if (is_null(self::$_versions)) { - // if Core module not instaled - try { - $select = $this->_read->select()->from($this->_resTable, array('code', 'version')); - self::$_versions = $this->_read->fetchPairs($select); - } - catch (Exception $e){ - self::$_versions = array(); - } - } - return isset(self::$_versions[$resName]) ? self::$_versions[$resName] : false; - } - - /** - * Set module wersion into DB - * - * @param string $moduleName - * @param string $version - * @return int - */ - function setDbVersion($resName, $version) - { - $dbModuleInfo = array( - 'code' => $resName, - 'version' => $version, - ); - - if ($this->getDbVersion($resName)) { - self::$_versions[$resName] = $version; - $condition = $this->_write->quoteInto('code=?', $resName); - return $this->_write->update($this->_resTable, $dbModuleInfo, $condition); - } - else { - self::$_versions[$resName] = $version; - return $this->_write->insert($this->_resTable, $dbModuleInfo); - } - } - - /** - * Get resource data version - * - * @param string $resName - * @return string | false - */ - public function getDataVersion($resName) - { - if (!$this->_read) { - return false; - } - if (is_null(self::$_dataVersions)) { - $select = $this->_read->select()->from($this->_resTable, array('code', 'data_version')); - self::$_dataVersions = $this->_read->fetchPairs($select); - } - return isset(self::$_dataVersions[$resName]) ? self::$_dataVersions[$resName] : false; - } - - /** - * Specify resource data version - * - * @param string $resName - * @param string $version - * @return Mage_Core_Model_Mysql4_Resource - */ - public function setDataVersion($resName, $version) - { - $data = array('code' => $resName, 'data_version' => $version); - - if ($this->getDbVersion($resName) || $this->getDataVersion($resName)) { - self::$_dataVersions[$resName] = $version; - $this->_write->update($this->_resTable, $data, array('code=?' => $resName)); - } - else { - self::$_dataVersions[$resName] = $version; - $this->_write->insert($this->_resTable, $data); - } - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Session.php b/app/code/core/Mage/Core/Model/Mysql4/Session.php index 18e174a9..0b209c07 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Session.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,209 +28,10 @@ /** * Mysql4 session save handler * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Session implements Zend_Session_SaveHandler_Interface +class Mage_Core_Model_Mysql4_Session extends Mage_Core_Model_Resource_Session { - /** - * Session lifetime - * - * @var integer - */ - protected $_lifeTime; - - /** - * Session data table name - * - * @var string - */ - protected $_sessionTable; - - /** - * Database read connection - * - * @var Zend_Db_Adapter_Abstract - */ - protected $_read; - - /** - * Database write connection - * - * @var Zend_Db_Adapter_Abstract - */ - protected $_write; - - /** - * Automatic cleaning factor of expired sessions - * - * value zero means no automatic cleaning, one means automatic cleaning each time a session is closed, and x>1 means - * cleaning once in x calls - */ - protected $_automaticCleaningFactor = 50; - - public function __construct() - { - $this->_sessionTable = Mage::getSingleton('core/resource')->getTableName('core/session'); - $this->_read = Mage::getSingleton('core/resource')->getConnection('core_read'); - $this->_write = Mage::getSingleton('core/resource')->getConnection('core_write'); - } - - public function __destruct() - { - session_write_close(); - } - - public function getLifeTime() - { - if (is_null($this->_lifeTime)) { - $configNode = Mage::app()->getStore()->isAdmin() ? 'admin/security/session_cookie_lifetime' : 'web/cookie/cookie_lifetime'; - $this->_lifeTime = (int) Mage::getStoreConfig($configNode); - - if ($this->_lifeTime < 60) { - $this->_lifeTime = ini_get('session.gc_maxlifetime'); - } - - if ($this->_lifeTime < 60) { - $this->_lifeTime = 3600; - } - } - return $this->_lifeTime; - } - - /** - * Check DB connection - * - * @return bool - */ - public function hasConnection() - { - if (!$this->_read) { - return false; - } - $tables = $this->_read->fetchAssoc('show tables'); - if (!isset($tables[$this->_sessionTable])) { - return false; - } - - return true; - } - - public function setSaveHandler() - { - if ($this->hasConnection()) { - session_set_save_handler( - array($this, 'open'), - array($this, 'close'), - array($this, 'read'), - array($this, 'write'), - array($this, 'destroy'), - array($this, 'gc') - ); - } else { - session_save_path(Mage::getBaseDir('session')); - } - return $this; - } - - /** - * Open session - * - * @param string $savePath ignored - * @param string $sessName ignored - * @return boolean - */ - public function open($savePath, $sessName) - { - return true; - } - - /** - * Close session - * - * @return boolean - */ - public function close() - { - $this->gc($this->getLifeTime()); - - return true; - } - - /** - * Fetch session data - * - * @param string $sessId - * @return string - */ - public function read($sessId) - { - $data = $this->_read->fetchOne( - "SELECT session_data FROM $this->_sessionTable - WHERE session_id = ? AND session_expires > ?", - array($sessId, time()) - ); - - return $data; - } - - /** - * Update session - * - * @param string $sessId - * @param string $sessData - * @return boolean - */ - public function write($sessId, $sessData) - { - $bind = array( - 'session_expires'=>time() + $this->getLifeTime(), - 'session_data'=>$sessData - ); - - $exists = $this->_write->fetchOne( - "SELECT session_id FROM `{$this->_sessionTable}` - WHERE session_id = ?", array($sessId) - ); - - if ($exists) { - $where = $this->_write->quoteInto('session_id=?', $sessId); - $this->_write->update($this->_sessionTable, $bind, $where); - } else { - $bind['session_id'] = $sessId; - $this->_write->insert($this->_sessionTable, $bind); - } - - return true; - } - - /** - * Destroy session - * - * @param string $sessId - * @return boolean - */ - public function destroy($sessId) - { - $this->_write->query("DELETE FROM `{$this->_sessionTable}` WHERE `session_id` = ?", array($sessId)); - return true; - } - - /** - * Garbage collection - * - * @param int $sessMaxLifeTime ignored - * @return boolean - */ - public function gc($sessMaxLifeTime) - { - if ($this->_automaticCleaningFactor > 0) { - if ($this->_automaticCleaningFactor == 1 || - rand(1, $this->_automaticCleaningFactor)==1) { - $this->_write->query("DELETE FROM `{$this->_sessionTable}` WHERE `session_expires` < ?", array(time())); - } - } - return true; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Store.php b/app/code/core/Mage/Core/Model/Mysql4/Store.php index 37c76a6f..cc55840e 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Store.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Store.php @@ -20,100 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Store extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Store extends Mage_Core_Model_Resource_Store { - protected function _construct() - { - $this->_init('core/store', 'store_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array(array( - 'field' => 'code', - 'title' => Mage::helper('core')->__('Store with the same code') - )); - return $this; - } - - protected function _beforeSave(Mage_Core_Model_Abstract $model) - { - if(!preg_match('/^[a-z]+[a-z0-9_]*$/',$model->getCode())) { - Mage::throwException( - Mage::helper('core')->__('The store code may contain only letters (a-z), numbers (0-9) or underscore(_), the first character must be a letter')); - } - - return $this; - } - - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - parent::_afterSave($object); - $this->_updateGroupDefaultStore($object->getGroupId(), $object->getId()); - $this->_changeGroup($object); - - return $this; - } - - protected function _afterDelete(Mage_Core_Model_Abstract $model) - { - $this->_getWriteAdapter()->delete( - $this->getTable('core/config_data'), - $this->_getWriteAdapter()->quoteInto("scope = 'stores' AND scope_id = ?", $model->getStoreId()) - ); - return $this; - } - - protected function _updateGroupDefaultStore($groupId, $store_id) - { - $write = $this->_getWriteAdapter(); - $cnt = $write->fetchOne($write->select() - ->from($this->getTable('core/store'), array('count'=>'COUNT(*)')) - ->where($write->quoteInto('group_id=?', $groupId)), - 'count'); - if ($cnt == 1) { - $write->update($this->getTable('core/store_group'), - array('default_store_id' => $store_id), - $write->quoteInto('group_id=?', $groupId) - ); - } - return $this; - } - - protected function _changeGroup(Mage_Core_Model_Abstract $model) { - if ($model->getOriginalGroupId() && $model->getGroupId() != $model->getOriginalGroupId()) { - $write = $this->_getWriteAdapter(); - $storeId = $write->fetchOne($write->select() - ->from($this->getTable('core/store_group'), 'default_store_id') - ->where($write->quoteInto('group_id=?', $model->getOriginalGroupId())), - 'default_store_id' - ); - if ($storeId == $model->getId()) { - $write->update($this->getTable('core/store_group'), - array('default_store_id'=>0), - $write->quoteInto('group_id=?', $model->getOriginalGroupId())); - } - } - return $this; - } - - protected function _getLoadSelect($field, $value, $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where($field.'=?', $value) - ->order('sort_order ASC'); - - return $select; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php index 36cd2c1a..b4144e53 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Store/Collection.php @@ -20,150 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Stores collection * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Store_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Store_Collection extends Mage_Core_Model_Resource_Store_Collection { - protected $_loadDefault = false; - - protected function _construct() - { - $this->_init('core/store'); - } - - public function setLoadDefault($loadDefault) - { - $this->_loadDefault = $loadDefault; - return $this; - } - - public function getLoadDefault() - { - return $this->_loadDefault; - } - - public function setWithoutDefaultFilter() - { - $this->getSelect()->where($this->getConnection()->quoteInto('main_table.store_id>?', 0)); - return $this; - } - - /** - * Add filter by group id. - * Group id can be passed as one single value or array of values. - * - * @param int|array $groupId - * @return Mage_Core_Model_Mysql4_Store_Collection - */ - public function addGroupFilter($groupId) - { - if (is_array($groupId)) { - $condition = $this->getConnection()->quoteInto("main_table.group_id IN (?)", $groupId); - } else { - $condition = $this->getConnection()->quoteInto("main_table.group_id = ?",$groupId); - } - - $this->addFilter('group_id', $condition, 'string'); - return $this; - } - - public function addIdFilter($store) - { - if (is_array($store)) { - $condition = $this->getConnection()->quoteInto("main_table.store_id IN (?)", $store); - } - else { - $condition = $this->getConnection()->quoteInto("main_table.store_id=?",$store); - } - - $this->addFilter('store_id', $condition, 'string'); - return $this; - } - - public function addWebsiteFilter($website) - { - if (is_array($website)) { - $condition = $this->getConnection()->quoteInto("main_table.website_id IN (?)", $website); - } - else { - $condition = $this->getConnection()->quoteInto("main_table.website_id=?",$website); - } - - $this->addFilter('website_id', $condition, 'string'); - return $this; - } - - /** - * Add root category id filter to store collection - * - * @param int|array $category - * @return Mage_Core_Model_Mysql4_Store_Collection - */ - public function addCategoryFilter($category) - { - if (!is_array($category)) { - $category = array($category); - } - return $this->loadByCategoryIds($category); - } - - public function toOptionArray() - { - return $this->_toOptionArray('store_id', 'name'); - } - - public function toOptionHash() - { - return $this->_toOptionHash('store_id', 'name'); - } - - public function load($printQuery = false, $logQuery = false) - { - if (!$this->getLoadDefault()) { - $this->getSelect()->where($this->getConnection()->quoteInto('main_table.store_id>?', 0)); - } - $this->addOrder('CASE WHEN main_table.store_id = 0 THEN 0 ELSE 1 END', 'ASC') - ->addOrder('main_table.sort_order', 'ASC') - ->addOrder('main_table.name', 'ASC'); - parent::load($printQuery, $logQuery); - return $this; - } - - /** - * Add root category id filter to store collection - * - * @param array $categories - * @return Mage_Core_Model_Mysql4_Store_Collection - */ - public function loadByCategoryIds(array $categories) - { - $this->setLoadDefault(true); - $condition = $this->getConnection()->quoteInto('group_table.root_category_id IN(?)', $categories); - $this->_select->joinLeft( - array('group_table' => $this->getTable('core/store_group')), - 'main_table.group_id=group_table.group_id', - array('root_category_id') - )->where($condition); - - return $this; - } - - public function addRootCategoryIdAttribute() - { - $this->_select->joinLeft( - array('group_table' => $this->getTable('core/store_group')), - 'main_table.group_id=group_table.group_id', - array('root_category_id') - ); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Store/Group.php b/app/code/core/Mage/Core/Model/Mysql4/Store/Group.php index 5a524c27..e17f79a0 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Store/Group.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Store/Group.php @@ -20,82 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Store group resource model * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ - -class Mage_Core_Model_Mysql4_Store_Group extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Store_Group extends Mage_Core_Model_Resource_Store_Group { - protected function _construct() - { - $this->_init('core/store_group', 'group_id'); - } - - protected function _afterSave(Mage_Core_Model_Abstract $model) - { - $this->_updateStoreWebsite($model->getId(), $model->getWebsiteId()); - $this->_updateWebsiteDefaultGroup($model->getWebsiteId(), $model->getId()); - $this->_changeWebsite($model); - - return $this; - } - - protected function _updateWebsiteDefaultGroup($websiteId, $groupId) - { - $write = $this->_getWriteAdapter(); - $cnt = $write->fetchOne($write->select() - ->from($this->getTable('core/store_group'), array('count'=>'COUNT(*)')) - ->where($write->quoteInto('website_id=?', $websiteId)), - 'count'); - if ($cnt == 1) { - $write->update($this->getTable('core/website'), - array('default_group_id' => $groupId), - $write->quoteInto('website_id=?', $websiteId) - ); - } - return $this; - } - - protected function _changeWebsite(Mage_Core_Model_Abstract $model) { - if ($model->getOriginalWebsiteId() && $model->getWebsiteId() != $model->getOriginalWebsiteId()) { - $write = $this->_getWriteAdapter(); - $groupId = $write->fetchOne($write->select() - ->from($this->getTable('core/website'), 'default_group_id') - ->where($write->quoteInto('website_id=?', $model->getOriginalWebsiteId())), - 'default_group_id' - ); - if ($groupId == $model->getId()) { - $write->update($this->getTable('core/website'), - array('default_group_id'=>0), - $write->quoteInto('website_id=?', $model->getOriginalWebsiteId())); - } - } - return $this; - } - - protected function _updateStoreWebsite($groupId, $websiteId) - { - $write = $this->_getWriteAdapter(); - $bind = array('website_id'=>$websiteId); - $condition = $write->quoteInto('group_id=?', $groupId); - $this->_getWriteAdapter()->update($this->getTable('core/store'), $bind, $condition); - return $this; - } - - protected function _saveDefaultStore($groupId, $storeId) - { - $write = $this->_getWriteAdapter(); - $bind = array('default_store_id'=>$storeId); - $condition = $write->quoteInto('group_id=?', $groupId); - $this->_getWriteAdapter()->update($this->getMainTable(), $bind, $condition); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Store/Group/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Store/Group/Collection.php index b8f77fa6..c6ead1b8 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Store/Group/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Store/Group/Collection.php @@ -20,66 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Store group collection * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ - -class Mage_Core_Model_Mysql4_Store_Group_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Store_Group_Collection extends Mage_Core_Model_Resource_Store_Group_Collection { - protected $_loadDefault = false; - - protected function _construct() - { - $this->_init('core/store_group'); - } - - public function load($printQuery = false, $logQuery = false) - { - if (!$this->_loadDefault) { - $this->setWithoutDefaultFilter(); - } - $this->addOrder('main_table.name', 'ASC'); - return parent::load($printQuery, $logQuery); - } - - public function setLoadDefault($loadDefault) - { - $this->_loadDefault = (bool)$loadDefault; - return $this; - } - - public function getLoadDefault() - { - return $this->_loadDefault; - } - - public function setWithoutDefaultFilter() - { - $this->getSelect()->where($this->getConnection()->quoteInto('main_table.group_id>?', 0)); - return $this; - } - - public function toOptionArray() - { - return $this->_toOptionArray('group_id', 'name'); - } - - public function addWebsiteFilter($website) - { - if (is_array($website)) { - $condition = $this->getConnection()->quoteInto('main_table.website_id IN(?)', $website); - } - else { - $condition = $this->getConnection()->quoteInto('main_table.website_id=?', $website); - } - return $this->addFilter('website_id', $condition, 'string'); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Translate.php b/app/code/core/Mage/Core/Model/Mysql4/Translate.php index a2cd5c66..72bbdb09 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Translate.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Translate.php @@ -20,98 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Translation resource model * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Translate extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Translate extends Mage_Core_Model_Resource_Translate { - protected function _construct() - { - $this->_init('core/translate', 'key_id'); - } - - public function getTranslationArray($storeId=null, $locale=null) - { - if(!Mage::isInstalled()) { - return array(); - } - - if (is_null($storeId)) { - $storeId = Mage::app()->getStore()->getId(); - } - - $read = $this->_getReadAdapter(); - if (!$read) { - return array(); - } - -// $select = $read->select() -// ->from(array('main'=>$this->getMainTable()), array( -// 'string', -// new Zend_Db_Expr('IFNULL(store.translate, main.translate)') -// )) -// ->joinLeft(array('store'=>$this->getMainTable()), -// $read->quoteInto('store.string=main.string AND store.store_id=?', $storeId), -// 'string') -// ->where('main.store_id=0'); -// -// $result = $read->fetchPairs($select); -// - $select = $read->select() - ->from($this->getMainTable()) - ->where('store_id in (?)', array(0, $storeId)) - ->where('locale=?', $locale) - ->order('store_id'); - - $result = array(); - foreach ($read->fetchAll($select) as $row) { - $result[$row['string']] = $row['translate']; - } - - return $result; - } - - public function getTranslationArrayByStrings(array $strings, $storeId=null) - { - if(!Mage::isInstalled()) { - return array(); - } - - if (is_null($storeId)) { - $storeId = Mage::app()->getStore()->getId(); - } - - $read = $this->_getReadAdapter(); - if (!$read) { - return array(); - } - - if (empty($strings)) { - return array(); - } - - $select = $read->select() - ->from($this->getMainTable()) - ->where('string in (:tr_strings)') - ->where('store_id = ?', $storeId); - $result = array(); - foreach ($read->fetchAll($select, array('tr_strings'=>$read->quote($strings))) as $row) { - $result[$row['string']] = $row['translate']; - } - - return $result; - } - - public function getMainChecksum() - { - return parent::getChecksum($this->getMainTable()); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Translate/String.php b/app/code/core/Mage/Core/Model/Mysql4/Translate/String.php index 2ef0f47e..9f3e3099 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Translate/String.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Translate/String.php @@ -20,181 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * String translate resource model * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Translate_String extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Translate_String extends Mage_Core_Model_Resource_Translate_String { - protected function _construct() - { - $this->_init('core/translate', 'key_id'); - } - - public function load(Mage_Core_Model_Abstract $object, $value, $field=null) - { - if (is_string($value)) { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where($this->getMainTable().'.string=:tr_string'); - $result = $this->_getReadAdapter()->fetchRow($select, array('tr_string'=>$value)); - $object->setData($result); - $this->_afterLoad($object); - return $result; - } - else { - return parent::load($object, $value, $field); - } - } - - protected function _getLoadSelect($field, $value, $object) - { - $select = parent::_getLoadSelect($field, $value, $object); - $select->where('store_id', 0); - return $select; - } - - - public function _afterLoad(Mage_Core_Model_Abstract $object) - { - $connection = $this->_getReadAdapter(); - $select = $connection->select() - ->from($this->getMainTable(), array('store_id', 'translate')) - ->where('string=:translate_string'); - $translations = $connection->fetchPairs($select, array('translate_string' => $object->getString())); - $object->setStoreTranslations($translations); - return parent::_afterLoad($object); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - $connection = $this->_getWriteAdapter(); - $select = $connection->select() - ->from($this->getMainTable(), 'key_id') - ->where('string=?', $object->getString()) - ->where('store_id=?', 0); - - $object->setId($connection->fetchOne($select)); - return parent::_beforeSave($object); - } - - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $connection = $this->_getWriteAdapter(); - $select = $connection->select() - ->from($this->getMainTable(), array('store_id', 'key_id')) - ->where('string=?', $object->getString()); - $stors = $connection->fetchPairs($select); - - $translations = $object->getStoreTranslations(); - - if (is_array($translations)) { - foreach ($translations as $storeId => $translate) { - $condition = $connection->quoteInto('store_id=? AND ', $storeId) . - $connection->quoteInto('string=?', $object->getString()); - - if (is_null($translate) || $translate=='') { - $connection->delete($this->getMainTable(), $condition); - } - else { - $data = array( - 'store_id' => $storeId, - 'string' => $object->getString(), - 'translate' =>$translate, - ); - - if (isset($stors[$storeId])) { - $connection->update( - $this->getMainTable(), - $data, - $connection->quoteInto('key_id=?', $stors[$storeId])); - } - else { - $connection->insert($this->getMainTable(), $data); - } - } - } - } - return parent::_afterSave($object); - } - - /** - * Delete translates - * - * @param string $string - * @param string $locale - * @param int|null $storeId - * - * @return Mage_Core_Model_Mysql4_Translate_String - */ - public function deleteTranslate($string, $locale = null, $storeId = null) - { - if (is_null($locale)) { - $locale = Mage::app()->getLocale()->getLocaleCode(); - } - - $where = array( - $this->_getWriteAdapter()->quoteInto('locale=?', $locale), - $this->_getWriteAdapter()->quoteInto('string=?', $string), - ); - - if ($storeId === false) { - $where[] = $this->_getWriteAdapter()->quoteInto('store_id>?', 0); - } - elseif (!is_null($storeId)) { - $where[] = $this->_getWriteAdapter()->quoteInto('store_id=?', $storeId); - } - - $this->_getWriteAdapter()->delete($this->getMainTable(), $where); - - return $this; - } - - public function saveTranslate($string, $translate, $locale=null, $storeId=null) - { - $write = $this->_getWriteAdapter(); - $table = $this->getMainTable(); - - if (is_null($locale)) { - $locale = Mage::app()->getLocale()->getLocaleCode(); - } - - if (is_null($storeId)) { - $storeId = Mage::app()->getStore()->getId(); - } - - $select = $write->select() - ->from($table, array('key_id', 'translate')) - ->where('store_id=?', $storeId) - ->where('locale=?', $locale) - ->where('string=?', $string) - ; - if ($row = $write->fetchRow($select)) { - $original = $string; - if (strpos($original, '::')!==false) { - list($scope, $original) = explode('::', $original); - } - if ($original == $translate) { - $write->delete($table, 'key_id='.(int)$row['key_id']); - } elseif ($row['translate']!=$translate) { - $write->update($table, array('translate'=>$translate), 'key_id='.(int)$row['key_id']); - } - } else { - $write->insert($table, array( - 'store_id'=>$storeId, - 'locale'=>$locale, - 'string'=>$string, - 'translate'=>$translate, - )); - } - - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php b/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php index 89869ef5..2fefa0a6 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,84 +28,10 @@ /** * Url rewrite resource model class * - * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Url_Rewrite extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Url_Rewrite extends Mage_Core_Model_Resource_Url_Rewrite { - protected $_tagTable; - - protected function _construct() - { - $this->_init('core/url_rewrite', 'url_rewrite_id'); - $this->_tagTable = $this->getTable('url_rewrite_tag'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array( - array( - 'field' => array('id_path','store_id','is_system'), - 'title' => Mage::helper('core')->__('ID Path for Specified Store') - ), - array( - 'field' => array('request_path','store_id'), - 'title' => Mage::helper('core')->__('Request Path for Specified Store'), - ) - ); - return $this; - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @return Zend_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - /* @var $select Varien_Db_Select */ - $select = parent::_getLoadSelect($field, $value, $object); - - if (!is_null($object->getStoreId())) { - $select->where('store_id IN(?)', array(0, $object->getStoreId())); - $select->order('store_id desc'); - $select->limit(1); - } - - return $select; - } - - /** - * Retrieve request_path using id_path and current store's id. - * - * @param string $idPath - * @param int|Mage_Core_Model_Store $store - * @return string|false - */ - public function getRequestPathByIdPath($idPath, $store) - { - if ($store instanceof Mage_Core_Model_Store) { - $storeId = (int)$store->getId(); - } else { - $storeId = (int)$store; - } - - $select = $this->_getReadAdapter()->select(); - /* @var $select Zend_Db_Select */ - $select->from(array('main_table' => $this->getMainTable()), 'request_path') - ->where('main_table.store_id = ?', $storeId) - ->where('main_table.id_path = ?', $idPath) - ->limit(1); - - return $this->_getReadAdapter()->fetchOne($select); - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite/Collection.php index e864d649..c7c49e7c 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Url/Rewrite/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,74 +28,10 @@ /** * Url rewrite resource collection model class * - * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Url_Rewrite_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Url_Rewrite_Collection extends Mage_Core_Model_Resource_Url_Rewrite_Collection { - protected function _construct() - { - $this->_init('core/url_rewrite'); - } - - /** - * Add filter for tags (combined by OR) - */ - public function addTagsFilter($tags) - { - $tagsArr = is_array($tags) ? $tags : explode(',', $tags); - - $sqlArr = array(); - foreach ($tagsArr as $t) { - $sqlArr[] = $this->getConnection()->quoteInto("find_in_set(?, `tags`)", $t); - } - - $cond = $this->getConnection()->quoteInto('`url_rewrite_id`=main_table.`url_rewrite_id` and `tag` in (?)', $tagsArr); - $this->getSelect()->join($this->getTable('url_rewrite_tag'), $cond, array()); - return $this; - } - - /** - * Filter collections by stores - * - * @param mixed $store - * @param bool $withAdmin - * @return Mage_Core_Model_Mysql4_Url_Rewrite_Collection - */ - public function addStoreFilter($store, $withAdmin = true) - { - if (is_array($store) || is_numeric($store)) { - if (!is_array($store)) { - $store = array($store); - } - } - else { - $store = Mage::helper('core')->getStoreId($store); - } - if ($withAdmin) { - $this->getSelect()->where('store_id = 0 OR store_id IN (?)', $store); - } - else { - $this->getSelect()->where('store_id IN (?)', $store); - } - return $this; - } - - public function filterAllByProductId($productId) - { - $this->getSelect() - ->where('id_path = ?', "product/{$productId}") - ->orWhere('id_path like ?', "product/{$productId}/%"); - - return $this; - } - - public function filterAllByCategory() - { - $this->getSelect() - ->where('id_path like ?', "category%"); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Variable.php b/app/code/core/Mage/Core/Model/Mysql4/Variable.php index 063e4010..d1149442 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Variable.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Variable.php @@ -20,126 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Custom variable resource model * - * @category Mage - * @package Mage_Core - * @author Magento Core Team <core@magentocommerce.com> + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Variable extends Mage_Core_Model_Mysql4_Abstract +class Mage_Core_Model_Mysql4_Variable extends Mage_Core_Model_Resource_Variable { - /** - * Constructor - */ - protected function _construct() - { - $this->_init('core/variable', 'variable_id'); - } - - /** - * Load variable by code - * - * @param Mage_Core_Model_Variable $object - * @param string $code - * @return Mage_Core_Model_Mysql4_Variable - */ - public function loadByCode(Mage_Core_Model_Variable $object, $code) - { - if ($result = $this->getVariableByCode($code, true, $object->getStoreId())) { - $object->setData($result); - } - return $this; - } - - /** - * Retrieve variable data by code - * - * @param string $code - * @param boolean $withValue - * @param integer $storeId - * @return array - */ - public function getVariableByCode($code, $withValue = false, $storeId = 0) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where($this->getMainTable().'.code = ?', $code); - if ($withValue) { - $this->_addValueToSelect($select, $storeId); - } - return $this->_getReadAdapter()->fetchRow($select); - } - - /** - * Perform actions after object save - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Core_Model_Mysql4_Variable - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - parent::_afterSave($object); - if ($object->getUseDefaultValue()) { - /* - * remove store value - */ - $this->_getWriteAdapter()->delete( - $this->getTable('core/variable_value'), array( - 'variable_id = ?' => $object->getId(), - 'store_id = ?' => $object->getStoreId() - )); - } else { - $this->_getWriteAdapter()->insertOnDuplicate( - $this->getTable('core/variable_value'), array( - 'variable_id' => $object->getId(), - 'store_id' => $object->getStoreId(), - 'plain_value' => $object->getPlainValue(), - 'html_value' => $object->getHtmlValue() - ), array('plain_value', 'html_value')); - } - return $this; - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @param Mage_Core_Model_Abstract $object - * @return Zend_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - $select = parent::_getLoadSelect($field, $value, $object); - $this->_addValueToSelect($select, $object->getStoreId()); - return $select; - } - - /** - * Add variable store and default value to select - * - * @param Zend_Db_Select $select - * @param integer $storeId - * @return Mage_Core_Model_Mysql4_Variable - */ - protected function _addValueToSelect(Zend_Db_Select $select, $storeId = 0) - { - $select->joinLeft( - array('default' => $this->getTable('core/variable_value')), - 'default.variable_id = '.$this->getMainTable().'.variable_id AND default.store_id = 0', - array()) - ->joinLeft( - array('store' => $this->getTable('core/variable_value')), - 'store.variable_id = default.variable_id AND store.store_id = ' . $storeId, - array()) - ->columns(array('plain_value' => new Zend_Db_Expr('IFNULL(store.plain_value, default.plain_value)'), - 'html_value' => new Zend_Db_Expr('IFNULL(store.html_value, default.html_value)'), - 'store_plain_value' => 'store.plain_value', 'store_html_value' => 'store.html_value')); - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Variable/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Variable/Collection.php index a2accb48..4327c597 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Variable/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Variable/Collection.php @@ -20,76 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Custom variabel collection * - * @category Mage - * @package Mage_Core - * @author Magento Core Team <core@magentocommerce.com> + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Variable_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Variable_Collection extends Mage_Core_Model_Resource_Variable_Collection { - protected $_storeId = 0; - - /** - * Constructor - */ - protected function _construct() - { - parent::_construct(); - $this->_init('core/variable'); - } - - /** - * Setter - * - * @param integer $storeId - * @return Mage_Core_Model_Mysql4_Variable_Collection - */ - public function setStoreId($storeId) - { - $this->_storeId = $storeId; - return $this; - } - - /** - * Getter - * - * @return integer - */ - public function getStoreId() - { - return $this->_storeId; - } - - /** - * Add store values to result - * - * @return Mage_Core_Model_Mysql4_Variable_Collection - */ - public function addValuesToResult() - { - $this->getSelect() - ->join( - array('value_table' => $this->getTable('core/variable_value')), - $this->getConnection()->quoteInto('value_table.variable_id = main_table.variable_id AND store_id = ?', $this->getStoreId()), - array()) - ->columns(array('value' => 'value_table.value')); - return $this; - } - - /** - * Retrieve option array - * - * @return array - */ - public function toOptionArray() - { - return $this->_toOptionArray('code', 'name'); - } - } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Website.php b/app/code/core/Mage/Core/Model/Mysql4/Website.php index c4508592..9c569741 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Website.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Website.php @@ -20,99 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Core_Model_Mysql4_Website extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Mysql4_Website extends Mage_Core_Model_Resource_Website { - protected function _construct() - { - $this->_init('core/website', 'website_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array(array( - 'field' => 'code', - 'title' => Mage::helper('core')->__('Website with the same code') - )); - return $this; - } - - /** - * Perform actions before object save - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Core_Model_Mysql4_Website - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if(!preg_match('/^[a-z]+[a-z0-9_]*$/', $object->getCode())) { - Mage::throwException(Mage::helper('core')->__('Website code may only contain letters (a-z), numbers (0-9) or underscore(_), the first character must be a letter')); - } - - return parent::_beforeSave($object); - } - - /** - * Perform actions after object save - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Core_Model_Mysql4_Website - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - if ($object->getIsDefault()) { - $this->_getWriteAdapter()->update( - $this->getMainTable(), - array('is_default' => 0), - 1 - ); - $this->_getWriteAdapter()->update( - $this->getMainTable(), - array('is_default' => 1), - $this->_getWriteAdapter()->quoteInto('website_id=?', $object->getId()) - ); - } - return parent::_afterSave($object); - } - - protected function _afterDelete(Mage_Core_Model_Abstract $model) - { - $this->_getWriteAdapter()->delete( - $this->getTable('core/config_data'), - $this->_getWriteAdapter()->quoteInto("scope = 'websites' AND scope_id = ?", $model->getWebsiteId()) - ); - return $this; - } - - /** - * Retrieve default stores select object - * Select fields website_id, store_id - * - * @param $withDefault include/exclude default admin website - * @return Varien_Db_Select - */ - public function getDefaultStoresSelect($withDefault = false) { - $select = $this->_getReadAdapter()->select() - ->from(array('website_table' => $this->getTable('core/website')), array('website_id')) - ->joinLeft( - array('store_group_table' => $this->getTable('core/store_group')), - '`website_table`.`website_id`=`store_group_table`.`website_id`' - . ' AND `website_table`.`default_group_id`=`store_group_table`.`group_id`', - array('store_id' => 'IFNULL(`store_group_table`.`default_store_id`, 0)') - ); - if (!$withDefault) { - $select->where('`website_table`.`website_id` <> ?', 0); - } - return $select; - } - } diff --git a/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php b/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php index f8d717ec..e1bcab52 100644 --- a/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php +++ b/app/code/core/Mage/Core/Model/Mysql4/Website/Collection.php @@ -20,121 +20,18 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Websites collection * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ -class Mage_Core_Model_Mysql4_Website_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Core_Model_Mysql4_Website_Collection extends Mage_Core_Model_Resource_Website_Collection { - protected $_loadDefault = false; - - protected $_map = array('fields' => array('website_id' => 'main_table.website_id')); - - protected function _construct() - { - $this->_init('core/website'); - } - - public function addIdFilter($ids) - { - if (is_array($ids)) { - if (empty($ids)) { - $this->addFieldToFilter('website_id', null); - } - else { - $this->addFieldToFilter('website_id', array('in'=>$ids)); - } - } - else { - $this->addFieldToFilter('website_id', $ids); - } - return $this; - } - - public function setLoadDefault($loadDefault) - { - $this->_loadDefault = $loadDefault; - return $this; - } - - public function getLoadDefault() - { - return $this->_loadDefault; - } - - public function toOptionArray() - { - return $this->_toOptionArray('website_id', 'name'); - } - - public function toOptionHash() - { - return $this->_toOptionHash('website_id', 'name'); - } - - public function load($printQuery = false, $logQuery = false) - { - if (!$this->getLoadDefault()) { - $this->getSelect()->where($this->getConnection()->quoteInto('main_table.website_id>?', 0)); - } - $this->unshiftOrder('main_table.name', 'ASC') // website name SECOND - ->unshiftOrder('main_table.sort_order', 'ASC') // website sort order FIRST - ; - parent::load($printQuery, $logQuery); - return $this; - } - - /** - * Join group and store info from appropriate tables. - * Defines new _idFiledName as 'website_group_store' bc for - * one website can be more then one row in collection. - * Sets extra combined ordering by group's name, defined - * sort ordering and store's name. - * - * @return Mage_Core_Model_Mysql4_Website_Collection - */ - public function joinGroupAndStore() - { - if (!$this->getFlag('groups_and_stores_joined')) { - $this->_idFieldName = 'website_group_store'; - $this->getSelect()->joinLeft( - array('group_table' => $this->getTable('core/store_group')), - 'main_table.website_id=group_table.website_id', - array('group_id'=>'group_id', 'group_title'=>'name') - )->joinLeft( - array('store_table' => $this->getTable('core/store')), - 'group_table.group_id=store_table.group_id', - array('store_id'=>'store_id', 'store_title'=>'name') - ); - $this->addOrder('group_table.name', 'ASC') // store name - ->addOrder('CASE WHEN store_table.store_id = 0 THEN 0 ELSE 1 END', 'ASC') // view is admin - ->addOrder('store_table.sort_order', 'ASC') // view sort order - ->addOrder('store_table.name', 'ASC') // view name - ; - $this->setFlag('groups_and_stores_joined', true); - } - return $this; - } - - /** - * Adding filter by group id or array of ids but only if - * tables with appropriate information were joined before. - * - * @param int|array $groupIds - * @return Mage_Core_Model_Mysql4_Website_Collection - */ - public function addFilterByGroupIds($groupIds) - { - if ($this->getFlag('groups_and_stores_joined')) { - $this->addFieldToFilter('group_table.group_id', $groupIds); - } - return $this; - } } diff --git a/app/code/core/Mage/Core/Model/Observer.php b/app/code/core/Mage/Core/Model/Observer.php new file mode 100644 index 00000000..615e25d0 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Observer.php @@ -0,0 +1,108 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Observer model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Observer +{ + /** + * Check if synchronize process is finished and generate notification message + * + * @param Varien_Event_Observer $observer + * @return Mage_Core_Model_Observer + */ + public function addSynchronizeNotification(Varien_Event_Observer $observer) + { + $adminSession = Mage::getSingleton('admin/session'); + if (!$adminSession->hasSyncProcessStopWatch()) { + $flag = Mage::getSingleton('core/file_storage')->getSyncFlag(); + $state = $flag->getState(); + if ($state == Mage_Core_Model_File_Storage_Flag::STATE_RUNNING) { + $syncProcessStopWatch = true; + } else { + $syncProcessStopWatch = false; + } + + $adminSession->setSyncProcessStopWatch($syncProcessStopWatch); + } + $adminSession->setSyncProcessStopWatch(false); + + if (!$adminSession->getSyncProcessStopWatch()) { + if (!isset($flag)) { + $flag = Mage::getSingleton('core/file_storage')->getSyncFlag(); + } + + $state = $flag->getState(); + if ($state == Mage_Core_Model_File_Storage_Flag::STATE_FINISHED) { + $flagData = $flag->getFlagData(); + if (isset($flagData['has_errors']) && $flagData['has_errors']) { + $severity = Mage_AdminNotification_Model_Inbox::SEVERITY_MAJOR; + $title = Mage::helper('adminhtml')->__('An error has occured while syncronizing media storages.'); + $description = Mage::helper('adminhtml')->__('One or more media files failed to be synchronized during the media storages syncronization process. Refer to the log file for details.'); + } else { + $severity = Mage_AdminNotification_Model_Inbox::SEVERITY_NOTICE; + $title = Mage::helper('adminhtml')->__('Media storages synchronization has completed!'); + $description = Mage::helper('adminhtml')->__('Synchronization of media storages has been successfully completed.'); + } + + $date = date('Y-m-d H:i:s'); + Mage::getModel('adminnotification/inbox')->parse(array( + array( + 'severity' => $severity, + 'date_added' => $date, + 'title' => $title, + 'description' => $description, + 'url' => '', + 'internal' => true + ) + )); + + $flag->setState(Mage_Core_Model_File_Storage_Flag::STATE_NOTIFIED)->save(); + } + + $adminSession->setSyncProcessStopWatch(false); + } + + return $this; + } + + /** + * Cron job method to clean old cache resources + * + * @param Mage_Cron_Model_Schedule $schedule + */ + public function cleanCache(Mage_Cron_Model_Schedule $schedule) + { + Mage::app()->getCache()->clean(Zend_Cache::CLEANING_MODE_OLD); + Mage::dispatchEvent('core_clean_cache'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource.php b/app/code/core/Mage/Core/Model/Resource.php index 1bcddf1c..44708e4d 100644 --- a/app/code/core/Mage/Core/Model/Resource.php +++ b/app/code/core/Mage/Core/Model/Resource.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,50 +31,68 @@ */ class Mage_Core_Model_Resource { + const AUTO_UPDATE_CACHE_KEY = 'DB_AUTOUPDATE'; + const AUTO_UPDATE_ONCE = 0; + const AUTO_UPDATE_NEVER = -1; + const AUTO_UPDATE_ALWAYS = 1; - const AUTO_UPDATE_CACHE_KEY = 'DB_AUTOUPDATE'; - const AUTO_UPDATE_ONCE = 0; - const AUTO_UPDATE_NEVER = -1; - const AUTO_UPDATE_ALWAYS = 1; - - const DEFAULT_READ_RESOURCE = 'core_read'; - const DEFAULT_WRITE_RESOURCE= 'core_write'; + const DEFAULT_READ_RESOURCE = 'core_read'; + const DEFAULT_WRITE_RESOURCE = 'core_write'; + const DEFAULT_SETUP_RESOURCE = 'core_setup'; /** * Instances of classes for connection types * * @var array */ - protected $_connectionTypes = array(); + protected $_connectionTypes = array(); /** * Instances of actual connections * * @var array */ - protected $_connections = array(); + protected $_connections = array(); + + /** + * Names of actual connections that wait to set cache + * + * @var array + */ + protected $_skippedConnections = array(); /** * Registry of resource entities * * @var array */ - protected $_entities = array(); + protected $_entities = array(); + /** + * Mapped tables cache array + * + * @var array + */ protected $_mappedTableNames; /** * Creates a connection to resource whenever needed * * @param string $name - * @return mixed + * @return Varien_Db_Adapter_Interface */ public function getConnection($name) { if (isset($this->_connections[$name])) { - return $this->_connections[$name]; + $connection = $this->_connections[$name]; + if (isset($this->_skippedConnections[$name]) && !Mage::app()->getIsCacheLocked()) { + $connection->setCacheAdapter(Mage::app()->getCache()); + unset($this->_skippedConnections[$name]); + } + return $connection; } $connConfig = Mage::getConfig()->getResourceConnectionConfig($name); + if (!$connConfig) { $this->_connections[$name] = $this->_getDefaultConnection($name); return $this->_connections[$name]; @@ -82,26 +100,97 @@ public function getConnection($name) if (!$connConfig->is('active', 1)) { return false; } - $origName = $connConfig->getParent()->getName(); + $origName = $connConfig->getParent()->getName(); if (isset($this->_connections[$origName])) { $this->_connections[$name] = $this->_connections[$origName]; return $this->_connections[$origName]; } - $typeInstance = $this->getConnectionTypeInstance((string)$connConfig->type); - $conn = $typeInstance->getConnection($connConfig); - if (method_exists($conn, 'setCacheAdapter')) { - $conn->setCacheAdapter(Mage::app()->getCache()); + $connection = $this->_newConnection((string)$connConfig->type, $connConfig); + if ($connection) { + if (Mage::app()->getIsCacheLocked()) { + $this->_skippedConnections[$name] = true; + } else { + $connection->setCacheAdapter(Mage::app()->getCache()); + } + } + + $this->_connections[$name] = $connection; + if ($origName !== $name) { + $this->_connections[$origName] = $connection; + } + + return $connection; + } + + /** + * Retrieve connection adapter class name by connection type + * + * @param string $type the connection type + * @return string|false + */ + protected function _getConnectionAdapterClassName($type) + { + $config = Mage::getConfig()->getResourceTypeConfig($type); + if (!empty($config->adapter)) { + return (string)$config->adapter; + } + return false; + } + + /** + * Create new connection adapter instance by connection type and config + * + * @param string $type the connection type + * @param Mage_Core_Model_Config_Element|array $config the connection configuration + * @return Varien_Db_Adapter_Interface|false + */ + protected function _newConnection($type, $config) + { + if ($config instanceof Mage_Core_Model_Config_Element) { + $config = $config->asArray(); + } + if (!is_array($config)) { + return false; + } + + $connection = false; + // try to get adapter and create connection + $className = $this->_getConnectionAdapterClassName($type); + if ($className) { + // define profiler settings + $config['profiler'] = isset($config['profiler']) && $config['profiler'] != 'false'; + + $connection = new $className($config); + if ($connection instanceof Varien_Db_Adapter_Interface) { + // run after initialization statements + if (!empty($config['initStatements'])) { + $connection->query($config['initStatements']); + } + } else { + $connection = false; + } } - $this->_connections[$name] = $conn; - if ($origName!==$name) { - $this->_connections[$origName] = $conn; + // try to get connection from type + if (!$connection) { + $typeInstance = $this->getConnectionTypeInstance($type); + $connection = $typeInstance->getConnection($config); + if (!$connection instanceof Varien_Db_Adapter_Interface) { + $connection = false; + } } - return $conn; + + return $connection; } + /** + * Retrieve default connection name by required connection name + * + * @param string $requiredConnectionName + * @return string + */ protected function _getDefaultConnection($requiredConnectionName) { if (strpos($requiredConnectionName, 'read') !== false) { @@ -131,40 +220,68 @@ public function getConnectionTypeInstance($type) /** * Get resource entity * - * @param string $resource + * @param string $model * @param string $entity * @return Varien_Simplexml_Config */ public function getEntity($model, $entity) { - //return Mage::getConfig()->getNode("global/models/$model/entities/$entity"); - return Mage::getConfig()->getNode()->global->models->{$model}->entities->{$entity}; + $modelsNode = Mage::getConfig()->getNode()->global->models; + $entityConfig = $modelsNode->$model->entities->{$entity}; + + /** + * Backwards compatibility for pre-MMDB extensions. + * In MMDB release resource nodes <..._mysql4> were renamed to <..._resource>. So <deprecatedNode> is left + * to keep name of previously used nodes, that still may be used by non-updated extensions. + */ + if (isset($modelsNode->$model->deprecatedNode)) { + $deprecatedNode = $modelsNode->$model->deprecatedNode; + if (isset($modelsNode->$deprecatedNode->entities->$entity)) { + $entityConfig = $modelsNode->$deprecatedNode->entities->$entity; + } + } + + return $entityConfig; } /** - * Get resource table name + * Get resource table name, validated by db adapter * - * @param string $modelEntity + * @param string|array $modelEntity * @return string */ public function getTableName($modelEntity) { - $arr = explode('/', $modelEntity); - if (isset($arr[1])) { - list($model, $entity) = $arr; - //$resourceModel = (string)Mage::getConfig()->getNode('global/models/'.$model.'/resourceModel'); - $resourceModel = (string) Mage::getConfig()->getNode()->global->models->{$model}->resourceModel; - $entityConfig = $this->getEntity($resourceModel, $entity); - if ($entityConfig) { + $tableSuffix = null; + if (is_array($modelEntity)) { + list($modelEntity, $tableSuffix) = $modelEntity; + } + + $parts = explode('/', $modelEntity); + if (isset($parts[1])) { + list($model, $entity) = $parts; + $entityConfig = false; + if (!empty(Mage::getConfig()->getNode()->global->models->{$model}->resourceModel)) { + $resourceModel = (string)Mage::getConfig()->getNode()->global->models->{$model}->resourceModel; + $entityConfig = $this->getEntity($resourceModel, $entity); + } + + if ($entityConfig && !empty($entityConfig->table)) { $tableName = (string)$entityConfig->table; } else { - Mage::throwException(Mage::helper('core')->__('Cannot retrieve entity config: %s', $modelEntity)); + Mage::throwException(Mage::helper('core')->__('Can\'t retrieve entity config: %s', $modelEntity)); } } else { $tableName = $modelEntity; } - Mage::dispatchEvent('resource_get_tablename', array('resource' => $this, 'model_entity' => $modelEntity, 'table_name' => $tableName)); + Mage::dispatchEvent('resource_get_tablename', array( + 'resource' => $this, + 'model_entity' => $modelEntity, + 'table_name' => $tableName, + 'table_suffix' => $tableSuffix + )); + $mappedTableName = $this->getMappedTableName($tableName); if ($mappedTableName) { $tableName = $mappedTableName; @@ -173,15 +290,31 @@ public function getTableName($modelEntity) $tableName = $tablePrefix . $tableName; } - return $tableName; + if (!is_null($tableSuffix)) { + $tableName .= '_' . $tableSuffix; + } + return $this->getConnection(self::DEFAULT_READ_RESOURCE)->getTableName($tableName); } + /** + * Set mapped table name + * + * @param string $tableName + * @param string $mappedName + * @return Mage_Core_Model_Resource + */ public function setMappedTableName($tableName, $mappedName) { $this->_mappedTableNames[$tableName] = $mappedName; return $this; } + /** + * Get mapped table name + * + * @param string $tableName + * @return bool|string + */ public function getMappedTableName($tableName) { if (isset($this->_mappedTableNames[$tableName])) { @@ -191,11 +324,18 @@ public function getMappedTableName($tableName) } } + /** + * Clean db row + * + * @param array $row + * @return Mage_Core_Model_Resource + */ public function cleanDbRow(&$row) { + $zeroDate = $this->getConnection(self::DEFAULT_READ_RESOURCE)->getSuggestedZeroDate(); if (!empty($row) && is_array($row)) { foreach ($row as $key=>&$value) { - if (is_string($value) && $value==='0000-00-00 00:00:00') { + if (is_string($value) && $value === $zeroDate) { $value = ''; } } @@ -203,11 +343,20 @@ public function cleanDbRow(&$row) return $this; } + /** + * Create new connection with custom config + * + * @param string $name + * @param string $type + * @param array $config + * @return unknown + */ public function createConnection($name, $type, $config) { if (!isset($this->_connections[$name])) { - $typeObj = $this->getConnectionTypeInstance($type); - $this->_connections[$name] = $typeObj->getConnection($config); + $connection = $this->_newConnection($type, $config); + + $this->_connections[$name] = $connection; } return $this->_connections[$name]; } @@ -230,5 +379,33 @@ public function setAutoUpdate($value) #Mage::app()->saveCache($value, self::AUTO_UPDATE_CACHE_KEY); return $this; } + /** + * Retrieve 32bit UNIQUE HASH for a Table index + * + * @param string $tableName + * @param array|string $fields + * @param string $indexType + * @return string + */ + public function getIdxName($tableName, $fields, $indexType = Varien_Db_Adapter_Interface::INDEX_TYPE_INDEX) + { + return $this->getConnection(self::DEFAULT_READ_RESOURCE) + ->getIndexName($this->getTableName($tableName), $fields, $indexType); + } + /** + * Retrieve 32bit UNIQUE HASH for a Table foreign key + * + * @param string $priTableName the target table name + * @param string $priColumnName the target table column name + * @param string $refTableName the reference table name + * @param string $refColumnName the reference table column name + * @return string + */ + public function getFkName($priTableName, $priColumnName, $refTableName, $refColumnName) + { + return $this->getConnection(self::DEFAULT_READ_RESOURCE) + ->getForeignKeyName($this->getTableName($priTableName), $priColumnName, + $this->getTableName($refTableName), $refColumnName); + } } diff --git a/app/code/core/Mage/Core/Model/Resource/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Abstract.php index 4a152f11..a1420239 100644 --- a/app/code/core/Mage/Core/Model/Resource/Abstract.php +++ b/app/code/core/Mage/Core/Model/Resource/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,8 +33,14 @@ */ abstract class Mage_Core_Model_Resource_Abstract { + /** + * Main constructor + */ public function __construct() { + /** + * Please override this one instead of overriding real __construct constructor + */ $this->_construct(); } @@ -122,39 +128,24 @@ public function rollBack() /** * Format date to internal format * - * @param string | Zend_Date $date - * @param bool $includeTime - * @return string + * @param string|Zend_Date $date + * @param bool $includeTime + * @return string */ public function formatDate($date, $includeTime=true) { - if ($date instanceof Zend_Date) { - if ($includeTime) { - return $date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); - } - else { - return $date->toString(Varien_Date::DATE_INTERNAL_FORMAT); - } - } - - if (empty($date)) { - return new Zend_Db_Expr('NULL'); - } - - if (!is_numeric($date)) { - $date = strtotime($date); - } - if ($includeTime) { - return date('Y-m-d H:i:s', $date); - } - else { - return date('Y-m-d', $date); - } + return Varien_Date::formatDate($date, $includeTime); } + /** + * Convert internal date to UNIX timestamp + * + * @param string $str + * @return int + */ public function mktime($str) { - return strtotime($str); + return Varien_Date::toTimestamp($str); } /** @@ -164,6 +155,7 @@ public function mktime($str) * @param string $field * @param mixed $defaultValue * @param bool $unsetEmpty + * @return Mage_Core_Model_Resource_Abstract */ protected function _serializeField(Varien_Object $object, $field, $defaultValue = null, $unsetEmpty = false) { @@ -180,6 +172,8 @@ protected function _serializeField(Varien_Object $object, $field, $defaultValue } elseif (is_array($value) || is_object($value)) { $object->setData($field, serialize($value)); } + + return $this; } /** @@ -198,4 +192,49 @@ protected function _unserializeField(Varien_Object $object, $field, $defaultValu $object->setData($field, unserialize($value)); } } + + /** + * Prepare data for passed table + * + * @param Varien_Object $object + * @param string $table + * @return array + */ + protected function _prepareDataForTable(Varien_Object $object, $table) + { + $data = array(); + $fields = $this->_getWriteAdapter()->describeTable($table); + foreach (array_keys($fields) as $field) { + if ($object->hasData($field)) { + $fieldValue = $object->getData($field); + if ($fieldValue instanceof Zend_Db_Expr) { + $data[$field] = $fieldValue; + } else { + if (null !== $fieldValue) { + $fieldValue = $this->_prepareTableValueForSave($fieldValue, $fields[$field]['DATA_TYPE']); + $data[$field] = $this->_getWriteAdapter()->prepareColumnValue($fields[$field], $fieldValue); + } else if (!empty($fields[$field]['NULLABLE'])) { + $data[$field] = null; + } + } + } + } + return $data; + } + + /** + * Prepare value for save + * + * @param mixed $value + * @param string $type + * @return mixed + */ + protected function _prepareTableValueForSave($value, $type) + { + $type = strtolower($type); + if ($type == 'decimal' || $type == 'numeric' || $type == 'float') { + $value = Mage::app()->getLocale()->getNumber($value); + } + return $value; + } } diff --git a/app/code/core/Mage/Core/Model/Resource/Cache.php b/app/code/core/Mage/Core/Model/Resource/Cache.php new file mode 100644 index 00000000..bf3f05f4 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Cache.php @@ -0,0 +1,100 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Cache resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Cache extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/cache_option', 'code'); + } + + /** + * Get all cache options + * + * @return array | false + */ + public function getAllOptions() + { + $adapter = $this->_getReadAdapter(); + if ($adapter) { + /** + * Check if table exist (it protect upgrades. cache settings checked before upgrades) + */ + if ($adapter->isTableExists($this->getMainTable())) { + $select = $adapter->select() + ->from($this->getMainTable(), array('code', 'value')); + return $adapter->fetchPairs($select); + } + } + return false; + } + + /** + * Save all options to option table + * + * @param array $options + * @return Mage_Core_Model_Resource_Cache + * @throws Exception + */ + public function saveAllOptions($options) + { + $adapter = $this->_getWriteAdapter(); + if (!$adapter) { + return $this; + } + + $data = array(); + foreach ($options as $code => $value) { + $data[] = array($code, $value); + } + + $adapter->beginTransaction(); + try { + $this->_getWriteAdapter()->delete($this->getMainTable()); + if ($data) { + $this->_getWriteAdapter()->insertArray($this->getMainTable(), array('code', 'value'), $data); + } + } catch (Exception $e) { + $adapter->rollback(); + throw $e; + } + $adapter->commit(); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Config.php b/app/code/core/Mage/Core/Model/Resource/Config.php new file mode 100644 index 00000000..d4210b28 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Config.php @@ -0,0 +1,228 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Resource Resource Model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Config extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/config_data', 'config_id'); + } + + /** + * Load configuration values into xml config object + * + * @param Mage_Core_Model_Config $xmlConfig + * @param string $condition + * @return Mage_Core_Model_Resource_Config + */ + public function loadToXml(Mage_Core_Model_Config $xmlConfig, $condition = null) + { + $read = $this->_getReadAdapter(); + if (!$read) { + return $this; + } + + $websites = array(); + $select = $read->select() + ->from($this->getTable('core/website'), array('website_id', 'code', 'name')); + $rowset = $read->fetchAssoc($select); + foreach ($rowset as $w) { + $xmlConfig->setNode('websites/'.$w['code'].'/system/website/id', $w['website_id']); + $xmlConfig->setNode('websites/'.$w['code'].'/system/website/name', $w['name']); + $websites[$w['website_id']] = array('code' => $w['code']); + } + + $stores = array(); + $select = $read->select() + ->from($this->getTable('core/store'), array('store_id', 'code', 'name', 'website_id')) + ->order('sort_order ' . Varien_Db_Select::SQL_ASC); + $rowset = $read->fetchAssoc($select); + foreach ($rowset as $s) { + if (!isset($websites[$s['website_id']])) { + continue; + } + $xmlConfig->setNode('stores/'.$s['code'].'/system/store/id', $s['store_id']); + $xmlConfig->setNode('stores/'.$s['code'].'/system/store/name', $s['name']); + $xmlConfig->setNode('stores/'.$s['code'].'/system/website/id', $s['website_id']); + $xmlConfig->setNode('websites/'.$websites[$s['website_id']]['code'].'/system/stores/'.$s['code'], $s['store_id']); + $stores[$s['store_id']] = array('code'=>$s['code']); + $websites[$s['website_id']]['stores'][$s['store_id']] = $s['code']; + } + + $substFrom = array(); + $substTo = array(); + + // load all configuration records from database, which are not inherited + $select = $read->select() + ->from($this->getMainTable(), array('scope', 'scope_id', 'path', 'value')); + if (!is_null($condition)) { + $select->where($condition); + } + $rowset = $read->fetchAll($select); + + + // set default config values from database + foreach ($rowset as $r) { + if ($r['scope'] !== 'default') { + continue; + } + $value = str_replace($substFrom, $substTo, $r['value']); + $xmlConfig->setNode('default/' . $r['path'], $value); + } + + // inherit default config values to all websites + $extendSource = $xmlConfig->getNode('default'); + foreach ($websites as $id=>$w) { + $websiteNode = $xmlConfig->getNode('websites/' . $w['code']); + $websiteNode->extend($extendSource); + } + + $deleteWebsites = array(); + // set websites config values from database + foreach ($rowset as $r) { + if ($r['scope'] !== 'websites') { + continue; + } + $value = str_replace($substFrom, $substTo, $r['value']); + if (isset($websites[$r['scope_id']])) { + $nodePath = sprintf('websites/%s/%s', $websites[$r['scope_id']]['code'], $r['path']); + $xmlConfig->setNode($nodePath, $value); + } else { + $deleteWebsites[$r['scope_id']] = $r['scope_id']; + } + } + + // extend website config values to all associated stores + foreach ($websites as $website) { + $extendSource = $xmlConfig->getNode('websites/' . $website['code']); + if (isset($website['stores'])) { + foreach ($website['stores'] as $sCode) { + $storeNode = $xmlConfig->getNode('stores/'.$sCode); + /** + * $extendSource DO NOT need overwrite source + */ + $storeNode->extend($extendSource, false); + } + } + } + + $deleteStores = array(); + // set stores config values from database + foreach ($rowset as $r) { + if ($r['scope'] !== 'stores') { + continue; + } + $value = str_replace($substFrom, $substTo, $r['value']); + if (isset($stores[$r['scope_id']])) { + $nodePath = sprintf('stores/%s/%s', $stores[$r['scope_id']]['code'], $r['path']); + $xmlConfig->setNode($nodePath, $value); + } else { + $deleteStores[$r['scope_id']] = $r['scope_id']; + } + } + + if ($deleteWebsites) { + $this->_getWriteAdapter()->delete($this->getMainTable(), array( + 'scope = ?' => 'websites', + 'scope_id IN(?)' => $deleteWebsites, + )); + } + + if ($deleteStores) { + $this->_getWriteAdapter()->delete($this->getMainTable(), array( + 'scope=?' => 'stores', + 'scope_id IN(?)' => $deleteStores, + )); + } + return $this; + } + + /** + * Save config value + * + * @param string $path + * @param string $value + * @param string $scope + * @param int $scopeId + * @return Mage_Core_Model_Resource_Config + */ + public function saveConfig($path, $value, $scope, $scopeId) + { + $writeAdapter = $this->_getWriteAdapter(); + $select = $writeAdapter->select() + ->from($this->getMainTable()) + ->where('path = ?', $path) + ->where('scope = ?', $scope) + ->where('scope_id = ?', $scopeId); + $row = $writeAdapter->fetchRow($select); + + $newData = array( + 'scope' => $scope, + 'scope_id' => $scopeId, + 'path' => $path, + 'value' => $value + ); + + if ($row) { + $whereCondition = array($this->getIdFieldName() . '=?' => $row[$this->getIdFieldName()]); + $writeAdapter->update($this->getMainTable(), $newData, $whereCondition); + } else { + $writeAdapter->insert($this->getMainTable(), $newData); + } + return $this; + } + + /** + * Delete config value + * + * @param string $path + * @param string $scope + * @param int $scopeId + * @return Mage_Core_Model_Resource_Config + */ + public function deleteConfig($path, $scope, $scopeId) + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable(), array( + $adapter->quoteInto('path = ?', $path), + $adapter->quoteInto('scope = ?', $scope), + $adapter->quoteInto('scope_id = ?', $scopeId) + )); + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Config/Data.php b/app/code/core/Mage/Core/Model/Resource/Config/Data.php new file mode 100644 index 00000000..688002af --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Config/Data.php @@ -0,0 +1,91 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core config data resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Config_Data extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/config_data', 'config_id'); + } + + /** + * Convert array to comma separated value + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Config_Data + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getId()) { + $this->_checkUnique($object); + } + + if (is_array($object->getValue())) { + $object->setValue(join(',', $object->getValue())); + } + return parent::_beforeSave($object); + } + + /** + * Validate unique configuration data before save + * Set id to object if exists configuration instead of throw exception + * + * @param Mage_Core_Model_Config_Data $object + * @return Mage_Core_Model_Resource_Config_Data + */ + protected function _checkUnique(Mage_Core_Model_Abstract $object) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), array($this->getIdFieldName())) + ->where('scope = :scope') + ->where('scope_id = :scope_id') + ->where('path = :path'); + $bind = array( + 'scope' => $object->getScope(), + 'scope_id' => $object->getScopeId(), + 'path' => $object->getPath() + ); + + $configId = $this->_getReadAdapter()->fetchOne($select, $bind); + if ($configId) { + $object->setId($configId); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Config/Data/Collection.php b/app/code/core/Mage/Core/Model/Resource/Config/Data/Collection.php new file mode 100644 index 00000000..69807da1 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Config/Data/Collection.php @@ -0,0 +1,85 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Config data collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Config_Data_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('core/config_data'); + } + + /** + * Add scope filter to collection + * + * @param string $scope + * @param int $scopeId + * @param string $section + * @return Mage_Core_Model_Resource_Config_Data_Collection + */ + public function addScopeFilter($scope, $scopeId, $section) + { + $this->addFieldToFilter('scope', $scope); + $this->addFieldToFilter('scope_id', $scopeId); + $this->addFieldToFilter('path', array('like' => $section . '/%')); + return $this; + } + + /** + * Add path filter + * + * @param string $section + * @return Mage_Core_Model_Resource_Config_Data_Collection + */ + public function addPathFilter($section) + { + $this->addFieldToFilter('path', array('like' => $section . '/%')); + return $this; + } + + /** + * Add value filter + * + * @param int|string $value + * @return Mage_Core_Model_Resource_Config_Data_Collection + */ + public function addValueFilter($value) + { + $this->addFieldToFilter('value', array('like' => $value)); + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Db/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Db/Abstract.php new file mode 100644 index 00000000..93d5aff6 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Db/Abstract.php @@ -0,0 +1,776 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Abstract resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Resource_Db_Abstract extends Mage_Core_Model_Resource_Abstract +{ + /** + * @deprecated since 1.5.0.0 + */ + const CHECKSUM_KEY_NAME= 'Checksum'; + + /** + * Cached resources singleton + * + * @var Mage_Core_Model_Resource + */ + protected $_resources; + + /** + * Prefix for resources that will be used in this resource model + * + * @var string + */ + protected $_resourcePrefix; + + /** + * Connections cache for this resource model + * + * @var array + */ + protected $_connections = array(); + + /** + * Resource model name that contains entities (names of tables) + * + * @var string + */ + protected $_resourceModel; + + /** + * Tables used in this resource model + * + * @var array + */ + protected $_tables = array(); + + /** + * Main table name + * + * @var string + */ + protected $_mainTable; + + /** + * Main table primary key field name + * + * @var string + */ + protected $_idFieldName; + + /** + * Primery key auto increment flag + * + * @var bool + */ + protected $_isPkAutoIncrement = true; + + /** + * Use is object new method for save of object + * + * @var boolean + */ + protected $_useIsObjectNew = false; + + /** + * Fields List for update in forsedSave + * + * @var array + */ + protected $_fieldsForUpdate = array(); + + /** + * Fields of main table + * + * @var array + */ + protected $_mainTableFields; + + /** + * Main table unique keys field names + * could array( + * array('field' => 'db_field_name1', 'title' => 'Field 1 should be unique') + * array('field' => 'db_field_name2', 'title' => 'Field 2 should be unique') + * array( + * 'field' => array('db_field_name3', 'db_field_name3'), + * 'title' => 'Field 3 and Field 4 combination should be unique' + * ) + * ) + * or string 'my_field_name' - will be autoconverted to + * array( array( 'field' => 'my_field_name', 'title' => 'my_field_name' ) ) + * + * @var array + */ + protected $_uniqueFields = null; + + /** + * Serializable fields declaration + * Structure: array( + * <field_name> => array( + * <default_value_for_serialization>, + * <default_for_unserialization>, + * <whether_to_unset_empty_when serializing> // optional parameter + * ), + * ) + * + * @var array + */ + protected $_serializableFields = array(); + + /** + * Standard resource model initialization + * + * @param string $mainTable + * @param string $idFieldName + * @return Mage_Core_Model_Resource_Abstract + */ + protected function _init($mainTable, $idFieldName) + { + $this->_setMainTable($mainTable, $idFieldName); + } + + /** + * Initialize connections and tables for this resource model + * If one or both arguments are string, will be used as prefix + * If $tables is null and $connections is string, $tables will be the same + * + * @param string|array $connections + * @param string|array|null $tables + * @return Mage_Core_Model_Resource_Abstract + */ + protected function _setResource($connections, $tables = null) + { + $this->_resources = Mage::getSingleton('core/resource'); + + if (is_array($connections)) { + foreach ($connections as $k=>$v) { + $this->_connections[$k] = $this->_resources->getConnection($v); + } + } else if (is_string($connections)) { + $this->_resourcePrefix = $connections; + } + + if (is_null($tables) && is_string($connections)) { + $this->_resourceModel = $this->_resourcePrefix; + } else if (is_array($tables)) { + foreach ($tables as $k => $v) { + $this->_tables[$k] = $this->_resources->getTableName($v); + } + } else if (is_string($tables)) { + $this->_resourceModel = $tables; + } + return $this; + } + + /** + * Set main entity table name and primary key field name + * If field name is ommited {table_name}_id will be used + * + * @param string $mainTable + * @param string|null $idFieldName + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _setMainTable($mainTable, $idFieldName = null) + { + $mainTableArr = explode('/', $mainTable); + + if (!empty($mainTableArr[1])) { + if (empty($this->_resourceModel)) { + $this->_setResource($mainTableArr[0]); + } + $this->_setMainTable($mainTableArr[1], $idFieldName); + } else { + $this->_mainTable = $mainTable; + if (is_null($idFieldName)) { + $idFieldName = $mainTable . '_id'; + } + $this->_idFieldName = $idFieldName; + } + + return $this; + } + + /** + * Get primary key field name + * + * @return string + */ + public function getIdFieldName() + { + if (empty($this->_idFieldName)) { + Mage::throwException(Mage::helper('core')->__('Empty identifier field name')); + } + return $this->_idFieldName; + } + + /** + * Returns main table name - extracted from "module/table" style and + * validated by db adapter + * + * @return string + */ + public function getMainTable() + { + if (empty($this->_mainTable)) { + Mage::throwException(Mage::helper('core')->__('Empty main table name')); + } + return $this->getTable($this->_mainTable); + } + + /** + * Get table name for the entity, validated by db adapter + * + * @param string $entityName + * @return string + */ + public function getTable($entityName) + { + if (is_array($entityName)) { + $cacheName = join('@', $entityName); + list($entityName, $entitySuffix) = $entityName; + } else { + $cacheName = $entityName; + $entitySuffix = null; + } + + if (isset($this->_tables[$cacheName])) { + return $this->_tables[$cacheName]; + } + + if (strpos($entityName, '/')) { + if (!is_null($entitySuffix)) { + $modelEntity = array($entityName, $entitySuffix); + } else { + $modelEntity = $entityName; + } + $this->_tables[$cacheName] = $this->_resources->getTableName($modelEntity); + } else if (!empty($this->_resourceModel)) { + $entityName = sprintf('%s/%s', $this->_resourceModel, $entityName); + if (!is_null($entitySuffix)) { + $modelEntity = array($entityName, $entitySuffix); + } else { + $modelEntity = $entityName; + } + $this->_tables[$cacheName] = $this->_resources->getTableName($modelEntity); + } else { + if (!is_null($entitySuffix)) { + $entityName .= '_' . $entitySuffix; + } + $this->_tables[$cacheName] = $entityName; + } + return $this->_tables[$cacheName]; + } + + + /** + * Retrieve table name for the entity separated value + * + * @param string $entityName + * @param string $valueType + * @return string + */ + public function getValueTable($entityName, $valueType) + { + return $this->getTable(array($entityName, $valueType)); + } + + /** + * Get connection by name or type + * + * @param string $connectionName + * @return Zend_Db_Adapter_Abstract + */ + protected function _getConnection($connectionName) + { + if (isset($this->_connections[$connectionName])) { + return $this->_connections[$connectionName]; + } + if (!empty($this->_resourcePrefix)) { + $this->_connections[$connectionName] = $this->_resources->getConnection( + $this->_resourcePrefix . '_' . $connectionName); + } else { + $this->_connections[$connectionName] = $this->_resources->getConnection($connectionName); + } + + return $this->_connections[$connectionName]; + } + + /** + * Retrieve connection for read data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getReadAdapter() + { + $writeAdapter = $this->_getWriteAdapter(); + if ($writeAdapter && $writeAdapter->getTransactionLevel() > 0) { + // if transaction is started we should use write connection for reading + return $writeAdapter; + } + return $this->_getConnection('read'); + } + + /** + * Retrieve connection for write data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getWriteAdapter() + { + return $this->_getConnection('write'); + } + + /** + * Temporary resolving collection compatibility + * + * @return Varien_Db_Adapter_Interface + */ + public function getReadConnection() + { + return $this->_getReadAdapter(); + } + + /** + * Load an object + * + * @param Mage_Core_Model_Abstract $object + * @param mixed $value + * @param string $field field to load by (defaults to model id) + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (is_null($field)) { + $field = $this->getIdFieldName(); + } + + $read = $this->_getReadAdapter(); + if ($read && !is_null($value)) { + $select = $this->_getLoadSelect($field, $value, $object); + $data = $read->fetchRow($select); + + if ($data) { + $object->setData($data); + } + } + + $this->unserializeFields($object); + $this->_afterLoad($object); + + return $this; + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Abstract $object + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $field = $this->_getReadAdapter()->quoteIdentifier(sprintf('%s.%s', $this->getMainTable(), $field)); + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where($field . '=?', $value); + return $select; + } + + /** + * Save object object data + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function save(Mage_Core_Model_Abstract $object) + { + if ($object->isDeleted()) { + return $this->delete($object); + } + + $this->_serializeFields($object); + $this->_beforeSave($object); + $this->_checkUnique($object); + if (!is_null($object->getId()) && (!$this->_useIsObjectNew || !$object->isObjectNew())) { + $condition = $this->_getWriteAdapter()->quoteInto($this->getIdFieldName().'=?', $object->getId()); + /** + * Not auto increment primary key support + */ + if ($this->_isPkAutoIncrement) { + $data = $this->_prepareDataForSave($object); + unset($data[$this->getIdFieldName()]); + $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); + } else { + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), array($this->getIdFieldName())) + ->where($condition); + if ($this->_getWriteAdapter()->fetchOne($select) !== false) { + $data = $this->_prepareDataForSave($object); + unset($data[$this->getIdFieldName()]); + if (!empty($data)) { + $this->_getWriteAdapter()->update($this->getMainTable(), $data, $condition); + } + } else { + $this->_getWriteAdapter()->insert($this->getMainTable(), $this->_prepareDataForSave($object)); + } + } + } else { + $bind = $this->_prepareDataForSave($object); + if ($this->_isPkAutoIncrement) { + unset($bind[$this->getIdFieldName()]); + } + $this->_getWriteAdapter()->insert($this->getMainTable(), $bind); + + $object->setId($this->_getWriteAdapter()->lastInsertId($this->getMainTable())); + + if ($this->_useIsObjectNew) { + $object->isObjectNew(false); + } + } + + $this->unserializeFields($object); + $this->_afterSave($object); + + return $this; + } + + /** + * Forsed save object data + * forsed update If duplicate unique key data + * + * @deprecated + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function forsedSave(Mage_Core_Model_Abstract $object) + { + $this->_beforeSave($object); + $bind = $this->_prepareDataForSave($object); + $adapter = $this->_getWriteAdapter(); + // update + if (!is_null($object->getId()) && $this->_isPkAutoIncrement) { + unset($bind[$this->getIdFieldName()]); + $condition = $adapter->quoteInto($this->getIdFieldName().'=?', $object->getId()); + $adapter->update($this->getMainTable(), $bind, $condition); + } else { + $adapter->insertOnDuplicate($this->getMainTable(), $bind, $this->_fieldsForUpdate); + $object->setId($adapter->lastInsertId($this->getMainTable())); + } + + $this->_afterSave($object); + + return $this; + } + + /** + * Delete the object + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function delete(Mage_Core_Model_Abstract $object) + { + $this->_beforeDelete($object); + $this->_getWriteAdapter()->delete( + $this->getMainTable(), + $this->_getWriteAdapter()->quoteInto($this->getIdFieldName() . '=?', $object->getId()) + ); + $this->_afterDelete($object); + return $this; + } + + /** + * Add unique field restriction + * + * @param array|string $field + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function addUniqueField($field) + { + if (is_null($this->_uniqueFields)) { + $this->_initUniqueFields(); + } + if (is_array($this->_uniqueFields) ) { + $this->_uniqueFields[] = $field; + } + return $this; + } + + /** + * Reset unique fields restrictions + * + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function resetUniqueField() + { + $this->_uniqueFields = array(); + return $this; + } + + /** + * Unserialize serializeable object fields + * + * @param Mage_Core_Model_Abstract $object + */ + public function unserializeFields(Mage_Core_Model_Abstract $object) + { + foreach ($this->_serializableFields as $field => $parameters) { + list($serializeDefault, $unserializeDefault) = $parameters; + $this->_unserializeField($object, $field, $unserializeDefault); + } + } + + /** + * Initialize unique fields + * + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array(); + return $this; + } + + /** + * Get configuration of all unique fields + * + * @return array + */ + public function getUniqueFields() + { + if (is_null($this->_uniqueFields)) { + $this->_initUniqueFields(); + } + return $this->_uniqueFields; + } + + /** + * Prepare data for save + * + * @param Mage_Core_Model_Abstract $object + * @return array + */ + protected function _prepareDataForSave(Mage_Core_Model_Abstract $object) + { + return $this->_prepareDataForTable($object, $this->getMainTable()); + } + + /** + * Check that model data fields that can be saved + * has really changed comparing with origData + * + * @param Mage_Core_Model_Abstract $object + * @return boolean + */ + public function hasDataChanged($object) + { + if (!$object->getOrigData()) { + return true; + } + + $fields = $this->_getWriteAdapter()->describeTable($this->getMainTable()); + foreach (array_keys($fields) as $field) { + if ($object->getOrigData($field) != $object->getData($field)) { + return true; + } + } + + return false; + } + + /** + * Prepare value for save + * + * @param mixed $value + * @param string $type + * @return mixed + */ + protected function _prepareValueForSave($value, $type) + { + return $this->_prepareTableValueForSave($value, $type); + } + + /** + * Check for unique values existence + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + * @throws Mage_Core_Exception + */ + protected function _checkUnique(Mage_Core_Model_Abstract $object) + { + $existent = array(); + $fields = $this->getUniqueFields(); + if (!empty($fields)) { + if (!is_array($fields)) { + $this->_uniqueFields = array( + array( + 'field' => $fields, + 'title' => $fields + )); + } + + $data = new Varien_Object($this->_prepareDataForSave($object)); + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable()); + + foreach ($fields as $unique) { + $select->reset(Zend_Db_Select::WHERE); + + if (is_array($unique['field'])) { + foreach ($unique['field'] as $field) { + $select->where($field . '=?', trim($data->getData($field))); + } + } else { + $select->where($unique['field'] . '=?', trim($data->getData($unique['field']))); + } + + if ($object->getId() || $object->getId() === '0') { + $select->where($this->getIdFieldName() . '!=?', $object->getId()); + } + + $test = $this->_getWriteAdapter()->fetchRow($select); + if ($test) { + $existent[] = $unique['title']; + } + } + } + + if (!empty($existent)) { + if (count($existent) == 1 ) { + $error = Mage::helper('core')->__('%s already exists.', $existent[0]); + } else { + $error = Mage::helper('core')->__('%s already exist.', implode(', ', $existent)); + } + Mage::throwException($error); + } + return $this; + } + + /** + * After load + * + * @param Mage_Core_Model_Abstract $object + */ + public function afterLoad(Mage_Core_Model_Abstract $object) + { + $this->_afterLoad($object); + } + + /** + * Perform actions after object load + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + return $this; + } + + /** + * Perform actions before object save + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + return $this; + } + + /** + * Perform actions after object save + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + return $this; + } + + /** + * Perform actions before object delete + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _beforeDelete(Mage_Core_Model_Abstract $object) + { + return $this; + } + + /** + * Perform actions after object delete + * + * @param Varien_Object $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _afterDelete(Mage_Core_Model_Abstract $object) + { + return $this; + } + + /** + * Serialize serializeable fields of the object + * + * @param Mage_Core_Model_Abstract $object + */ + protected function _serializeFields(Mage_Core_Model_Abstract $object) + { + foreach ($this->_serializableFields as $field => $parameters) { + list($serializeDefault, $unserializeDefault) = $parameters; + $this->_serializeField($object, $field, $serializeDefault, isset($parameters[2])); + } + } + + /** + * Retrieve table checksum + * + * @param string|array $table + * @return int|array + */ + public function getChecksum($table) + { + if (!$this->_getReadAdapter()) { + return false; + } + $checksum = $this->_getReadAdapter()->getTablesChecksum($table); + if (count($checksum) == 1) { + return $checksum[$table]; + } + return $checksum; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php new file mode 100644 index 00000000..6e8341b6 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Db/Collection/Abstract.php @@ -0,0 +1,719 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Abstract Core Resource Collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Resource_Db_Collection_Abstract extends Varien_Data_Collection_Db +{ + const CACHE_TAG = 'COLLECTION_DATA'; + + /** + * Model name + * + * @var string + */ + protected $_model; + + /** + * Resource model name + * + * @var string + */ + protected $_resourceModel; + + /** + * Resource instance + * + * @var Mage_Core_Model_Resource_Db_Abstract + */ + protected $_resource; + + /** + * Fields to select in query + * + * @var array|null + */ + protected $_fieldsToSelect = null; + + /** + * Fields initial fields to select like id_field + * + * @var array|null + */ + protected $_initialFieldsToSelect = null; + + /** + * Fields to select changed flag + * + * @var booleam + */ + protected $_fieldsToSelectChanged = false; + + /** + * Store joined tables here + * + * @var array + */ + protected $_joinedTables = array(); + + /** + * Collection main table + * + * @var string + */ + protected $_mainTable = null; + + /** + * Reset items data changed flag + * + * @var boolean + */ + protected $_resetItemsDataChanged = false; + + /** + * Name prefix of events that are dispatched by model + * + * @var string + */ + protected $_eventPrefix = ''; + + /** + * Name of event parameter + * + * @var string + */ + protected $_eventObject = ''; + + /** + * Use analytic function flag + * If true - allows to prepare final select with analytic function + * + * @var bool + */ + protected $_useAnalyticFunction = false; + + /** + * Collection constructor + * + * @param Mage_Core_Model_Resource_Db_Abstract $resource + */ + public function __construct($resource = null) + { + parent::__construct(); + $this->_construct(); + $this->_resource = $resource; + $this->setConnection($this->getResource()->getReadConnection()); + $this->_initSelect(); + } + + /** + * Initialization here + * + */ + protected function _construct() + { + + } + + /** + * Retrieve main table + * + * @return string + */ + public function getMainTable() + { + if ($this->_mainTable === null) { + $this->setMainTable($this->getResource()->getMainTable()); + } + + return $this->_mainTable; + } + + /** + * Set main collection table + * + * @param string $table + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function setMainTable($table) + { + if (strpos($table, '/') !== false) { + $table = $this->getTable($table); + } + + if ($this->_mainTable !== null && $table !== $this->_mainTable && $this->getSelect() !== null) { + $from = $this->getSelect()->getPart(Zend_Db_Select::FROM); + if (isset($from['main_table'])) { + $from['main_table']['tableName'] = $table; + } + $this->getSelect()->setPart(Zend_Db_Select::FROM, $from); + } + + $this->_mainTable = $table; + return $this; + } + + /** + * Init collection select + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _initSelect() + { + $this->getSelect()->from(array('main_table' => $this->getMainTable())); + return $this; + } + + /** + * Get Zend_Db_Select instance and applies fields to select if needed + * + * @return Varien_Db_Select + */ + public function getSelect() + { + if ($this->_select && $this->_fieldsToSelectChanged) { + $this->_fieldsToSelectChanged = false; + $this->_initSelectFields(); + } + return parent::getSelect(); + } + + /** + * Init fields for select + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _initSelectFields() + { + $columns = $this->_select->getPart(Zend_Db_Select::COLUMNS); + $columnsToSelect = array(); + foreach ($columns as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($correlationName !== 'main_table') { // Add joined fields to select + if ($column instanceof Zend_Db_Expr) { + $column = $column->__toString(); + } + $key = ($alias !== null ? $alias : $column); + $columnsToSelect[$key] = $columnEntry; + } + } + + $columns = $columnsToSelect; + + $columnsToSelect = array_keys($columnsToSelect); + + if ($this->_fieldsToSelect !== null) { + $insertIndex = 0; + foreach ($this->_fieldsToSelect as $alias => $field) { + if (!is_string($alias)) { + $alias = null; + } + + if ($field instanceof Zend_Db_Expr) { + $column = $field->__toString(); + } else { + $column = $field; + } + + if (($alias !== null && in_array($alias, $columnsToSelect)) || + // If field already joined from another table + ($alias === null && isset($alias, $columnsToSelect))) { + continue; + } + + $columnEntry = array('main_table', $field, $alias); + array_splice($columns, $insertIndex, 0, array($columnEntry)); // Insert column + $insertIndex ++; + + } + } else { + array_unshift($columns, array('main_table', '*', null)); + } + + $this->_select->setPart(Zend_Db_Select::COLUMNS, $columns); + + return $this; + } + + /** + * Retrieve initial fields to select like id field + * + * @return array + */ + protected function _getInitialFieldsToSelect() + { + if ($this->_initialFieldsToSelect === null) { + $this->_initialFieldsToSelect = array(); + $this->_initInitialFieldsToSelect(); + } + + return $this->_initialFieldsToSelect; + } + + /** + * Initialize initial fields to select like id field + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _initInitialFieldsToSelect() + { + $idFieldName = $this->getResource()->getIdFieldName(); + if ($idFieldName) { + $this->_initialFieldsToSelect[] = $idFieldName; + } + return $this; + } + + /** + * Add field to select + * + * @param string|array $field + * @param string|null $alias + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function addFieldToSelect($field, $alias = null) + { + if ($field === '*') { // If we will select all fields + $this->_fieldsToSelect = null; + $this->_fieldsToSelectChanged = true; + return $this; + } + + if (is_array($field)) { + if ($this->_fieldsToSelect === null) { + $this->_fieldsToSelect = $this->_getInitialFieldsToSelect(); + } + + foreach ($field as $key => $value) { + $this->addFieldToSelect( + $value, + (is_string($key) ? $key : null), + false + ); + } + + $this->_fieldsToSelectChanged = true; + return $this; + } + + if ($alias === null) { + $this->_fieldsToSelect[] = $field; + } else { + $this->_fieldsToSelect[$alias] = $field; + } + + $this->_fieldsToSelectChanged = true; + return $this; + } + + /** + * Add attribute expression (SUM, COUNT, etc) + * Example: ('sub_total', 'SUM({{attribute}})', 'revenue') + * Example: ('sub_total', 'SUM({{revenue}})', 'revenue') + * For some functions like SUM use groupByAttribute. + * + * @param string $alias + * @param string $expression + * @param array $fields + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function addExpressionFieldToSelect($alias, $expression, $fields) + { + // validate alias + if (!is_array($fields)) { + $fields = array($fields=>$fields); + } + + $fullExpression = $expression; + foreach ($fields as $fieldKey=>$fieldItem) { + $fullExpression = str_replace('{{' . $fieldKey . '}}', $fieldItem, $fullExpression); + } + + $this->getSelect()->columns(array($alias=>$fullExpression)); + + return $this; + } + + /** + * Removes field from select + * + * @param string|null $field + * @param boolean $isAlias Alias identifier + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function removeFieldFromSelect($field, $isAlias = false) + { + if ($isAlias) { + if (isset($this->_fieldsToSelect[$field])) { + unset($this->_fieldsToSelect[$field]); + } + } else { + foreach ($this->_fieldsToSelect as $key => $value) { + if ($value === $field) { + unset($this->_fieldsToSelect[$key]); + break; + } + } + } + + $this->_fieldsToSelectChanged = true; + return $this; + } + + /** + * Removes all fields from select + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function removeAllFieldsFromSelect() + { + $this->_fieldsToSelect = $this->_getInitialFieldsToSelect(); + $this->_fieldsToSelectChanged = true; + return $this; + } + + /** + * Standard resource collection initialization + * + * @param string $model + * @param Mage_Core_Model_Resource_Db_Abstract $resourceModel + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _init($model, $resourceModel = null) + { + $this->setModel($model); + if (is_null($resourceModel)) { + $resourceModel = $model; + } + $this->setResourceModel($resourceModel); + return $this; + } + + /** + * Set model name for collection items + * + * @param string $model + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function setModel($model) + { + if (is_string($model)) { + $this->_model = $model; + $this->setItemObjectClass(Mage::getConfig()->getModelClassName($model)); + } + return $this; + } + + /** + * Get model instance + * + * @param array $args + * @return Varien_Object + */ + public function getModelName($args = array()) + { + return $this->_model; + } + + /** + * Set resource model name for collection items + * + * @param string $model + */ + public function setResourceModel($model) + { + $this->_resourceModel = $model; + } + + /** + * Retrieve resource model name + * + * @return string + */ + public function getResourceModelName() + { + return $this->_resourceModel; + } + + /** + * Get resource instance + * + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function getResource() + { + if (empty($this->_resource)) { + $this->_resource = Mage::getResourceModel($this->getResourceModelName()); + } + return $this->_resource; + } + + /** + * Retrieve table name + * + * @param string $table + * @return string + */ + public function getTable($table) + { + return $this->getResource()->getTable($table); + } + + /** + * Retrieve all ids for collection + * + * @return array + */ + public function getAllIds() + { + $idsSelect = clone $this->getSelect(); + $idsSelect->reset(Zend_Db_Select::ORDER); + $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT); + $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET); + $idsSelect->reset(Zend_Db_Select::COLUMNS); + + $idsSelect->columns($this->getResource()->getIdFieldName(), 'main_table'); + return $this->getConnection()->fetchCol($idsSelect); + } + + public function getData() + { + if ($this->_data === null) { + + + $this->_renderFilters() + ->_renderOrders() + ->_renderLimit(); + /** + * Prepare select for execute + * @var string $query + */ + $query = $this->_prepareSelect($this->getSelect()); + $this->_data = $this->_fetchAll($query, $this->_bindParams); + $this->_afterLoadData(); + } + return $this->_data; + } + + /** + * Prepare select for load + * + * @return string + */ + protected function _prepareSelect(Varien_Db_Select $select) + { + $helper = Mage::getResourceHelper('core'); + + $unionParts = $select->getPart(Zend_Db_Select::UNION); + if (!empty($unionParts)) { + $select = $helper->limitUnion($select); + } + + if ($this->_useAnalyticFunction) { + return $helper->getQueryUsingAnalyticFunction($select); + } + + return (string)$select; + } + /** + * Join table to collection select + * + * @param string $table + * @param string $cond + * @param string $cols + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function join($table, $cond, $cols = '*') + { + if (is_array($table)) { + foreach ($table as $k => $v) { + $alias = $k; + $table = $v; + break; + } + } else { + $alias = $table; + } + + if (!isset($this->_joinedTables[$table])) { + $this->getSelect()->join( + array($alias => $this->getTable($table)), + $cond, + $cols + ); + $this->_joinedTables[$alias] = true; + } + return $this; + } + + /** + * Redeclare before load method for adding event + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _beforeLoad() + { + parent::_beforeLoad(); + Mage::dispatchEvent('core_collection_abstract_load_before', array('collection' => $this)); + if ($this->_eventPrefix && $this->_eventObject) { + Mage::dispatchEvent($this->_eventPrefix.'_load_before', array( + $this->_eventObject => $this + )); + } + return $this; + } + + /** + * Set reset items data changed flag + * + * @param boolean $flag + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function setResetItemsDataChanged($flag) + { + $this->_resetItemsDataChanged = (bool)$flag; + return $this; + } + + /** + * Set flag data has changed to all collection items + * + * @return Mage_Core_Model_Mysql4_Collection_Abstract + */ + public function resetItemsDataChanged() + { + foreach ($this->_items as $item) { + $item->setDataChanges(false); + } + + return $this; + } + + /** + * Redeclare after load method for specifying collection items original data + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _afterLoad() + { + parent::_afterLoad(); + foreach ($this->_items as $item) { + $item->setOrigData(); + if ($this->_resetItemsDataChanged) { + $item->setDataChanges(false); + } + } + Mage::dispatchEvent('core_collection_abstract_load_after', array('collection' => $this)); + if ($this->_eventPrefix && $this->_eventObject) { + Mage::dispatchEvent($this->_eventPrefix.'_load_after', array( + $this->_eventObject => $this + )); + } + return $this; + } + + /** + * Save all the entities in the collection + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + public function save() + { + foreach ($this->getItems() as $item) { + $item->save(); + } + return $this; + } + + /** + * Check if cache can be used for collection + * + * @return bool + */ + protected function _canUseCache() + { + return Mage::app()->useCache('collections') && !empty($this->_cacheConf); + } + + /** + * Load cached data for select + * + * @param Zend_Db_Select $select + * @return string | false + */ + protected function _loadCache($select) + { + $data = Mage::app()->loadCache($this->_getSelectCacheId($select)); + return $data; + } + + /** + * Save collection data to cache + * + * @param array $data + * @param Zend_Db_Select $select + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _saveCache($data, $select) + { + Mage::app()->saveCache(serialize($data), $this->_getSelectCacheId($select), $this->_getCacheTags()); + return $this; + } + + /** + * Redeclared for processing cache tags throw application object + * + * @return array + */ + protected function _getCacheTags() + { + $tags = parent::_getCacheTags(); + $tags[] = Mage_Core_Model_App::CACHE_TAG; + $tags[] = self::CACHE_TAG; + return $tags; + } + + /** + * Format Date to internal database date format + * + * @param int|string|Zend_Date $date + * @param boolean $includeTime + * @return string + */ + public function formatDate($date, $includeTime = true) + { + return Varien_Date::formatDate($date, $includeTime); + } + + + +} diff --git a/app/code/core/Mage/Core/Model/Resource/Design.php b/app/code/core/Mage/Core/Model/Resource/Design.php new file mode 100644 index 00000000..b7de143c --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Design.php @@ -0,0 +1,191 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Design Resource Model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Design extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table and primary key + * + */ + protected function _construct() + { + $this->_init('core/design_change', 'design_change_id'); + } + + /** + * Perform actions before object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + * @throws Mage_Core_Exception + */ + public function _beforeSave(Mage_Core_Model_Abstract $object) + { + if ($date = $object->getDateFrom()) { + $object->setDateFrom($this->formatDate($date)); + } else { + $object->setDateFrom(null); + } + + if ($date = $object->getDateTo()) { + $object->setDateTo($this->formatDate($date)); + } else { + $object->setDateTo(null); + } + + if (!is_null($object->getDateFrom()) && !is_null($object->getDateTo()) + && Varien_Date::toTimestamp($object->getDateFrom()) > Varien_Date::toTimestamp($object->getDateTo())) { + Mage::throwException(Mage::helper('core')->__('Start date cannot be greater than end date.')); + } + + $check = $this->_checkIntersection( + $object->getStoreId(), + $object->getDateFrom(), + $object->getDateTo(), + $object->getId() + ); + + if ($check) { + Mage::throwException( + Mage::helper('core')->__('Your design change for the specified store intersects with another one, please specify another date range.')); + } + + if ($object->getDateFrom() === null) + $object->setDateFrom(new Zend_Db_Expr('null')); + if ($object->getDateTo() === null) + $object->setDateTo(new Zend_Db_Expr('null')); + + parent::_beforeSave($object); + } + + + /** + * Check intersections + * + * @param int $storeId + * @param date $dateFrom + * @param date $dateTo + * @param int $currentId + * @return Array + */ + protected function _checkIntersection($storeId, $dateFrom, $dateTo, $currentId) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from(array('main_table'=>$this->getTable('design_change'))) + ->where('main_table.store_id = :store_id') + ->where('main_table.design_change_id <> :current_id'); + + $dateConditions = array('date_to IS NULL AND date_from IS NULL'); + + if (!is_null($dateFrom)) { + $dateConditions[] = ':date_from BETWEEN date_from AND date_to'; + $dateConditions[] = ':date_from >= date_from and date_to IS NULL'; + $dateConditions[] = ':date_from <= date_to and date_from IS NULL'; + } else { + $dateConditions[] = 'date_from IS NULL'; + } + + if (!is_null($dateTo)) { + $dateConditions[] = ':date_to BETWEEN date_from AND date_to'; + $dateConditions[] = ':date_to >= date_from AND date_to IS NULL'; + $dateConditions[] = ':date_to <= date_to AND date_from IS NULL'; + } else { + $dateConditions[] = 'date_to IS NULL'; + } + + if (is_null($dateFrom) && !is_null($dateTo)) { + $dateConditions[] = 'date_to <= :date_to OR date_from <= :date_to'; + } + + if (!is_null($dateFrom) && is_null($dateTo)) { + $dateConditions[] = 'date_to >= :date_from OR date_from >= :date_from'; + } + + if (!is_null($dateFrom) && !is_null($dateTo)) { + $dateConditions[] = 'date_from BETWEEN :date_from AND :date_to'; + $dateConditions[] = 'date_to BETWEEN :date_from AND :date_to'; + } elseif (is_null($dateFrom) && is_null($dateTo)) { + $dateConditions = array(); + } + + $condition = ''; + if (!empty($dateConditions)) { + $condition = '(' . implode(') OR (', $dateConditions) . ')'; + $select->where($condition); + } + + $bind = array( + 'store_id' => (int)$storeId, + 'current_id' => (int)$currentId, + ); + + if (!is_null($dateTo)) { + $bind['date_to'] = $dateTo; + } + if (!is_null($dateFrom)) { + $bind['date_from'] = $dateFrom; + } + + $result = $adapter->fetchOne($select, $bind); + return $result; + } + + /** + * Load changes for specific store and date + * + * @param int $storeId + * @param string $date + * @return array + */ + public function loadChange($storeId, $date = null) + { + if (is_null($date)) { + $date = Varien_Date::now(); + } + + $select = $this->_getReadAdapter()->select() + ->from(array('main_table' => $this->getTable('design_change'))) + ->where('store_id = :store_id') + ->where('date_from <= :required_date or date_from IS NULL') + ->where('date_to >= :required_date or date_to IS NULL'); + + $bind = array( + 'store_id' => (int)$storeId, + 'required_date' => $date + ); + + return $this->_getReadAdapter()->fetchRow($select, $bind); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Design/Collection.php b/app/code/core/Mage/Core/Model/Resource/Design/Collection.php new file mode 100644 index 00000000..aec260be --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Design/Collection.php @@ -0,0 +1,88 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Design resource collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Design_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Core Design resource collection + * + */ + protected function _construct() + { + $this->_init('core/design'); + } + + /** + * Join store data to collection + * + * @return Mage_Core_Model_Resource_Design_Collection + */ + public function joinStore() + { + return $this->join( + array('cs' => 'core/store'), + 'cs.store_id = main_table.store_id', + array('cs.name')); + } + + /** + * Add date filter to collection + * + * @param null|int|string|Zend_Date $date + * @return Mage_Core_Model_Resource_Design_Collection + */ + public function addDateFilter($date = null) + { + if (is_null($date)) { + $date = $this->formatDate(true); + } else { + $date = $this->formatDate($date); + } + + $this->addFieldToFilter('date_from', array('lteq' => $date)); + $this->addFieldToFilter('date_to', array('gteq' => $date)); + return $this; + } + + /** + * Add store filter to collection + * + * @param int|array $storeId + * @return Mage_Core_Model_Resource_Design_Collection + */ + public function addStoreFilter($storeId) + { + return $this->addFieldToFilter('store_id', array('in' => $storeId)); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Design/Package/Collection.php b/app/code/core/Mage/Core/Model/Resource/Design/Package/Collection.php new file mode 100755 index 00000000..2a4c2bfd --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Design/Package/Collection.php @@ -0,0 +1,69 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Design package collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Design_Package_Collection extends Varien_Object +{ + /** + * Load design package collection + * + * @return Mage_Core_Model_Resource_Design_Package_Collection + */ + public function load() + { + $packages = $this->getData('packages'); + if (is_null($packages)) { + $packages = Mage::getModel('core/design_package')->getPackageList(); + $this->setData('packages', $packages); + } + + return $this; + } + + /** + * Convert to option array + * + * @return array + */ + public function toOptionArray() + { + $options = array(); + $packages = $this->getData('packages'); + foreach ($packages as $package) { + $options[] = array('value' => $package, 'label' => $package); + } + array_unshift($options, array('value' => '', 'label' => '')); + + return $options; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Email/Template.php b/app/code/core/Mage/Core/Model/Resource/Email/Template.php new file mode 100644 index 00000000..c5a1e997 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Email/Template.php @@ -0,0 +1,138 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Template db resource + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Email_Template extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize email template resource model + * + */ + protected function _construct() + { + $this->_init('core/email_template', 'template_id'); + } + + /** + * Load by template code from DB. + * + * @param string $templateCode + * @return array + */ + public function loadByCode($templateCode) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where('template_code = :template_code'); + $result = $this->_getReadAdapter()->fetchRow($select, array('template_code' => $templateCode)); + + if (!$result) { + return array(); + } + return $result; + } + + /** + * Check usage of template code in other templates + * + * @param Mage_Core_Model_Email_Template $template + * @return boolean + */ + public function checkCodeUsage(Mage_Core_Model_Email_Template $template) + { + if ($template->getTemplateActual() != 0 || is_null($template->getTemplateActual())) { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), 'COUNT(*)') + ->where('template_code = :template_code'); + $bind = array( + 'template_code' => $template->getTemplateCode() + ); + + $templateId = $template->getId(); + if ($templateId) { + $select->where('template_id != :template_id'); + $bind['template_id'] = $templateId; + } + + $result = $this->_getReadAdapter()->fetchOne($select, $bind); + if ($result) { + return true; + } + } + return false; + } + + /** + * Set template type, added at and modified at time + * + * @param Mage_Core_Model_Email_Template $object + * @return Mage_Core_Model_Resource_Email_Template + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if ($object->isObjectNew()) { + $object->setCreatedAt($this->formatDate(true)); + } + $object->setModifiedAt($this->formatDate(true)); + $object->setTemplateType((int)$object->getTemplateType()); + + return parent::_beforeSave($object); + } + + /** + * Retrieve config scope and scope id of specified email template by email pathes + * + * @param array $paths + * @param int|string $templateId + * @return array + */ + public function getSystemConfigByPathsAndTemplateId($paths, $templateId) + { + $orWhere = array(); + $pathesCounter = 1; + $bind = array(); + foreach ($paths as $path) { + $pathAlias = 'path_' . $pathesCounter; + $orWhere[] = 'path = :' . $pathAlias; + $bind[$pathAlias] = $path; + $pathesCounter++; + } + $bind['template_id'] = $templateId; + $select = $this->_getReadAdapter()->select() + ->from($this->getTable('core/config_data'), array('scope', 'scope_id', 'path')) + ->where('value LIKE :template_id') + ->where(join(' OR ', $orWhere)); + + return $this->_getReadAdapter()->fetchAll($select, $bind); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Email/Template/Collection.php b/app/code/core/Mage/Core/Model/Resource/Email/Template/Collection.php new file mode 100644 index 00000000..50d3ae81 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Email/Template/Collection.php @@ -0,0 +1,63 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Templates collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Email_Template_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Template table name + * + * @var string + */ + protected $_templateTable; + + /** + * Define resource table + * + */ + public function _construct() + { + $this->_init('core/email_template'); + $this->_templateTable = $this->getMainTable(); + } + + /** + * Convert collection items to select options array + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('template_id', 'template_code'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Entity/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Entity/Abstract.php index f793a2be..fb99b530 100644 --- a/app/code/core/Mage/Core/Model/Resource/Entity/Abstract.php +++ b/app/code/core/Mage/Core/Model/Resource/Entity/Abstract.php @@ -20,22 +20,37 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - abstract class Mage_Core_Model_Resource_Entity_Abstract { protected $_name = null; + /** + * Configuration object + * + * @var Varien_Simplexml_Config + */ protected $_config = array(); + /** + * Set config + * + * @param Varien_Simplexml_Config $config + */ public function __construct($config) { $this->_config = $config; } - public function getConfig($key='') + /** + * Get config by key + * + * @param string $key + * @return string|boolean + */ + public function getConfig($key = '') { if (''===$key) { return $this->_config; diff --git a/app/code/core/Mage/Core/Model/Resource/Entity/Table.php b/app/code/core/Mage/Core/Model/Resource/Entity/Table.php index d63f456c..12923593 100644 --- a/app/code/core/Mage/Core/Model/Resource/Entity/Table.php +++ b/app/code/core/Mage/Core/Model/Resource/Entity/Table.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,6 +31,11 @@ */ class Mage_Core_Model_Resource_Entity_Table extends Mage_Core_Model_Resource_Entity_Abstract { + /** + * Get table + * + * @return String + */ function getTable() { return $this->getConfig('table'); diff --git a/app/code/core/Mage/Core/Model/Resource/File/Storage/Abstract.php b/app/code/core/Mage/Core/Model/Resource/File/Storage/Abstract.php new file mode 100644 index 00000000..12477905 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/File/Storage/Abstract.php @@ -0,0 +1,92 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Abstract storage resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Resource_File_Storage_Abstract extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * File storage connection name + * + * @var string + */ + protected $_connectionName = null; + + /** + * Sets name of connection the resource will use + * + * @param string $name + * @return Mage_Core_Model_Resource_File_Storage_Abstract + */ + public function setConnectionName($name) + { + $this->_connectionName = $name; + return $this; + } + + /** + * Retrieve connection for read data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getReadAdapter() + { + return $this->_getConnection($this->_connectionName); + } + + /** + * Retrieve connection for write data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getWriteAdapter() + { + return $this->_getConnection($this->_connectionName); + } + + /** + * Get connection by name or type + * + * @param string $connectionName + * @return Varien_Db_Adapter_Interface + */ + protected function _getConnection($connectionName) + { + if (isset($this->_connections[$connectionName])) { + return $this->_connections[$connectionName]; + } + + $this->_connections[$connectionName] = $this->_resources->getConnection($connectionName); + + return $this->_connections[$connectionName]; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/File/Storage/Database.php b/app/code/core/Mage/Core/Model/Resource/File/Storage/Database.php new file mode 100644 index 00000000..ebce8095 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/File/Storage/Database.php @@ -0,0 +1,350 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * File storage database resource resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_File_Storage_Database extends Mage_Core_Model_Resource_File_Storage_Abstract +{ + /** + * Define table name and id field for resource + */ + protected function _construct() + { + $this->_init('core/file_storage', 'file_id'); + } + + /** + * Create database scheme for storing files + * + * @return Mage_Core_Model_Resource_File_Storage_Database + */ + public function createDatabaseScheme() + { + $adapter = $this->_getWriteAdapter(); + $table = $this->getMainTable(); + if ($adapter->isTableExists($table)) { + return $this; + } + + $dirStorageTable = $this->getTable('core/directory_storage'); // For foreign key + + $ddlTable = $adapter->newTable($table) + ->addColumn('file_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true + ), 'File Id') + ->addColumn('content', Varien_Db_Ddl_Table::TYPE_VARBINARY, Varien_Db_Ddl_Table::MAX_VARBINARY_SIZE, array( + 'nullable' => false + ), 'File Content') + ->addColumn('upload_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT + ), 'Upload Timestamp') + ->addColumn('filename', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'nullable' => false + ), 'Filename') + ->addColumn('directory_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'default' => null + ), 'Identifier of Directory where File is Located') + ->addColumn('directory', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'default' => null + ), 'Directory Path') + ->addIndex($adapter->getIndexName($table, array('filename', 'directory_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('filename', 'directory_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($adapter->getIndexName($table, array('directory_id')), array('directory_id')) + ->addForeignKey($adapter->getForeignKeyName($table, 'directory_id', $dirStorageTable, 'directory_id'), + 'directory_id', $dirStorageTable, 'directory_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('File Storage'); + + $adapter->createTable($ddlTable); + return $this; + } + + /** + * Decodes blob content retrieved by DB driver + * + * @param array $row Table row with 'content' key in it + * @return array + */ + protected function _decodeFileContent($row) + { + $row['content'] = $this->_getReadAdapter()->decodeVarbinary($row['content']); + return $row; + } + + /** + * Decodes blob content retrieved by Database driver + * + * @param array $rows Array of table rows (files), each containing 'content' key + * @return array + */ + protected function _decodeAllFilesContent($rows) + { + foreach ($rows as $key => $row) { + $rows[$key] = $this->_decodeFileContent($row); + } + return $rows; + } + + /** + * Load entity by filename + * + * @param Mage_Core_Model_File_Storage_Database $object + * @param string $filename + * @param string $path + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function loadByFilename(Mage_Core_Model_File_Storage_Database $object, $filename, $path) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $filename) + ->where($adapter->prepareSqlCondition('directory', array('seq' => $path))); + + $row = $adapter->fetchRow($select); + if ($row) { + $row = $this->_decodeFileContent($row); + $object->setData($row); + $this->_afterLoad($object); + } + + return $this; + } + + /** + * Clear files in storage + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function clearFiles() + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable()); + + return $this; + } + + /** + * Get files from storage at defined range + * + * @param int $offset + * @param int $count + * @return array + */ + public function getFiles($offset = 0, $count = 100) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('filename', 'content', 'directory') + ) + ->order('file_id') + ->limit($count, $offset); + + $rows = $adapter->fetchAll($select); + return $this->_decodeAllFilesContent($rows); + } + + /** + * Save file to storage + * + * @param Mage_Core_Model_File_Storage_Database|array $object + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function saveFile($file) + { + $adapter = $this->_getWriteAdapter(); + + $contentParam = new Varien_Db_Statement_Parameter($file['content']); + $contentParam->setIsBlob(true); + $data = array( + 'content' => $contentParam, + 'upload_time' => $file['update_time'], + 'filename' => $file['filename'], + 'directory_id' => $file['directory_id'], + 'directory' => $file['directory'] + ); + + $adapter->insertOnDuplicate($this->getMainTable(), $data, array('content', 'upload_time')); + + return $this; + } + + /** + * Rename files in database + * + * @param string $oldFilename + * @param string $oldPath + * @param string $newFilename + * @param string $newPath + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function renameFile($oldFilename, $oldPath, $newFilename, $newPath) + { + $adapter = $this->_getWriteAdapter(); + $dataUpdate = array('filename' => $newFilename, 'directory' => $newPath); + + $dataWhere = array('filename = ?' => $oldFilename); + $dataWhere[] = new Zend_Db_Expr($adapter->prepareSqlCondition('directory', array('seq' => $oldPath))); + + $adapter->update($this->getMainTable(), $dataUpdate, $dataWhere); + + return $this; + } + + /** + * Copy files in database + * + * @param string $oldFilename + * @param string $oldPath + * @param string $newFilename + * @param string $newPath + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function copyFile($oldFilename, $oldPath, $newFilename, $newPath) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $oldFilename) + ->where($adapter->prepareSqlCondition('directory', array('seq' => $oldPath))); + + $data = $adapter->fetchRow($select); + if (!$data) { + return $this; + } + + if (isset($data['file_id']) && isset($data['filename'])) { + unset($data['file_id']); + $data['filename'] = $newFilename; + $data['directory'] = $newPath; + + $writeAdapter = $this->_getWriteAdapter(); + $writeAdapter->insertOnDuplicate($this->getMainTable(), $data, array('content', 'upload_time')); + } + + return $this; + } + + /** + * Check whether file exists in DB + * + * @param string $filename + * @param string $path + * @return bool + */ + public function fileExists($filename, $path) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('filename = ?', $filename) + ->where($adapter->prepareSqlCondition('directory', array('seq' => $path))) + ->limit(1); + + $data = $adapter->fetchRow($select); + return (bool)$data; + } + + /** + * Delete files that starts with given $folderName + * + * @param string $folderName + */ + public function deleteFolder($folderName = '') + { + $folderName = rtrim($folderName, '/'); + if (!strlen($folderName)) { + return; + } + + /* @var $resHelper Mage_Core_Model_Resource_Helper_Abstract */ + $resHelper = Mage::getResourceHelper('core'); + $likeExpression = $resHelper->addLikeEscape($folderName . '/', array('position' => 'start')); + $this->_getWriteAdapter() + ->delete($this->getMainTable(), new Zend_Db_Expr('filename LIKE ' . $likeExpression)); + } + + /** + * Delete file + * + * @param string $filename + * @param string $directory + */ + public function deleteFile($filename, $directory) + { + $adapter = $this->_getWriteAdapter(); + + $where = array('filename = ?' => $filename); + $where[] = new Zend_Db_Expr($adapter->prepareSqlCondition('directory', array('seq' => $directory))); + + $adapter->delete($this->getMainTable(), $where); + } + + /** + * Return directory file listing + * + * @param string $directory + * @return mixed + */ + public function getDirectoryFiles($directory) + { + $directory = trim($directory, '/'); + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array( + 'filename', + 'directory', + 'content' + ) + ) + ->where($adapter->prepareSqlCondition('directory', array('seq' => $directory))) + ->order('file_id'); + + $rows = $adapter->fetchAll($select); + return $this->_decodeAllFilesContent($rows); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/File/Storage/Directory/Database.php b/app/code/core/Mage/Core/Model/Resource/File/Storage/Directory/Database.php new file mode 100644 index 00000000..19a56f11 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/File/Storage/Directory/Database.php @@ -0,0 +1,222 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Directory storage database resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_File_Storage_Directory_Database extends Mage_Core_Model_Resource_File_Storage_Abstract +{ + /** + * Define table name and id field for resource + */ + protected function _construct() + { + $this->_init('core/directory_storage', 'directory_id'); + } + + /** + * Create database scheme for storing files + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function createDatabaseScheme() + { + $adapter = $this->_getWriteAdapter(); + $table = $this->getMainTable(); + if ($adapter->isTableExists($table)) { + return $this; + } + + $ddlTable = $adapter->newTable($table) + ->addColumn('directory_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true + ), 'Directory Id') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'nullable' => false + ), 'Directory Name') + ->addColumn('path', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'default' => null), 'Path to the Directory') + ->addColumn('upload_time', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT + ), 'Upload Timestamp') + ->addColumn('parent_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => true, + 'default' => null, + 'unsigned' => true + ), 'Parent Directory Id') + ->addIndex($adapter->getIndexName($table, array('name', 'parent_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('name', 'parent_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($adapter->getIndexName($table, array('parent_id')), array('parent_id')) + ->addForeignKey($adapter->getForeignKeyName($table, 'parent_id', $table, 'directory_id'), + 'parent_id', $table, 'directory_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Directory Storage'); + + $adapter->createTable($ddlTable); + return $this; + } + + /** + * Load entity by path + * + * @param Mage_Core_Model_File_Storage_Directory_Database $object + * @param string $path + * @return Mage_Core_Model_Mysql4_File_Storage_Directory_Database + */ + public function loadByPath(Mage_Core_Model_File_Storage_Directory_Database $object, $path) + { + $adapter = $this->_getReadAdapter(); + + $name = basename($path); + $path = dirname($path); + if ($path == '.') { + $path = ''; + } + + $select = $adapter->select() + ->from(array('e' => $this->getMainTable())) + ->where('name = ?', $name) + ->where($adapter->prepareSqlCondition('path', array('seq' => $path))); + + $data = $adapter->fetchRow($select); + if ($data) { + $object->setData($data); + $this->_afterLoad($object); + } + + return $this; + } + + /** + * Return parent id + * + * @param string $path + * @return int + */ + public function getParentId($path) + { + $adapter = $this->_getReadAdapter(); + + $name = basename($path); + $path = dirname($path); + if ($path == '.') { + $path = ''; + } + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('directory_id') + ) + ->where('name = ?', $name) + ->where($adapter->prepareSqlCondition('path', array('seq' => $path))); + + return $adapter->fetchOne($select); + } + + /** + * Delete all directories from storage + * + * @return Mage_Core_Model_Mysql4_File_Storage_Database + */ + public function clearDirectories() + { + $adapter = $this->_getWriteAdapter(); + $adapter->delete($this->getMainTable()); + + return $this; + } + + /** + * Export directories from database + * + * @param int $offset + * @param int $count + * @return mixed + */ + public function exportDirectories($offset, $count = 100) + { + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('name', 'path') + ) + ->order('directory_id') + ->limit($count, $offset); + + return $adapter->fetchAll($select); + } + + /** + * Return directory file listing + * + * @param string $directory + * @return mixed + */ + public function getSubdirectories($directory) + { + $directory = trim($directory, '/'); + $adapter = $this->_getReadAdapter(); + + $select = $adapter->select() + ->from( + array('e' => $this->getMainTable()), + array('name', 'path') + ) + ->where($adapter->prepareSqlCondition('path', array('seq' => $directory))) + ->order('directory_id'); + + return $adapter->fetchAll($select); + } + + /** + * Delete directory + * + * @param string $name + * @param string $path + */ + public function deleteDirectory($name, $path) + { + $adapter = $this->_getWriteAdapter(); + + $where = array('name = ?' => $name); + $where[] = new Zend_Db_Expr($adapter->prepareSqlCondition('path', array('seq' => $path))); + + $adapter->delete($this->getMainTable(), $where); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/File/Storage/File.php b/app/code/core/Mage/Core/Model/Resource/File/Storage/File.php new file mode 100644 index 00000000..fa4ab3a0 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/File/Storage/File.php @@ -0,0 +1,197 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Model for synchronization from DB to filesystem + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_File_Storage_File +{ + /** + * Prefix of model events names + * + * @var string + */ + protected $_mediaBaseDirectory = null; + + /** + * Files at storage + * + * @var array + */ + public function getMediaBaseDirectory() + { + if (is_null($this->_mediaBaseDirectory)) { + $this->_mediaBaseDirectory = Mage::helper('core/file_storage_database')->getMediaBaseDir(); + } + + return $this->_mediaBaseDirectory; + } + + /** + * Collect files and directories recursively + * + * @param string$dir + * @return array + */ + public function getStorageData($dir = '') + { + $files = array(); + $directories = array(); + $currentDir = $this->getMediaBaseDirectory() . $dir; + + if (is_dir($currentDir)) { + $dh = opendir($currentDir); + if ($dh) { + while (($file = readdir($dh)) !== false) { + if ($file == '.' || $file == '..' || $file == '.svn' || $file == '.htaccess') { + continue; + } + + $fullPath = $currentDir . DS . $file; + $relativePath = $dir . DS . $file; + if (is_dir($fullPath)) { + $directories[] = array( + 'name' => $file, + 'path' => str_replace(DS, '/', ltrim($dir, DS)) + ); + + $data = $this->getStorageData($relativePath); + $directories = array_merge($directories, $data['directories']); + $files = array_merge($files, $data['files']); + } else { + $files[] = $relativePath; + } + } + closedir($dh); + } + } + + return array('files' => $files, 'directories' => $directories); + } + + /** + * Clear files and directories in storage + * + * @param string $dir + * @return Mage_Core_Model_Mysql4_File_Storage_File + */ + public function clear($dir = '') + { + $currentDir = $this->getMediaBaseDirectory() . $dir; + + if (is_dir($currentDir)) { + $dh = opendir($currentDir); + if ($dh) { + while (($file = readdir($dh)) !== false) { + if ($file == '.' || $file == '..') { + continue; + } + + $fullPath = $currentDir . DS . $file; + if (is_dir($fullPath)) { + $this->clear($dir . DS . $file); + } else { + @unlink($fullPath); + } + } + closedir($dh); + @rmdir($currentDir); + } + } + + return $this; + } + + /** + * Save directory to storage + * + * @param array $dir + * @return bool + */ + public function saveDir($dir) + { + if (!isset($dir['name']) || !strlen($dir['name']) + || !isset($dir['path']) + ) { + return false; + } + + $path = (strlen($dir['path'])) + ? $dir['path'] . DS . $dir['name'] + : $dir['name']; + $path = Mage::helper('core/file_storage_database')->getMediaBaseDir() . DS . str_replace('/', DS, $path); + + if (!file_exists($path) || !is_dir($path)) { + if (!@mkdir($path, 0777, true)) { + Mage::throwException(Mage::helper('core')->__('Unable to create directory: %s', $path)); + } + } + + return true; + } + + /** + * Save file to storage + * + * @param string $filePath + * @param string $content + * @param bool $overwrite + * @return bool + */ + public function saveFile($filePath, $content, $overwrite = false) + { + $filename = basename($filePath); + $path = $this->getMediaBaseDirectory() . DS . str_replace('/', DS ,dirname($filePath)); + + if (!file_exists($path) || !is_dir($path)) { + @mkdir($path, 0777, true); + } + + $ioFile = new Varien_Io_File(); + $ioFile->cd($path); + + if (!$ioFile->fileExists($filename) || ($overwrite && $ioFile->rm($filename))) { + $ioFile->streamOpen($filename); + $ioFile->streamLock(true); + $result = $ioFile->streamWrite($content); + $ioFile->streamUnlock(); + $ioFile->streamClose(); + + if ($result) { + return true; + } + + Mage::throwException(Mage::helper('core')->__('Unable to save file: %s', $filePath)); + } + + return false; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Flag.php b/app/code/core/Mage/Core/Model/Resource/Flag.php new file mode 100644 index 00000000..512c2d82 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Flag.php @@ -0,0 +1,45 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Flag model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Flag extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/flag', 'flag_id'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Helper/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Helper/Abstract.php new file mode 100644 index 00000000..e9c4b335 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Helper/Abstract.php @@ -0,0 +1,326 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Abstract resource helper class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Resource_Helper_Abstract +{ + /** + * Read adapter instance + * + * @var Varien_Db_Adapter_Interface + */ + protected $_readAdapter; + + /** + * Write adapter instance + * + * @var Varien_Db_Adapter_Interface + */ + protected $_writeAdapter; + + /** + * Resource helper module prefix + * + * @var string + */ + protected $_modulePrefix; + + /** + * Initialize resource helper instance + * + * @param string $module + */ + public function __construct($module) + { + $this->_modulePrefix = (string)$module; + } + + /** + * Retrieve connection for read data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getReadAdapter() + { + if ($this->_readAdapter === null) { + $this->_readAdapter = $this->_getConnection('read'); + } + + return $this->_readAdapter; + } + + /** + * Retrieve connection for write data + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getWriteAdapter() + { + if ($this->_writeAdapter === null) { + $this->_writeAdapter = $this->_getConnection('write'); + } + + return $this->_writeAdapter; + } + + /** + * Retrieves connection to the resource + * + * @param string $name + * @return Varien_Db_Adapter_Interface + */ + protected function _getConnection($name) + { + $connection = sprintf('%s_%s', $this->_modulePrefix, $name); + /** @var $resource Mage_Core_Model_Resource */ + $resource = Mage::getSingleton('core/resource'); + + return $resource->getConnection($connection); + } + + /** + * Escapes value, that participates in LIKE, with '\' symbol. + * Note: this func cannot be used on its own, because different RDMBS may use different default escape symbols, + * so you should either use addLikeEscape() to produce LIKE construction, or add escape symbol on your own. + * + * By default escapes '_', '%' and '\' symbols. If some masking symbols must not be escaped, then you can set + * appropriate options in $options. + * + * $options can contain following flags: + * - 'allow_symbol_mask' - the '_' symbol will not be escaped + * - 'allow_string_mask' - the '%' symbol will not be escaped + * - 'position' ('any', 'start', 'end') - expression will be formed so that $value will be found at position within string, + * by default when nothing set - string must be fully matched with $value + * + * @param string $value + * @param array $options + * @return string + */ + public function escapeLikeValue($value, $options = array()) + { + $value = str_replace('\\', '\\\\', $value); + + $from = array(); + $to = array(); + if (empty($options['allow_symbol_mask'])) { + $from[] = '_'; + $to[] = '\_'; + } + if (empty($options['allow_string_mask'])) { + $from[] = '%'; + $to[] = '\%'; + } + if ($from) { + $value = str_replace($from, $to, $value); + } + + if (isset($options['position'])) { + switch ($options['position']) { + case 'any': + $value = '%' . $value . '%'; + break; + case 'start': + $value = $value . '%'; + break; + case 'end': + $value = '%' . $value; + break; + } + } + + return $value; + } + + /** + * Escapes, quotes and adds escape symbol to LIKE expression. + * For options and escaping see escapeLikeValue(). + * + * @param string $value + * @param array $options + * @return Zend_Db_Expr + * + * @see escapeLikeValue() + */ + abstract public function addLikeEscape($value, $options = array()); + + /** + * Returns case insensitive LIKE construction. + * For options and escaping see escapeLikeValue(). + * + * @param string $field + * @param string $value + * @param array $options + * @return Zend_Db_Expr + * + * @see escapeLikeValue() + */ + public function getCILike($field, $value, $options = array()) + { + $quotedField = $this->_getReadAdapter()->quoteIdentifier($field); + return new Zend_Db_Expr($quotedField . ' LIKE ' . $this->addLikeEscape($value, $options)); + } + + /** + * Converts old pre-MMDB column definition for MySQL to new cross-db column DDL definition. + * Used to convert data from 3rd party extensions that hasn't been updated to MMDB style yet. + * + * E.g. Converts type 'varchar(255)' to array('type' => Varien_Db_Ddl_Table::TYPE_TEXT, 'length' => 255) + * + * @param array $column + * @return array + */ + public function convertOldColumnDefinition($column) + { + // Match type and size - e.g. varchar(100) or decimal(12,4) or int + $matches = array(); + $definition = trim($column['type']); + if (!preg_match('/([^(]*)(\\((.*)\\))?/', $definition, $matches)) { + throw Mage::exception( + 'Mage_Core', + Mage::helper('core')->__("Wrong old style column type definition: {$definition}.") + ); + } + + $length = null; + $proposedLength = (isset($matches[3]) && strlen($matches[3])) ? $matches[3] : null; + switch (strtolower($matches[1])) { + case 'bool': + $length = null; + $type = Varien_Db_Ddl_Table::TYPE_BOOLEAN; + break; + case 'char': + case 'varchar': + case 'tinytext': + $length = $proposedLength; + if (!$length) { + $length = 255; + } + $type = Varien_Db_Ddl_Table::TYPE_TEXT; + break; + case 'text': + $length = $proposedLength; + if (!$length) { + $length = '64k'; + } + $type = Varien_Db_Ddl_Table::TYPE_TEXT; + break; + case 'mediumtext': + $length = $proposedLength; + if (!$length) { + $length = '16M'; + } + $type = Varien_Db_Ddl_Table::TYPE_TEXT; + break; + case 'longtext': + $length = $proposedLength; + if (!$length) { + $length = '4G'; + } + $type = Varien_Db_Ddl_Table::TYPE_TEXT; + break; + case 'blob': + $length = $proposedLength; + if (!$length) { + $length = '64k'; + } + $type = Varien_Db_Ddl_Table::TYPE_BLOB; + break; + case 'mediumblob': + $length = $proposedLength; + if (!$length) { + $length = '16M'; + } + $type = Varien_Db_Ddl_Table::TYPE_BLOB; + break; + case 'longblob': + $length = $proposedLength; + if (!$length) { + $length = '4G'; + } + $type = Varien_Db_Ddl_Table::TYPE_BLOB; + break; + case 'tinyint': + case 'smallint': + $type = Varien_Db_Ddl_Table::TYPE_SMALLINT; + break; + case 'mediumint': + case 'int': + $type = Varien_Db_Ddl_Table::TYPE_INTEGER; + break; + case 'bigint': + $type = Varien_Db_Ddl_Table::TYPE_BIGINT; + break; + case 'float': + $type = Varien_Db_Ddl_Table::TYPE_FLOAT; + break; + case 'decimal': + case 'numeric': + $length = $proposedLength; + $type = Varien_Db_Ddl_Table::TYPE_DECIMAL; + break; + case 'datetime': + $type = Varien_Db_Ddl_Table::TYPE_DATETIME; + break; + case 'timestamp': + case 'time': + $type = Varien_Db_Ddl_Table::TYPE_TIMESTAMP; + break; + case 'date': + $type = Varien_Db_Ddl_Table::TYPE_DATE; + break; + default: + throw Mage::exception( + 'Mage_Core', + Mage::helper('core')->__("Unknown old style column type definition: {$definition}.") + ); + } + + $result = array( + 'type' => $type, + 'length' => $length, + 'unsigned' => $column['unsigned'], + 'nullable' => $column['is_null'], + 'default' => $column['default'], + 'identity' => stripos($column['extra'], 'auto_increment') !== false + ); + + /** + * Process the case when 'is_null' prohibits null value, and 'default' proposed to be null. + * It just means that default value not specified, and we must remove it from column definition. + */ + if (false === $column['is_null'] && null === $column['default']) { + unset($result['default']); + } + + return $result; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Helper/Mysql4.php b/app/code/core/Mage/Core/Model/Resource/Helper/Mysql4.php new file mode 100644 index 00000000..d1738d51 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Helper/Mysql4.php @@ -0,0 +1,371 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Resource helper class for MySql Varien DB Adapter + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Helper_Mysql4 extends Mage_Core_Model_Resource_Helper_Abstract +{ + /** + * Returns expresion for field unification + * + * @param string $field + * @return Zend_Db_Expr + */ + public function castField($field) + { + return $field; + } + /** + * Returns analytic expression for database column + * + * @param string $column + * @param string $groupAliasName OPTIONAL + * @param string $orderBy OPTIONAL + * @return Zend_Db_Expr + */ + public function prepareColumn($column, $groupAliasName = null, $orderBy = null) + { + return new Zend_Db_Expr((string)$column); + } + + /** + * Returns select query with analytic functions + * + * @param Varien_Db_Select $select + * @return string + */ + public function getQueryUsingAnalyticFunction(Varien_Db_Select $select) + { + return $select->assemble(); + } + + /** + * + * Returns Insert From Select On Duplicate query with analytic functions + * + * @param Varien_Db_Select $select + * @param string $table + * @param array $table + * @return string + */ + public function getInsertFromSelectUsingAnalytic(Varien_Db_Select $select, $table, $fields) + { + return $select->insertFromSelect($table, $fields); + } + + /** + * Correct limitation of queries with UNION + * No need to do additional actions on MySQL + * + * @param Varien_Db_Select $select + * @return Varien_Db_Select + */ + public function limitUnion($select) + { + return $select; + } + + /** + * Returns array of quoted orders with direction + * + * @param Varien_Db_Select $select + * @param bool $autoReset + * @return array + */ + protected function _prepareOrder(Varien_Db_Select $select, $autoReset = false) + { + $selectOrders = $select->getPart(Zend_Db_Select::ORDER); + if (!$selectOrders) { + return array(); + } + + $orders = array(); + foreach ($selectOrders as $term) { + if (is_array($term)) { + if (!is_numeric($term[0])) { + $orders[] = sprintf('%s %s', $this->_getReadAdapter()->quoteIdentifier($term[0], true), $term[1]); + } + } else { + if (!is_numeric($term)) { + $orders[] = $this->_getReadAdapter()->quoteIdentifier($term, true); + } + } + } + + if ($autoReset) { + $select->reset(Zend_Db_Select::ORDER); + } + + return $orders; + } + + /** + * Truncate alias name from field. + * + * Result string depends from second optional argument $reverse + * which can be true if you need the first part of the field. + * Field can be with 'dot' delimiter. + * + * @param string $field + * @param bool $reverse OPTIONAL + * @return string + */ + protected function _truncateAliasName($field, $reverse = false) + { + $string = $field; + if (!is_numeric($field) && (strpos($field, '.') !== false)) { + $size = strpos($field, '.'); + if ($reverse) { + $string = substr($field, 0, $size); + } else { + $string = substr($field, $size + 1); + } + } + + return $string; + } + + /** + * Returns quoted group by fields + * + * @param Varien_Db_Select $select + * @param bool $autoReset + * @return array + */ + protected function _prepareGroup(Varien_Db_Select $select, $autoReset = false) + { + $selectGroups = $select->getPart(Zend_Db_Select::GROUP); + if (!$selectGroups) { + return array(); + } + + $groups = array(); + foreach ($selectGroups as $term) { + $groups[] = $this->_getReadAdapter()->quoteIdentifier($term, true); + } + + if ($autoReset) { + $select->reset(Zend_Db_Select::GROUP); + } + + return $groups; + } + + /** + * Prepare and returns having array + * + * @param Varien_Db_Select $select + * @param bool $autoReset + * @return array + * @throws Zend_Db_Exception + */ + protected function _prepareHaving(Varien_Db_Select $select, $autoReset = false) + { + $selectHavings = $select->getPart(Zend_Db_Select::HAVING); + if (!$selectHavings) { + return array(); + } + + $havings = array(); + $columns = $select->getPart(Zend_Db_Select::COLUMNS); + foreach ($columns as $columnEntry) { + $correlationName = (string)$columnEntry[1]; + $column = $columnEntry[2]; + foreach ($selectHavings as $having) { + /** + * Looking for column expression in the having clause + */ + if (strpos($having, $correlationName) !== false) { + if (is_string($column)) { + /** + * Replace column expression to column alias in having clause + */ + $havings[] = str_replace($correlationName, $column, $having); + } else { + throw new Zend_Db_Exception(sprintf("Can't prepare expression without column alias: '%s'", $correlationName)); + } + } + } + } + + if ($autoReset) { + $select->reset(Zend_Db_Select::HAVING); + } + + return $havings; + } + + /** + * + * @param string $query + * @param int $limitCount + * @param int $limitOffset + * @param array $columnList + * @return string + */ + protected function _assembleLimit($query, $limitCount, $limitOffset, $columnList = array()) + { + if ($limitCount !== null) { + $limitCount = intval($limitCount); + if ($limitCount <= 0) { +// throw new Exception("LIMIT argument count={$limitCount} is not valid"); + } + + $limitOffset = intval($limitOffset); + if ($limitOffset < 0) { +// throw new Exception("LIMIT argument offset={$limitOffset} is not valid"); + } + + if ($limitOffset + $limitCount != $limitOffset + 1) { + $columns = array(); + foreach ($columnList as $columnEntry) { + $columns[] = $columnEntry[2] ? $columnEntry[2] : $columnEntry[1]; + } + + $query = sprintf('%s LIMIT %s, %s', $query, $limitCount, $limitOffset); + } + } + + return $query; + } + + /** + * Prepare select column list + * + * @param Varien_Db_Select $select + * @param $groupByCondition OPTIONAL + * @return array + * @throws Zend_Db_Exception + */ + public function prepareColumnsList(Varien_Db_Select $select, $groupByCondition = null) + { + if (!count($select->getPart(Zend_Db_Select::FROM))) { + return $select->getPart(Zend_Db_Select::COLUMNS); + } + + $columns = $select->getPart(Zend_Db_Select::COLUMNS); + $tables = $select->getPart(Zend_Db_Select::FROM); + $preparedColumns = array(); + + foreach ($columns as $columnEntry) { + list($correlationName, $column, $alias) = $columnEntry; + if ($column instanceof Zend_Db_Expr) { + if ($alias !== null) { + if (preg_match('/(^|[^a-zA-Z_])^(SELECT)?(SUM|MIN|MAX|AVG|COUNT)\s*\(/i', $column, $matches)) { + $column = $this->prepareColumn($column, $groupByCondition); + } + $preparedColumns[strtoupper($alias)] = array(null, $column, $alias); + } else { + throw new Zend_Db_Exception("Can't prepare expression without alias"); + } + } else { + if ($column == Zend_Db_Select::SQL_WILDCARD) { + if ($tables[$correlationName]['tableName'] instanceof Zend_Db_Expr) { + throw new Zend_Db_Exception("Can't prepare expression when tableName is instance of Zend_Db_Expr"); + } + $tableColumns = $this->_getReadAdapter()->describeTable($tables[$correlationName]['tableName']); + foreach(array_keys($tableColumns) as $col) { + $preparedColumns[strtoupper($col)] = array($correlationName, $col, null); + } + } else { + $columnKey = is_null($alias) ? $column : $alias; + $preparedColumns[strtoupper($columnKey)] = array($correlationName, $column, $alias); + } + } + } + +// $select->reset(Zend_Db_Select::COLUMNS); +// $select->setPart(Zend_Db_Select::COLUMNS, array_values($preparedColumns)); + + return $preparedColumns; + } + + /** + * Add prepared column group_concat expression + * + * @param Varien_Db_Select $select + * @param string $fieldAlias Field alias which will be added with column group_concat expression + * @param string $fields + * @param string $groupConcatDelimiter + * @param string $fieldsDelimiter + * @param string $additionalWhere + * @return Varien_Db_Select + */ + public function addGroupConcatColumn($select, $fieldAlias, $fields, $groupConcatDelimiter = ',', $fieldsDelimiter = '', $additionalWhere = '') + { + if (is_array($fields)) { + $fieldExpr = $this->_getReadAdapter()->getConcatSql($fields, $fieldsDelimiter); + } else { + $fieldExpr = $fields; + } + if ($additionalWhere) { + $fieldExpr = $this->_getReadAdapter()->getCheckSql($additionalWhere, $fieldExpr, "''"); + } + $separator = ''; + if ($groupConcatDelimiter) { + $separator = sprintf(" SEPARATOR '%s'", $groupConcatDelimiter); + } + + $select->columns(array($fieldAlias => new Zend_Db_Expr(sprintf('GROUP_CONCAT(%s%s)', $fieldExpr, $separator)))); + + return $select; + } + + /** + * Returns expression of days passed from $startDate to $endDate + * + * @param string|Zend_Db_Expr $startDate + * @param string|Zend_Db_Expr $endDate + * @return Zend_Db_Expr + */ + public function getDateDiff($startDate, $endDate) + { + $dateDiff = '(TO_DAYS(' . $endDate . ') - TO_DAYS(' . $startDate . '))'; + return new Zend_Db_Expr($dateDiff); + } + + /** + * Escapes and quotes LIKE value. + * Stating escape symbol in expression is not required, because we use standard MySQL escape symbol. + * For options and escaping see escapeLikeValue(). + * + * @param string $value + * @param array $options + * @return Zend_Db_Expr + * + * @see escapeLikeValue() + */ + public function addLikeEscape($value, $options = array()) + { + $value = $this->escapeLikeValue($value, $options); + return new Zend_Db_Expr($this->_getReadAdapter()->quote($value)); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Iterator.php b/app/code/core/Mage/Core/Model/Resource/Iterator.php index 113428cf..42eece9d 100644 --- a/app/code/core/Mage/Core/Model/Resource/Iterator.php +++ b/app/code/core/Mage/Core/Model/Resource/Iterator.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,14 +35,14 @@ class Mage_Core_Model_Resource_Iterator extends Varien_Object * Walk over records fetched from query one by one using callback function * * @param Zend_Db_Statement_Interface|Zend_Db_Select|string $query - * @param array|string $callback + * @param array|string $callbacks * @param array $args - * @return Mage_Core_Model_Resource_Activerecord + * @param Varien_Db_Adapter_Interface $adapter + * @return Mage_Core_Model_Resource_Iterator */ - public function walk($query, array $callbacks, array $args=array()) + public function walk($query, array $callbacks, array $args=array(), $adapter = null) { - $stmt = $this->_getStatement($query); - + $stmt = $this->_getStatement($query, $adapter); $args['idx'] = 0; while ($row = $stmt->fetch()) { $args['row'] = $row; @@ -64,8 +64,9 @@ public function walk($query, array $callbacks, array $args=array()) * @param Zend_Db_Statement_Interface|Zend_Db_Select|string $query * @param Zend_Db_Adapter_Abstract $conn * @return Zend_Db_Statement_Interface + * @throws Mage_Core_Exception */ - protected function _getStatement($query, $conn=null) + protected function _getStatement($query, $conn = null) { if ($query instanceof Zend_Db_Statement_Interface) { return $query; @@ -75,15 +76,13 @@ protected function _getStatement($query, $conn=null) return $query->query(); } - $hlp = Mage::helper('core'); - if (is_string($query)) { if (!$conn instanceof Zend_Db_Adapter_Abstract) { - Mage::throwException($hlp->__('Invalid connection')); + Mage::throwException(Mage::helper('core')->__('Invalid connection')); } return $conn->query($query); } - Mage::throwException($hlp->__('Invalid query')); + Mage::throwException(Mage::helper('core')->__('Invalid query')); } } diff --git a/app/code/core/Mage/Core/Model/Resource/Language.php b/app/code/core/Mage/Core/Model/Resource/Language.php new file mode 100755 index 00000000..ba564948 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Language.php @@ -0,0 +1,45 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Language resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Language extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/language', 'language_code'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Language/Collection.php b/app/code/core/Mage/Core/Model/Resource/Language/Collection.php new file mode 100644 index 00000000..26b793bb --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Language/Collection.php @@ -0,0 +1,65 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Language Resource collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Language_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('core/language'); + } + + /** + * Convert collection items to array of select options + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('language_code', 'language_title', array('title'=>'language_title')); + } + + /** + * Convert items array to hash for select options + * + * @return array + */ + public function toOptionHash() + { + return $this->_toOptionHash('language_code', 'language_title'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Layout.php b/app/code/core/Mage/Core/Model/Resource/Layout.php new file mode 100644 index 00000000..de214f76 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Layout.php @@ -0,0 +1,88 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core layout update resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Layout extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/layout_update', 'layout_update_id'); + } + + /** + * Retrieve layout updates by handle + * + * @param string $handle + * @param array $params + * @return string + */ + public function fetchUpdatesByHandle($handle, $params = array()) + { + $bind = array( + 'store_id' => Mage::app()->getStore()->getId(), + 'area' => Mage::getSingleton('core/design_package')->getArea(), + 'package' => Mage::getSingleton('core/design_package')->getPackageName(), + 'theme' => Mage::getSingleton('core/design_package')->getTheme('layout') + ); + + foreach ($params as $key => $value) { + if (isset($bind[$key])) { + $bind[$key] = $value; + } + } + $bind['layout_update_handle'] = $handle; + $result = ''; + + $readAdapter = $this->_getReadAdapter(); + if ($readAdapter) { + $select = $readAdapter->select() + ->from(array('layout_update' => $this->getMainTable()), array('xml')) + ->join(array('link'=>$this->getTable('core/layout_link')), + 'link.layout_update_id=layout_update.layout_update_id', + '') + ->where('link.store_id IN (0, :store_id)') + ->where('link.area = :area') + ->where('link.package = :package') + ->where('link.theme = :theme') + ->where('layout_update.handle = :layout_update_handle') + ->order('layout_update.sort_order ' . Varien_Db_Select::SQL_ASC); + + $result = join('', $readAdapter->fetchCol($select, $bind)); + } + return $result; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Resource.php b/app/code/core/Mage/Core/Model/Resource/Resource.php new file mode 100644 index 00000000..e550b3e6 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Resource.php @@ -0,0 +1,177 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Resource Resource Model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Resource extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Database versions + * + * @var array + */ + protected static $_versions = null; + + /** + * Resource data versions cache array + * + * @var array + */ + protected static $_dataVersions = null; + + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/resource', 'store_id'); + } + + /** + * Fill static versions arrays. + * This routine fetches Db and Data versions of at once to optimize sql requests. However, when upgrading, it's + * possible that 'data' column will be created only after all Db installs are passed. So $neededType contains + * information on main purpose of calling this routine, and even when 'data' column is absent - it won't require + * reissuing new sql just to get 'db' version of module. + * + * @param string $needType Can be 'db' or 'data' + * @return Mage_Core_Model_Resource_Resource + */ + protected function _loadVersionData($needType) + { + if ((($needType == 'db') && is_null(self::$_versions)) + || (($needType == 'data') && is_null(self::$_dataVersions))) { + self::$_versions = array(); // Db version column always exists + self::$_dataVersions = null; // Data version array will be filled only if Data column exist + + if ($this->_getReadAdapter()->isTableExists($this->getMainTable())) { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), '*'); + $rowset = $this->_getReadAdapter()->fetchAll($select); + foreach ($rowset as $row) { + self::$_versions[$row['code']] = $row['version']; + if (array_key_exists('data_version', $row)) { + if (is_null(self::$_dataVersions)) { + self::$_dataVersions = array(); + } + self::$_dataVersions[$row['code']] = $row['data_version']; + } + } + } + } + + return $this; + } + + + /** + * Get Module version from DB + * + * @param string $resName + * @return bool|string + */ + public function getDbVersion($resName) + { + if (!$this->_getReadAdapter()) { + return false; + } + $this->_loadVersionData('db'); + return isset(self::$_versions[$resName]) ? self::$_versions[$resName] : false; + } + + /** + * Set module version into DB + * + * @param string $resName + * @param string $version + * @return int + */ + public function setDbVersion($resName, $version) + { + $dbModuleInfo = array( + 'code' => $resName, + 'version' => $version, + ); + + if ($this->getDbVersion($resName)) { + self::$_versions[$resName] = $version; + return $this->_getWriteAdapter()->update($this->getMainTable(), + $dbModuleInfo, + array('code = ?' => $resName)); + } else { + self::$_versions[$resName] = $version; + return $this->_getWriteAdapter()->insert($this->getMainTable(), $dbModuleInfo); + } + } + + /** + * Get resource data version + * + * @param string $resName + * @return string|false + */ + public function getDataVersion($resName) + { + if (!$this->_getReadAdapter()) { + return false; + } + + $this->_loadVersionData('data'); + + return isset(self::$_dataVersions[$resName]) ? self::$_dataVersions[$resName] : false; + } + + /** + * Specify resource data version + * + * @param string $resName + * @param string $version + * @return Mage_Core_Model_Resource_Resource + */ + public function setDataVersion($resName, $version) + { + $data = array( + 'code' => $resName, + 'data_version' => $version + ); + + if ($this->getDbVersion($resName) || $this->getDataVersion($resName)) { + self::$_dataVersions[$resName] = $version; + $this->_getWriteAdapter()->update($this->getMainTable(), $data, array('code = ?' => $resName)); + } else { + self::$_dataVersions[$resName] = $version; + $this->_getWriteAdapter()->insert($this->getMainTable(), $data); + } + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Session.php b/app/code/core/Mage/Core/Model/Resource/Session.php new file mode 100644 index 00000000..3fc4817a --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Session.php @@ -0,0 +1,276 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Mysql4 session save handler + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Session implements Zend_Session_SaveHandler_Interface +{ + /** + * Session maximum cookie lifetime + */ + const SEESION_MAX_COOKIE_LIFETIME = 3155692600; + + /** + * Session lifetime + * + * @var integer + */ + protected $_lifeTime; + + /** + * Session data table name + * + * @var string + */ + protected $_sessionTable; + + /** + * Database read connection + * + * @var Varien_Db_Adapter_Interface + */ + protected $_read; + + /** + * Database write connection + * + * @var Varien_Db_Adapter_Interface + */ + protected $_write; + + /** + * Automatic cleaning factor of expired sessions + * value zero means no automatic cleaning, one means automatic cleaning each time a session is closed, and x>1 means + * cleaning once in x calls + * + * @var int + */ + protected $_automaticCleaningFactor = 50; + + /** + * Constructor + * + */ + public function __construct() + { + $resource = Mage::getSingleton('core/resource'); + $this->_sessionTable = $resource->getTableName('core/session'); + $this->_read = $resource->getConnection('core_read'); + $this->_write = $resource->getConnection('core_write'); + } + + /** + * Destrucor + * + */ + public function __destruct() + { + session_write_close(); + } + + /** + * Retrieve session life time + * + * @return int + */ + public function getLifeTime() + { + if (is_null($this->_lifeTime)) { + $configNode = Mage::app()->getStore()->isAdmin() ? + 'admin/security/session_cookie_lifetime' : 'web/cookie/cookie_lifetime'; + $this->_lifeTime = (int) Mage::getStoreConfig($configNode); + + if ($this->_lifeTime < 60) { + $this->_lifeTime = ini_get('session.gc_maxlifetime'); + } + + if ($this->_lifeTime < 60) { + $this->_lifeTime = 3600; //one hour + } + + if ($this->_lifeTime > self::SEESION_MAX_COOKIE_LIFETIME) { + $this->_lifeTime = self::SEESION_MAX_COOKIE_LIFETIME; // 100 years + } + } + return $this->_lifeTime; + } + + /** + * Check DB connection + * + * @return bool + */ + public function hasConnection() + { + if (!$this->_read) { + return false; + } + if (!$this->_read->isTableExists($this->_sessionTable)) { + return false; + } + + return true; + } + + /** + * Setup save handler + * + * @return Mage_Core_Model_Resource_Session + */ + public function setSaveHandler() + { + if ($this->hasConnection()) { + session_set_save_handler( + array($this, 'open'), + array($this, 'close'), + array($this, 'read'), + array($this, 'write'), + array($this, 'destroy'), + array($this, 'gc') + ); + } else { + session_save_path(Mage::getBaseDir('session')); + } + return $this; + } + + /** + * Open session + * + * @param string $savePath ignored + * @param string $sessName ignored + * @return boolean + */ + public function open($savePath, $sessName) + { + return true; + } + + /** + * Close session + * + * @return boolean + */ + public function close() + { + $this->gc($this->getLifeTime()); + + return true; + } + + /** + * Fetch session data + * + * @param string $sessId + * @return string + */ + public function read($sessId) + { + $select = $this->_read->select() + ->from($this->_sessionTable, array('session_data')) + ->where('session_id = :session_id') + ->where('session_expires > :session_expires'); + $bind = array( + 'session_id' => $sessId, + 'session_expires' => Varien_Date::toTimestamp(true) + ); + + $data = $this->_read->fetchOne($select, $bind); + + return $data; + } + + /** + * Update session + * + * @param string $sessId + * @param string $sessData + * @return boolean + */ + public function write($sessId, $sessData) + { + $bindValues = array( + 'session_id' => $sessId + ); + $select = $this->_write->select() + ->from($this->_sessionTable) + ->where('session_id = :session_id'); + $exists = $this->_read->fetchOne($select, $bindValues); + + $bind = array( + 'session_expires' => Varien_Date::toTimestamp(true) + $this->getLifeTime(), + 'session_data' => $sessData + ); + if ($exists) { + $where = array( + 'session_id=?' => $sessId + ); + $this->_write->update($this->_sessionTable, $bind, $where); + } else { + $bind['session_id'] = $sessId; + $this->_write->insert($this->_sessionTable, $bind); + } + + return true; + } + + /** + * Destroy session + * + * @param string $sessId + * @return boolean + */ + public function destroy($sessId) + { + $where = array('session_id = ?' => $sessId); + $this->_write->delete($this->_sessionTable, $where); + return true; + } + + /** + * Garbage collection + * + * @param int $sessMaxLifeTime ignored + * @return boolean + */ + public function gc($sessMaxLifeTime) + { + if ($this->_automaticCleaningFactor > 0) { + if ($this->_automaticCleaningFactor == 1 || + rand(1, $this->_automaticCleaningFactor) == 1) { + $where = array('session_expires < ?' => Varien_Date::toTimestamp(true)); + $this->_write->delete($this->_sessionTable, $where); + } + } + return true; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Setup.php b/app/code/core/Mage/Core/Model/Resource/Setup.php index 3f56603e..e9c35d70 100644 --- a/app/code/core/Mage/Core/Model/Resource/Setup.php +++ b/app/code/core/Mage/Core/Model/Resource/Setup.php @@ -20,25 +20,56 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** - * Resource setup model + * Resource Setup Model * + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ class Mage_Core_Model_Resource_Setup { - const DEFAULT_SETUP_CONNECTION = 'core_setup'; - const VERSION_COMPARE_EQUAL = 0; - const VERSION_COMPARE_LOWER = -1; - const VERSION_COMPARE_GREATER = 1; + const DEFAULT_SETUP_CONNECTION = 'core_setup'; + const VERSION_COMPARE_EQUAL = 0; + const VERSION_COMPARE_LOWER = -1; + const VERSION_COMPARE_GREATER = 1; + + const TYPE_DB_INSTALL = 'install'; + const TYPE_DB_UPGRADE = 'upgrade'; + const TYPE_DB_ROLLBACK = 'rollback'; + const TYPE_DB_UNINSTALL = 'uninstall'; + const TYPE_DATA_INSTALL = 'data-install'; + const TYPE_DATA_UPGRADE = 'data-upgrade'; + /** + * Setup resource name + * @var string + */ protected $_resourceName; + + /** + * Setup resource configuration object + * + * @var Varien_Simplexml_Object + */ protected $_resourceConfig; + + /** + * Connection configuration object + * + * @var Varien_Simplexml_Object + */ protected $_connectionConfig; + + /** + * Setup module configuration object + * + * @var Varien_Simplexml_Object + */ protected $_moduleConfig; /** @@ -54,18 +85,45 @@ class Mage_Core_Model_Resource_Setup * @var Varien_Db_Adapter_Pdo_Mysql */ protected $_conn; + /** + * Tables cache array + * + * @var array + */ protected $_tables = array(); + /** + * Tables data cache array + * + * @var array + */ protected $_setupCache = array(); /** - * Flag wich allow to detect that some schema update was applied dueting request + * Flag which shows, that setup has hooked queries from DB adapter + * + * @var bool + */ + protected $_queriesHooked = false; + + /** + * Flag which allow to detect that some schema update was applied dueting request * * @var bool */ protected static $_hadUpdates; + /** + * Flag which allow run data install or upgrade + * + * @var bool + */ protected static $_schemaUpdatesChecked; + /** + * Initialize resource configurations, setup connection, etc + * + * @param string $resourceName the setup resource name + */ public function __construct($resourceName) { $config = Mage::getConfig(); @@ -85,7 +143,7 @@ public function __construct($resourceName) * If module setup configuration wasn't loaded */ if (!$connection) { - $connection = Mage::getSingleton('core/resource')->getConnection('core_setup'); + $connection = Mage::getSingleton('core/resource')->getConnection($this->_resourceName); } $this->_conn = $connection; } @@ -93,7 +151,7 @@ public function __construct($resourceName) /** * Get connection object * - * @return Varien_Db_Adapter_Pdo_Mysql + * @return Varien_Db_Adapter_Interface */ public function getConnection() { @@ -114,23 +172,39 @@ public function setTable($tableName, $realTableName) } /** - * Get table name by table placeholder + * Get table name (validated by db adapter) by table placeholder * - * @param string $tableName - * @return string + * @param string|array $tableName + * @return string */ public function getTable($tableName) { - if (!isset($this->_tables[$tableName])) { - $this->_tables[$tableName] = Mage::getSingleton('core/resource')->getTableName($tableName); + $cacheKey = $this->_getTableCacheName($tableName); + if (!isset($this->_tables[$cacheKey])) { + $this->_tables[$cacheKey] = Mage::getSingleton('core/resource')->getTableName($tableName); + } + return $this->_tables[$cacheKey]; + } + + /** + * Retrieve table name for cache + * + * @param string|array $tableName + * @return string + */ + protected function _getTableCacheName($tableName) + { + if (is_array($tableName)) { + return join('_', $tableName); + } - return $this->_tables[$tableName]; + return $tableName; } /** * Get core resource resource model * - * @return Mage_Core_Model_Mysql4_Resource + * @return Mage_Core_Model_Resource_Resource */ protected function _getResource() { @@ -140,7 +214,7 @@ protected function _getResource() /** * Apply database updates whenever needed * - * @return boolean + * @return boolean */ static public function applyAllUpdates() { @@ -149,7 +223,7 @@ static public function applyAllUpdates() $resources = Mage::getConfig()->getNode('global/resources')->children(); $afterApplyUpdates = array(); - foreach ($resources as $resName=>$resource) { + foreach ($resources as $resName => $resource) { if (!$resource->setup) { continue; } @@ -175,6 +249,7 @@ static public function applyAllUpdates() /** * Apply database data updates whenever needed + * */ static public function applyAllDataUpdates() { @@ -182,7 +257,7 @@ static public function applyAllDataUpdates() return; } $resources = Mage::getConfig()->getNode('global/resources')->children(); - foreach ($resources as $resName=>$resource) { + foreach ($resources as $resName => $resource) { if (!$resource->setup) { continue; } @@ -213,17 +288,29 @@ public function applyDataUpdates() } elseif ($configVer) { $this->_installData($configVer); } + return $this; } /** * Apply module resource install, upgrade and data scripts + * + * @return Mage_Core_Model_Resource_Setup */ public function applyUpdates() { $dbVer = $this->_getResource()->getDbVersion($this->_resourceName); $configVer = (string)$this->_moduleConfig->version; + + /** + * Hook queries in adapter, so that in MySQL compatibility mode extensions and custom modules will avoid + * errors due to changes in database structure + */ + if (((string)$this->_moduleConfig->codePool != 'core') && Mage::helper('core')->useDbCompatibleMode()) { + $this->_hookQueries(); + } + // Module is installed - if ($dbVer!==false) { + if ($dbVer !== false) { $status = version_compare($configVer, $dbVer); switch ($status) { case self::VERSION_COMPARE_LOWER: @@ -239,18 +326,73 @@ public function applyUpdates() } elseif ($configVer) { $this->_installResourceDb($configVer); } + + $this->_unhookQueries(); + + return $this; + } + + /** + * Hooks queries to strengthen backwards compatibility in MySQL. + * Currently - dynamically updates column types for foreign keys, when their targets were changed + * during MMDB development. + * + * @return Mage_Core_Model_Resource_Setup + */ + protected function _hookQueries() + { + $this->_queriesHooked = true; + /* @var $adapter Varien_Db_Adapter_Pdo_Mysql */ + $adapter = $this->getConnection(); + $adapter->setQueryHook(array('object' => $this, 'method' => 'callbackQueryHook')); + return $this; + } + + /** + * Removes query hook + * + * @return Mage_Core_Model_Resource_Setup + */ + protected function _unhookQueries() + { + if (!$this->_queriesHooked) { + return $this; + } + /* @var $adapter Varien_Db_Adapter_Pdo_Mysql */ + $adapter = $this->getConnection(); + $adapter->setQueryHook(null); + $this->_queriesHooked = false; + return $this; + } + + /** + * Callback function, called on every query adapter processes. + * Modifies SQL or tables, so that foreign keys will be set successfully + * + * @param string $sql + * @param array $bind + * @return Mage_Core_Model_Resource_Setup + */ + public function callbackQueryHook(&$sql, &$bind) + { + Mage::getSingleton('core/resource_setup_query_modifier', array($this->getConnection())) + ->processQuery($sql, $bind); + return $this; } /** * Run data install scripts * * @param string $newVersion + * @return Mage_Core_Model_Resource_Setup */ protected function _installData($newVersion) { - $oldVersion = $this->_modifyResourceDb('data-install', '', $newVersion); - $this->_modifyResourceDb('data-upgrade', $oldVersion, $newVersion); + $oldVersion = $this->_modifyResourceDb(self::TYPE_DATA_INSTALL, '', $newVersion); + $this->_modifyResourceDb(self::TYPE_DATA_UPGRADE, $oldVersion, $newVersion); $this->_getResource()->setDataVersion($this->_resourceName, $newVersion); + + return $this; } /** @@ -258,24 +400,29 @@ protected function _installData($newVersion) * * @param string $oldVersion * @param string $newVersion + * @return Mage_Core_Model_Resource_Setup */ protected function _upgradeData($oldVersion, $newVersion) { - $appliedVersion = $this->_modifyResourceDb('data-upgrade', $oldVersion, $newVersion); + $this->_modifyResourceDb('data-upgrade', $oldVersion, $newVersion); $this->_getResource()->setDataVersion($this->_resourceName, $newVersion); + + return $this; } /** * Run resource installation file * - * @param string $version - * @return boolean + * @param string $newVersion + * @return Mage_Core_Model_Resource_Setup */ protected function _installResourceDb($newVersion) { - $oldVersion = $this->_modifyResourceDb('install', '', $newVersion); - $this->_modifyResourceDb('upgrade', $oldVersion, $newVersion); + $oldVersion = $this->_modifyResourceDb(self::TYPE_DB_INSTALL, '', $newVersion); + $this->_modifyResourceDb(self::TYPE_DB_UPGRADE, $oldVersion, $newVersion); $this->_getResource()->setDbVersion($this->_resourceName, $newVersion); + + return $this; } /** @@ -283,166 +430,279 @@ protected function _installResourceDb($newVersion) * * @param string $oldVersion * @param string $newVersion + * @return Mage_Core_Model_Resource_Setup */ protected function _upgradeResourceDb($oldVersion, $newVersion) { - $this->_modifyResourceDb('upgrade', $oldVersion, $newVersion); + $this->_modifyResourceDb(self::TYPE_DB_UPGRADE, $oldVersion, $newVersion); $this->_getResource()->setDbVersion($this->_resourceName, $newVersion); + + return $this; } /** * Roll back resource * - * @param string $newVersion - * @return bool + * @param string $newVersion + * @param string $oldVersion + * @return Mage_Core_Model_Resource_Setup */ - protected function _rollbackResourceDb($newVersion, $oldVersion) { - $this->_modifyResourceDb('rollback', $newVersion, $oldVersion); + $this->_modifyResourceDb(self::TYPE_DB_ROLLBACK, $newVersion, $oldVersion); + return $this; } /** * Uninstall resource * - * @param $version existing resource version - * @return bool + * @param string $version existing resource version + * @return Mage_Core_Model_Resource_Setup */ - protected function _uninstallResourceDb($version) { - $this->_modifyResourceDb('uninstall', $version, ''); + $this->_modifyResourceDb(self::TYPE_DB_UNINSTALL, $version, ''); + return $this; } /** - * Run module modification files. Return version of last applied upgrade (false if no upgrades applied) + * Retrieve available Database install/upgrade files for current module * - * @param string $actionType install|upgrade|uninstall - * @param string $fromVersion - * @param string $toVersion - * @return string | false + * @param string $actionType + * @param string $fromVersion + * @param string $toVersion + * @return array */ - - protected function _modifyResourceDb($actionType, $fromVersion, $toVersion) + protected function _getAvailableDbFiles($actionType, $fromVersion, $toVersion) { - $resModel = (string)$this->_connectionConfig->model; - $modName = (string)$this->_moduleConfig[0]->getName(); + $resModel = (string)$this->_connectionConfig->model; + $modName = (string)$this->_moduleConfig[0]->getName(); - $sqlFilesDir = Mage::getModuleDir('sql', $modName).DS.$this->_resourceName; - if (!is_dir($sqlFilesDir) || !is_readable($sqlFilesDir)) { - return false; + $filesDir = Mage::getModuleDir('sql', $modName) . DS . $this->_resourceName; + if (!is_dir($filesDir) || !is_readable($filesDir)) { + return array(); } - // Read resource files - $arrAvailableFiles = array(); - $sqlDir = dir($sqlFilesDir); - while (false !== ($sqlFile = $sqlDir->read())) { + + $dbFiles = array(); + $typeFiles = array(); + $regExpDb = sprintf('#^%s-(.*)\.(php|sql)$#i', $actionType); + $regExpType = sprintf('#^%s-%s-(.*)\.(php|sql)$#i', $resModel, $actionType); + $handlerDir = dir($filesDir); + while (false !== ($file = $handlerDir->read())) { $matches = array(); - if (preg_match('#^'.$resModel.'-'.$actionType.'-(.*)\.(sql|php)$#i', $sqlFile, $matches)) { - $arrAvailableFiles[$matches[1]] = $sqlFile; + if (preg_match($regExpDb, $file, $matches)) { + $dbFiles[$matches[1]] = $filesDir . DS . $file; + } else if (preg_match($regExpType, $file, $matches)) { + $typeFiles[$matches[1]] = $filesDir . DS . $file; } } - $sqlDir->close(); - if (empty($arrAvailableFiles)) { - return false; + $handlerDir->close(); + + if (empty($typeFiles) && empty($dbFiles)) { + return array(); } - // Get SQL files name - $arrModifyFiles = $this->_getModifySqlFiles($actionType, $fromVersion, $toVersion, $arrAvailableFiles); - if (empty($arrModifyFiles)) { - return false; + foreach ($typeFiles as $version => $file) { + $dbFiles[$version] = $file; } - $modifyVersion = false; - foreach ($arrModifyFiles as $resourceFile) { - $sqlFile = $sqlFilesDir.DS.$resourceFile['fileName']; - $fileType = pathinfo($resourceFile['fileName'], PATHINFO_EXTENSION); - // Execute SQL - if ($this->_conn) { - if (method_exists($this->_conn, 'disallowDdlCache')) { - $this->_conn->disallowDdlCache(); + return $this->_getModifySqlFiles($actionType, $fromVersion, $toVersion, $dbFiles); + } + + /** + * Retrieve available Data install/upgrade files for current module + * + * @param string $actionType + * @param string $fromVersion + * @param string $toVersion + * @return array + */ + protected function _getAvailableDataFiles($actionType, $fromVersion, $toVersion) + { + $modName = (string)$this->_moduleConfig[0]->getName(); + $files = array(); + + $filesDir = Mage::getModuleDir('data', $modName) . DS . $this->_resourceName; + if (is_dir($filesDir) && is_readable($filesDir)) { + $regExp = sprintf('#^%s-(.*)\.php$#i', $actionType); + $handlerDir = dir($filesDir); + while (false !== ($file = $handlerDir->read())) { + $matches = array(); + if (preg_match($regExp, $file, $matches)) { + $files[$matches[1]] = $filesDir . DS . $file; } - try { - switch ($fileType) { - case 'sql': - $sql = file_get_contents($sqlFile); - if ($sql!='') { - $result = $this->run($sql); - } else { - $result = true; - } - break; - case 'php': - $conn = $this->_conn; - $result = include($sqlFile); - break; - default: - $result = false; - } - if ($result) { - if (strpos($actionType, 'data-') !== false) { - $this->_getResource()->setDataVersion($this->_resourceName, $resourceFile['toVersion']); + + } + $handlerDir->close(); + } + + // search data files in old location + $filesDir = Mage::getModuleDir('sql', $modName) . DS . $this->_resourceName; + if (is_dir($filesDir) && is_readable($filesDir)) { + $regExp = sprintf('#^%s-%s-(.*)\.php$#i', $this->_connectionConfig->model, $actionType); + $handlerDir = dir($filesDir); + + while (false !== ($file = $handlerDir->read())) { + $matches = array(); + if (preg_match($regExp, $file, $matches)) { + $files[$matches[1]] = $filesDir . DS . $file; + } + } + $handlerDir->close(); + } + + if (empty($files)) { + return array(); + } + + return $this->_getModifySqlFiles($actionType, $fromVersion, $toVersion, $files); + } + + /** + * Save resource version + * + * @param string $actionType + * @param string $version + * @return Mage_Core_Model_Resource_Setup + */ + protected function _setResourceVersion($actionType, $version) + { + switch ($actionType) { + case self::TYPE_DB_INSTALL: + case self::TYPE_DB_UPGRADE: + $this->_getResource()->setDbVersion($this->_resourceName, $version); + break; + case self::TYPE_DATA_INSTALL: + case self::TYPE_DATA_UPGRADE: + $this->_getResource()->setDataVersion($this->_resourceName, $version); + break; + + } + + return $this; + } + + /** + * Run module modification files. Return version of last applied upgrade (false if no upgrades applied) + * + * @param string $actionType self::TYPE_* + * @param string $fromVersion + * @param string $toVersion + * @return string|false + * @throws Mage_Core_Exception + */ + + protected function _modifyResourceDb($actionType, $fromVersion, $toVersion) + { + switch ($actionType) { + case self::TYPE_DB_INSTALL: + case self::TYPE_DB_UPGRADE: + $files = $this->_getAvailableDbFiles($actionType, $fromVersion, $toVersion); + break; + case self::TYPE_DATA_INSTALL: + case self::TYPE_DATA_UPGRADE: + $files = $this->_getAvailableDataFiles($actionType, $fromVersion, $toVersion); + break; + default: + $files = array(); + break; + } + if (empty($files) || !$this->getConnection()) { + return false; + } + + $version = false; + + foreach ($files as $file) { + $fileName = $file['fileName']; + $fileType = pathinfo($fileName, PATHINFO_EXTENSION); + $this->getConnection()->disallowDdlCache(); + try { + switch ($fileType) { + case 'php': + $conn = $this->getConnection(); + $result = include $fileName; + break; + case 'sql': + $sql = file_get_contents($fileName); + if (!empty($sql)) { + + $result = $this->run($sql); } else { - $this->_getResource()->setDbVersion($this->_resourceName, $resourceFile['toVersion']); + $result = true; } - } - } catch (Exception $e){ - echo "<pre>".print_r($e,1)."</pre>"; - throw Mage::exception('Mage_Core', Mage::helper('core')->__('Error in file: "%s" - %s', $sqlFile, $e->getMessage())); + break; + default: + $result = false; + break; } - if (method_exists($this->_conn, 'allowDdlCache')) { - $this->_conn->allowDdlCache(); + + if ($result) { + $this->_setResourceVersion($actionType, $file['toVersion']); } + } catch (Exception $e) { + printf('<pre>%s</pre>', print_r($e, true)); + throw Mage::exception('Mage_Core', Mage::helper('core')->__('Error in file: "%s" - %s', $fileName, $e->getMessage())); } - $modifyVersion = $resourceFile['toVersion']; + $version = $file['toVersion']; + $this->getConnection()->allowDdlCache(); } self::$_hadUpdates = true; - return $modifyVersion; + return $version; } /** - * Get sql files for modifications + * Get data files for modifications * - * @param $actionType - * @return array + * @param string $actionType + * @param string $fromVersion + * @param string $toVersion + * @param array $arrFiles + * @return array */ protected function _getModifySqlFiles($actionType, $fromVersion, $toVersion, $arrFiles) { $arrRes = array(); - switch ($actionType) { - case 'install': - case 'data-install': + case self::TYPE_DB_INSTALL: + case self::TYPE_DATA_INSTALL: uksort($arrFiles, 'version_compare'); foreach ($arrFiles as $version => $file) { - if (version_compare($version, $toVersion)!==self::VERSION_COMPARE_GREATER) { - $arrRes[0] = array('toVersion'=>$version, 'fileName'=>$file); + if (version_compare($version, $toVersion) !== self::VERSION_COMPARE_GREATER) { + $arrRes[0] = array( + 'toVersion' => $version, + 'fileName' => $file + ); } } break; - case 'upgrade': - case 'data-upgrade': + case self::TYPE_DB_UPGRADE: + case self::TYPE_DATA_UPGRADE: uksort($arrFiles, 'version_compare'); foreach ($arrFiles as $version => $file) { - $version_info = explode('-', $version); + $versionInfo = explode('-', $version); // In array must be 2 elements: 0 => version from, 1 => version to - if (count($version_info)!=2) { + if (count($versionInfo)!=2) { break; } - $infoFrom = $version_info[0]; - $infoTo = $version_info[1]; - if (version_compare($infoFrom, $fromVersion)!==self::VERSION_COMPARE_LOWER - && version_compare($infoTo, $toVersion)!==self::VERSION_COMPARE_GREATER) { - $arrRes[] = array('toVersion'=>$infoTo, 'fileName'=>$file); + $infoFrom = $versionInfo[0]; + $infoTo = $versionInfo[1]; + if (version_compare($infoFrom, $fromVersion) !== self::VERSION_COMPARE_LOWER + && version_compare($infoTo, $toVersion) !== self::VERSION_COMPARE_GREATER) { + $arrRes[] = array( + 'toVersion' => $infoTo, + 'fileName' => $file + ); } } break; - case 'rollback': + case self::TYPE_DB_ROLLBACK: break; - case 'uninstall': + case self::TYPE_DB_UNINSTALL: break; } return $arrRes; @@ -464,48 +724,60 @@ protected function _getModifySqlFiles($actionType, $fromVersion, $toVersion, $ar */ public function getTableRow($table, $idField, $id, $field=null, $parentField=null, $parentId=0) { - if (strpos($table, '/')!==false) { + if (strpos($table, '/') !== false) { $table = $this->getTable($table); } if (empty($this->_setupCache[$table][$parentId][$id])) { - $sql = "select * from $table where $idField=?"; + $adapter = $this->getConnection(); + $bind = array('id_field' => $id); + $select = $adapter->select() + ->from($table) + ->where($adapter->quoteIdentifier($idField) . '= :id_field'); if (!is_null($parentField)) { - $sql .= $this->_conn->quoteInto(" and $parentField=?", $parentId); + $select->where($adapter->quoteIdentifier($parentField) . '= :parent_id'); + $bind['parent_id'] = $parentId; } - $this->_setupCache[$table][$parentId][$id] = $this->_conn->fetchRow($sql, $id); + $this->_setupCache[$table][$parentId][$id] = $adapter->fetchRow($select, $bind); } + if (is_null($field)) { return $this->_setupCache[$table][$parentId][$id]; } - return isset($this->_setupCache[$table][$parentId][$id][$field]) ? $this->_setupCache[$table][$parentId][$id][$field] : false; + return isset($this->_setupCache[$table][$parentId][$id][$field]) + ? $this->_setupCache[$table][$parentId][$id][$field] + : false; } - /** + + /** * Delete table row * - * @param string $table - * @param string $idField - * @param string|int $id - * @param null|string $parentField - * @param int|string $parentId - * @return Mage_Core_Model_Resource_Setup + * @param string $table + * @param string $idField + * @param string|int $id + * @param null|string $parentField + * @param int|string $parentId + * @return Mage_Core_Model_Resource_Setup */ - public function deleteTableRow($table, $idField, $id, $parentField=null, $parentId=0) + public function deleteTableRow($table, $idField, $id, $parentField = null, $parentId = 0) { - if (strpos($table, '/')!==false) { + if (strpos($table, '/') !== false) { $table = $this->getTable($table); } - $condition = $this->_conn->quoteInto("$idField=?", $id); - if ($parentField !== null) { - $condition.= $this->_conn->quoteInto(" AND $parentField=?", $parentId); + $adapter = $this->getConnection(); + $where = array($adapter->quoteIdentifier($idField) . '=?' => $id); + if (!is_null($parentField)) { + $where[$adapter->quoteIdentifier($parentField) . '=?'] = $parentId; } - $this->_conn->delete($table, $condition); + + $adapter->delete($table, $where); if (isset($this->_setupCache[$table][$parentId][$id])) { unset($this->_setupCache[$table][$parentId][$id]); } + return $this; } @@ -521,29 +793,26 @@ public function deleteTableRow($table, $idField, $id, $parentField=null, $parent * @param string|integer $parentId * @return Mage_Eav_Model_Entity_Setup */ - public function updateTableRow($table, $idField, $id, $field, $value=null, $parentField=null, $parentId=0) + public function updateTableRow($table, $idField, $id, $field, $value = null, $parentField = null, $parentId = 0) { - if (is_array($field)) { - $updateArr = array(); - foreach ($field as $f=>$v) { - $updateArr[] = $this->_conn->quoteInto("$f=?", $v); - } - $updateStr = join(', ', $updateArr); - } else { - $updateStr = $this->_conn->quoteInto("$field=?", $value); - } - if (strpos($table, '/')!==false) { + if (strpos($table, '/') !== false) { $table = $this->getTable($table); } - $sql = "update $table set $updateStr where ".$this->_conn->quoteInto("$idField=?", $id); - if (!is_null($parentField)) { - $sql .= $this->_conn->quoteInto(" and $parentField=?", $parentId); + + if (is_array($field)) { + $data = $field; + } else { + $data = array($field => $value); } - $this->_conn->query($sql); + + $adapter = $this->getConnection(); + $where = array($adapter->quoteIdentifier($idField) . '=?' => $id); + $adapter->update($table, $data, $where); if (isset($this->_setupCache[$table][$parentId][$id])) { if (is_array($field)) { - $this->_setupCache[$table][$parentId][$id] = array_merge($this->_setupCache[$table][$parentId][$id], $field); + $this->_setupCache[$table][$parentId][$id] = + array_merge($this->_setupCache[$table][$parentId][$id], $field); } else { $this->_setupCache[$table][$parentId][$id][$field] = $value; } @@ -552,110 +821,169 @@ public function updateTableRow($table, $idField, $id, $field, $value=null, $pare return $this; } + /** + * Update table data + * + * @param string $table + * @param Zend_Db_Expr $conditionExpr + * @param Zend_Db_Expr $valueExpr + * @return Mage_Core_Model_Resource_Setup + * + * @deprecated since 1.4.0.1 + */ public function updateTable($table, $conditionExpr, $valueExpr) { - if (strpos($table, '/')!==false) { + if (strpos($table, '/') !== false) { $table = $this->getTable($table); } - $sql = 'update ' . $table . ' set ' . $valueExpr . ' where ' . $conditionExpr; - $this->_conn->query($sql); + $query = sprintf('UPDATE %s SET %s WHERE %s', + $this->getConnection()->quoteIdentifier($table), + $conditionExpr, + $valueExpr); + + $this->getConnection()->query($query); + return $this; } + /** + * Check is table exists + * + * @param string $table + * @return bool + */ public function tableExists($table) { - $select = $this->getConnection()->quoteInto('SHOW TABLES LIKE ?', $table); - $result = $this->getConnection()->fetchOne($select); - return !empty($result); + if (strpos($table, '/') !== false) { + $table = $this->getTable($table); + } + + return $this->getConnection()->isTableExists($table); + } /******************* CONFIG *****************/ + /** + * Undefined + * + * @param string $path + * @param string $label + * @param array $data + * @param string $default + * @return Mage_Core_Model_Resource_Setup + * @deprecated since 1.4.0.1 + */ public function addConfigField($path, $label, array $data=array(), $default=null) { - $data['level'] = sizeof(explode('/', $path)); - $data['path'] = $path; - $data['frontend_label'] = $label; - if ($id = $this->getTableRow('core/config_field', 'path', $path, 'field_id')) { - $this->updateTableRow('core/config_field', 'field_id', $id, $data); - } else { - if (empty($data['sort_order'])) { - $sql = "select max(sort_order) cnt from ".$this->getTable('core/config_field')." where level=".($data['level']+1); - if ($data['level']>1) { - $sql.= $this->_conn->quoteInto(" and path like ?", dirname($path).'/%'); - } - - $result = $this->_conn->raw_fetchRow($sql); - $this->_conn->fetchAll($sql); -#print_r($result); die; - $data['sort_order'] = $result['cnt']+1; -/* -// Triggers "Command out of sync" mysql error for next statement!?!? - $data['sort_order'] = $this->_conn->fetchOne("select max(sort_order) - from ".$this->getTable('core/config_field')." - where level=?".$parentWhere, $data['level'])+1; -*/ - } - - #$this->_conn->raw_query("insert into ".$this->getTable('core/config_field')." (".join(',', array_keys($data)).") values ('".join("','", array_values($data))."')"); - $this->_conn->insert($this->getTable('core/config_field'), $data); - } - - if (!is_null($default)) { - $this->setConfigData($path, $default); - } return $this; } - public function setConfigData($path, $value, $scope='default', $scopeId=0, $inherit=0) + /** + * Save configuration data + * + * @param string $path + * @param string $value + * @param int|string $scope + * @param int $scopeId + * @param int $inherit + * @return Mage_Core_Model_Resource_Setup + */ + public function setConfigData($path, $value, $scope = 'default', $scopeId = 0, $inherit=0) { - $this->_conn->showTableStatus($this->getTable('core/config_data')); // this is a fix for mysql 4.1 - $this->_conn->raw_query("replace into ".$this->getTable('core/config_data')." (scope, scope_id, path, value) values ('$scope', $scopeId, '$path', '$value')"); + $table = $this->getTable('core/config_data'); + // this is a fix for mysql 4.1 + $this->getConnection()->showTableStatus($table); + + $data = array( + 'scope' => $scope, + 'scope_id' => $scopeId, + 'path' => $path, + 'value' => $value + ); + $this->getConnection()->insertOnDuplicate($table, $data, array('value')); return $this; } /** * Delete config field values * - * @param string $path - * @param string $scope (default|stores|websites|config) - * @return Mage_Core_Model_Resource_Setup + * @param string $path + * @param string $scope (default|stores|websites|config) + * @return Mage_Core_Model_Resource_Setup */ - public function deleteConfigData($path, $scope=null) + public function deleteConfigData($path, $scope = null) { - $sql = "delete from ".$this->getTable('core/config_data')." where path='".$path."'"; - if ($scope) { - $sql.= " and scope='".$scope."'"; + $where = array('path = ?' => $path); + if (!is_null($scope)) { + $where['scope = ?'] = $scope; } - $this->_conn->raw_query($sql); + $this->getConnection()->delete($this->getTable('core/config_data'), $where); return $this; } + /** + * Run plain SQL query(ies) + * + * @param string $sql + * @return Mage_Core_Model_Resource_Setup + */ public function run($sql) { - $this->_conn->multi_query($sql); + $this->getConnection()->multiQuery($sql); return $this; } + /** + * Prepare database before install/upgrade + * + * @return Mage_Core_Model_Resource_Setup + */ public function startSetup() { - $this->_conn->multi_query("SET SQL_MODE=''; -SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; -SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO'; -"); - + $this->getConnection()->startSetup(); return $this; } + /** + * Prepare database after install/upgrade + * + * @return Mage_Core_Model_Resource_Setup + */ public function endSetup() { - $this->_conn->multi_query(" -SET SQL_MODE=IFNULL(@OLD_SQL_MODE,''); -SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS=0, 0, 1); -"); + $this->getConnection()->endSetup(); return $this; } + /** + * Retrieve 32bit UNIQUE HASH for a Table index + * + * @param string $tableName + * @param array|string $fields + * @param string $indexType + * @return string + */ + public function getIdxName($tableName, $fields, $indexType = '') + { + return Mage::getSingleton('core/resource')->getIdxName($tableName, $fields, $indexType); + } + + /** + * Retrieve 32bit UNIQUE HASH for a Table foreign key + * + * @param string $priTableName the target table name + * @param string $priColumnName the target table column name + * @param string $refTableName the reference table name + * @param string $refColumnName the reference table column name + * @return string + */ + public function getFkName($priTableName, $priColumnName, $refTableName, $refColumnName) + { + return Mage::getSingleton('core/resource') + ->getFkName($priTableName, $priColumnName, $refTableName, $refColumnName); + } + /** * Check call afterApplyAllUpdates method for setup class * diff --git a/app/code/core/Mage/Core/Model/Resource/Setup/Query/Modifier.php b/app/code/core/Mage/Core/Model/Resource/Setup/Query/Modifier.php new file mode 100644 index 00000000..72632af1 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Setup/Query/Modifier.php @@ -0,0 +1,349 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Modifier of queries, developed for backwards compatibility on MySQL, + * while creating foreign keys + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Setup_Query_Modifier +{ + /** + * MySQL adapter instance + * + * @var Varien_Db_Adapter_Pdo_Mysql + */ + protected $_adapter; + + /** + * Types of column we process for foreign keys + * + * @var array + */ + protected $_processedTypes = array('tinyint', 'smallint', 'mediumint', 'int', 'longint'); + + /** + * Inits query modifier + * + * @param $adapter Varien_Db_Adapter_Pdo_Mysql + * @return void + */ + public function __construct($args) + { + $this->_adapter = $args[0]; + } + + /** + * Returns column definition from CREATE TABLE sql + * + * @param string $sql + * @param string $column + * @return array + */ + protected function _getColumnDefinitionFromSql($sql, $column) + { + $result = null; + foreach ($this->_processedTypes as $type) { + $pattern = '/\s([^\s]+)\s+' . $type . '[^\s]*(\s+[^,]+)/i'; + if (!preg_match_all($pattern, $sql, $matches, PREG_SET_ORDER)) { + continue; + } + foreach ($matches as $match) { + $gotColumn = $this->_prepareIdentifier($match[1]); + if ($gotColumn != $column) { + continue; + } + + $definition = $match[2]; + $unsigned = preg_match('/\sUNSIGNED/i', $definition) > 0; + + $result = array( + 'type' => $type, + 'unsigned' => $unsigned + ); + break; + } + if ($result) { + break; + } + } + + return $result; + } + + /** + * Replaces first occurence of $needle in a $haystack + * + * @param string $haystack + * @param string $needle + * @param array $replacement + * @param bool $caseInsensitive + * @return string + */ + protected function _firstReplace($haystack, $needle, $replacement, $caseInsensitive = false) + { + $pos = $caseInsensitive ? stripos($haystack, $needle) : strpos($haystack, $needle); + if ($pos === false) { + return $haystack; + } + + return substr($haystack, 0, $pos) . $replacement . substr($haystack, $pos + strlen($needle)); + } + + /** + * Fixes column definition in CREATE TABLE sql to match defintion of column it's set to + * + * @param string $sql + * @param string $column + * @param array $refColumnDefinition + * @return Mage_Core_Model_Resource_Setup_Query_Modifier + */ + protected function _fixColumnDefinitionInSql(&$sql, $column, $refColumnDefinition) + { + $pos = stripos($sql, "`{$column}`"); // First try to find column directly recorded + if ($pos === false) { + $pattern = '/[`\s]' . preg_quote($column, '/') . '[`\s]/i'; + if (!preg_match($pattern, $sql, $matches)) { + return $this; + } + + $columnEntry = $matches[0]; + $pos = strpos($sql, $columnEntry); + if ($pos === false) { + return $this; + } + } + + $startSql = substr($sql, 0, $pos); + $restSql = substr($sql, $pos); + + // Column type definition + $columnDefinition = $this->_getColumnDefinitionFromSql($sql, $column); + if (!$columnDefinition) { + return $this; + } + + // Find pattern for type defintion + $pattern = '/\s*([^\s]+)\s+(' . $columnDefinition['type'] . '[^\s]*)\s+([^,]+)/i'; + if (!preg_match($pattern, $restSql, $matches)) { + return $this; + } + + // Replace defined type with needed type + $typeDefined = $matches[2]; + $typeNeeded = $refColumnDefinition['type']; + if ($refColumnDefinition['unsigned'] && !$columnDefinition['unsigned']) { + $typeNeeded .= ' unsigned'; + } + + $restSql = $this->_firstReplace($restSql, $typeDefined, $typeNeeded); + + if (!$refColumnDefinition['unsigned'] && ($columnDefinition['unsigned'])) { + $restSql = $this->_firstReplace($restSql, 'unsigned', '', true); + } + + // Compose SQL back + $sql = $startSql . $restSql; + + return $this; + } + + /** + * Fixes column definition in already existing table, so outgoing foreign key will be successfully set + * + * @param string $sql + * @param string $column + * @param array $refColumnDefinition + * @return Mage_Core_Model_Resource_Setup_Query_Modifier + */ + protected function _fixColumnDefinitionInTable($table, $column, $refColumnDefinition) + { + $description = $this->_adapter->fetchAll('DESCRIBE ' . $table); + foreach ($description as $columnData) { + $columnName = $this->_prepareIdentifier($columnData['Field']); + if ($columnName != $column) { + continue; + } + $definition = $refColumnDefinition['type']; + if ($refColumnDefinition['unsigned']) { + $definition .= ' UNSIGNED'; + } + if ($columnData['Null'] == 'YES') { + $definition .= ' NULL'; + } else { + $definition .= ' NOT NULL'; + } + if ($columnData['Default']) { + $definition .= ' DEFAULT ' . $columnData['Default']; + } + if ($columnData['Extra']) { + $definition .= ' ' . $columnData['Extra']; + } + + $query = 'ALTER TABLE ' . $table . ' MODIFY COLUMN ' . $column . ' ' . $definition; + $this->_adapter->query($query); + } + return $this; + } + + /** + * Returns column definition from already existing table + * + * @param string $sql + * @param string $column + * @return array|null + */ + protected function _getColumnDefinitionFromTable($table, $column) + { + $description = $this->_adapter->describeTable($table); + if (!isset($description[$column])) { + return null; + } + + return array( + 'type' => $this->_prepareIdentifier($description[$column]['DATA_TYPE']), + 'unsigned' => (bool) $description[$column]['UNSIGNED'] + ); + } + + /** + * Returns whether table exists + * + * @param string $table + * @return bool + */ + protected function _tableExists($table) + { + $rows = $this->_adapter->fetchAll('SHOW TABLES'); + foreach ($rows as $row) { + $tableFound = strtolower(current($row)); + if ($table == $tableFound) { + return true; + } + } + return false; + } + + /** + * Trims and lowercases identifier, to make common view of all of them + * + * @param string $identifier + * @return string + */ + protected function _prepareIdentifier($identifier) + { + return strtolower(trim($identifier, "`\n\r\t")); + } + + /** + * Processes query, modifies targeted columns to fit foreign keys restrictions + * + * @param string $sql + * @param array $bind + * @return Mage_Core_Model_Resource_Setup_Query_Modifier + */ + public function processQuery(&$sql, &$bind) + { + // Quick test to skip queries without foreign keys + if (!stripos($sql, 'foreign')) { + return $this; + } + + // Find foreign keys set + $pattern = '/CONSTRAINT\s+[^\s]+\s+FOREIGN\s+KEY[^(]+\\(([^),]+)\\)\s+REFERENCES\s+([^\s.]+)\s+\\(([^)]+)\\)/i'; + if (!preg_match_all($pattern, $sql, $matchesFk, PREG_SET_ORDER)) { + return $this; + } + + // Get current table name + if (!preg_match('/\s*(CREATE|ALTER)\s+TABLE\s+([^\s.]+)/i', $sql, $match)) { + return $this; + } + + $operation = $this->_prepareIdentifier($match[1]); + $table = $this->_prepareIdentifier($match[2]); + + // Process all + foreach ($matchesFk as $match) { + $column = $this->_prepareIdentifier($match[1]); + $refTable = $this->_prepareIdentifier($match[2]); + $refColumn = $this->_prepareIdentifier($match[3]); + + // Check tables existance + if (($operation != 'create') && !($this->_tableExists($table))) { + continue; + } + if (!$this->_tableExists($refTable)) { + continue; + } + + // Self references are out of our fix scope + if ($refTable == $table) { + continue; + } + + // Extract column type + if ($operation == 'create') { + $columnDefinition = $this->_getColumnDefinitionFromSql($sql, $column); + } else { + $columnDefinition = $this->_getColumnDefinitionFromTable($table, $column); + } + + // We fix only int columns + if (!$columnDefinition || !in_array($columnDefinition['type'], $this->_processedTypes)) { + continue; + } + + // Extract referenced column type + $refColumnDefinition = $this->_getColumnDefinitionFromTable($refTable, $refColumn); + if (!$refColumnDefinition) { + continue; + } + + // We fix only int columns + if (!$refColumnDefinition || !in_array($refColumnDefinition['type'], $this->_processedTypes)) { + continue; + } + + // Whether we need to fix + if ($refColumnDefinition == $columnDefinition) { + continue; + } + + // Fix column to be the same type as referenced one + if ($operation == 'create') { + $this->_fixColumnDefinitionInSql($sql, $column, $refColumnDefinition); + } else { + $this->_fixColumnDefinitionInTable($table, $column, $refColumnDefinition); + } + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Store.php b/app/code/core/Mage/Core/Model/Resource/Store.php new file mode 100644 index 00000000..4ac77866 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Store.php @@ -0,0 +1,175 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Store Resource Model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Store extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table and primary key + * + */ + protected function _construct() + { + $this->_init('core/store', 'store_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Core_Model_Resource_Store + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array(array( + 'field' => 'code', + 'title' => Mage::helper('core')->__('Store with the same code') + )); + return $this; + } + + /** + * Check store code before save + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Store + */ + protected function _beforeSave(Mage_Core_Model_Abstract $model) + { + if (!preg_match('/^[a-z]+[a-z0-9_]*$/', $model->getCode())) { + Mage::throwException( + Mage::helper('core')->__('The store code may contain only letters (a-z), numbers (0-9) or underscore(_), the first character must be a letter')); + } + + return $this; + } + + /** + * Update Store Group data after save store + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Store + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + parent::_afterSave($object); + $this->_updateGroupDefaultStore($object->getGroupId(), $object->getId()); + $this->_changeGroup($object); + + return $this; + } + + /** + * Remove core configuration data after delete store + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Store + */ + protected function _afterDelete(Mage_Core_Model_Abstract $model) + { + $where = array( + 'scope = ?' => 'stores', + 'scope_id = ?' => $model->getStoreId() + ); + + $this->_getWriteAdapter()->delete( + $this->getTable('core/config_data'), + $where + ); + return $this; + } + + /** + * Update Default store for Store Group + * + * @param int $groupId + * @param int $storeId + * @return Mage_Core_Model_Resource_Store + */ + protected function _updateGroupDefaultStore($groupId, $storeId) + { + $adapter = $this->_getWriteAdapter(); + + $bindValues = array('group_id' => (int)$groupId); + $select = $adapter->select() + ->from($this->getMainTable(), array('count' => 'COUNT(*)')) + ->where('group_id = :group_id'); + $count = $adapter->fetchOne($select, $bindValues); + + if ($count == 1) { + $bind = array('default_store_id' => (int)$storeId); + $where = array('group_id = ?' => (int)$groupId); + $adapter->update($this->getTable('core/store_group'), $bind, $where); + } + + return $this; + } + + /** + * Change store group for store + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Store + */ + protected function _changeGroup(Mage_Core_Model_Abstract $model) + { + if ($model->getOriginalGroupId() && $model->getGroupId() != $model->getOriginalGroupId()) { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getTable('core/store_group'), 'default_store_id') + ->where($adapter->quoteInto('group_id=?', $model->getOriginalGroupId())); + $storeId = $adapter->fetchOne($select, 'default_store_id'); + + if ($storeId == $model->getId()) { + $bind = array('default_store_id' => Mage_Core_Model_App::ADMIN_STORE_ID); + $where = array('group_id = ?' => $model->getOriginalGroupId()); + $this->_getWriteAdapter()->update($this->getTable('core/store_group'), $bind, $where); + } + } + return $this; + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Abstract $object + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $select->order('sort_order'); + return $select; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Store/Collection.php b/app/code/core/Mage/Core/Model/Resource/Store/Collection.php new file mode 100644 index 00000000..67e6223a --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Store/Collection.php @@ -0,0 +1,209 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Stores collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Store_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Load default flag + * + * @deprecated since 1.5.0.0 + * @var boolean + */ + protected $_loadDefault = false; + + /** + * Define resource model + * + */ + protected function _construct() + { + $this->setFlag('load_default_store', false); + $this->_init('core/store'); + } + + /** + * Set flag for load default (admin) store + * + * @param boolean $loadDefault + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function setLoadDefault($loadDefault) + { + $this->setFlag('load_default_store', (bool)$loadDefault); + return $this; + } + + /** + * Is load default (admin) store + * + * @return boolean + */ + public function getLoadDefault() + { + return $this->getFlag('load_default_store'); + } + + /** + * Add disable default store filter to collection + * + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function setWithoutDefaultFilter() + { + $this->addFieldToFilter('main_table.store_id', array('gt' => 0)); + return $this; + } + + /** + * Add filter by group id. + * Group id can be passed as one single value or array of values. + * + * @param int|array $groupId + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function addGroupFilter($groupId) + { + return $this->addFieldToFilter('main_table.group_id', array('in' => $groupId)); + } + + /** + * Add store id(s) filter to collection + * + * @param int|array $store + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function addIdFilter($store) + { + return $this->addFieldToFilter('main_table.store_id', array('in' => $store)); + } + + /** + * Add filter by website to collection + * + * @param int|array $website + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function addWebsiteFilter($website) + { + return $this->addFieldToFilter('main_table.website_id', array('in' => $website)); + } + + /** + * Add root category id filter to store collection + * + * @param int|array $category + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function addCategoryFilter($category) + { + if (!is_array($category)) { + $category = array($category); + } + return $this->loadByCategoryIds($category); + } + + /** + * Convert items array to array for select options + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('store_id', 'name'); + } + + /** + * Convert items array to hash for select options + * + * @return array + */ + public function toOptionHash() + { + return $this->_toOptionHash('store_id', 'name'); + } + + /** + * Load collection data + * + * @param boolean $printQuery + * @param boolean $logQuery + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function load($printQuery = false, $logQuery = false) + { + if (!$this->getLoadDefault()) { + $this->setWithoutDefaultFilter(); + } + + if (!$this->isLoaded()) { + $this->addOrder('CASE WHEN main_table.store_id = 0 THEN 0 ELSE 1 END', Varien_Db_Select::SQL_ASC) + ->addOrder('main_table.sort_order', Varien_Db_Select::SQL_ASC) + ->addOrder('main_table.name', Varien_Db_Select::SQL_ASC); + } + return parent::load($printQuery, $logQuery); + } + + /** + * Add root category id filter to store collection + * + * @param array $categories + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function loadByCategoryIds(array $categories) + { + $this->addRootCategoryIdAttribute(); + $this->addFieldToFilter('group_table.root_category_id', array('in' => $categories)); + + return $this; + } + + /** + * Add store root category data to collection + * + * @return Mage_Core_Model_Resource_Store_Collection + */ + public function addRootCategoryIdAttribute() + { + if (!$this->getFlag('core_store_group_table_joined')) { + $this->getSelect()->join( + array('group_table' => $this->getTable('core/store_group')), + 'main_table.group_id = group_table.group_id', + array('root_category_id') + ); + $this->setFlag('core_store_group_table_joined', true); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Store/Group.php b/app/code/core/Mage/Core/Model/Resource/Store/Group.php new file mode 100644 index 00000000..84884a73 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Store/Group.php @@ -0,0 +1,136 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Store group resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Store_Group extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/store_group', 'group_id'); + } + + /** + * Update default store group for website + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Store_Group + */ + protected function _afterSave(Mage_Core_Model_Abstract $model) + { + $this->_updateStoreWebsite($model->getId(), $model->getWebsiteId()); + $this->_updateWebsiteDefaultGroup($model->getWebsiteId(), $model->getId()); + $this->_changeWebsite($model); + + return $this; + } + + /** + * Update default store group for website + * + * @param int $websiteId + * @param int $groupId + * @return Mage_Core_Model_Resource_Store_Group + */ + protected function _updateWebsiteDefaultGroup($websiteId, $groupId) + { + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), 'COUNT(*)') + ->where('website_id = :website'); + $count = $this->_getWriteAdapter()->fetchOne($select, array('website' => $websiteId)); + + if ($count == 1) { + $bind = array('default_group_id' => $groupId); + $where = array('website_id = ?' => $websiteId); + $this->_getWriteAdapter()->update($this->getTable('core/website'), $bind, $where); + } + return $this; + } + + /** + * Change store group website + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Store_Group + */ + protected function _changeWebsite(Mage_Core_Model_Abstract $model) + { + if ($model->getOriginalWebsiteId() && $model->getWebsiteId() != $model->getOriginalWebsiteId()) { + $select = $this->_getWriteAdapter()->select() + ->from($this->getTable('core/website'), 'default_group_id') + ->where('website_id = :website_id'); + $groupId = $this->_getWriteAdapter()->fetchOne($select, array('website_id' => $model->getOriginalWebsiteId())); + + if ($groupId == $model->getId()) { + $bind = array('default_group_id' => 0); + $where = array('website_id = ?' => $model->getOriginalWebsiteId()); + $this->_getWriteAdapter()->update($this->getTable('core/website'), $bind, $where); + } + } + return $this; + } + + /** + * Update website for stores that assigned to store group + * + * @param int $groupId + * @param int $websiteId + * @return Mage_Core_Model_Resource_Store_Group + */ + protected function _updateStoreWebsite($groupId, $websiteId) + { + $bind = array('website_id' => $websiteId); + $where = array('group_id = ?' => $groupId); + $this->_getWriteAdapter()->update($this->getTable('core/store'), $bind, $where); + return $this; + } + + /** + * Save default store for store group + * + * @param int $groupId + * @param int $storeId + * @return Mage_Core_Model_Resource_Store_Group + */ + protected function _saveDefaultStore($groupId, $storeId) + { + $bind = array('default_store_id' => $storeId); + $where = array('group_id = ?' => $groupId); + $this->_getWriteAdapter()->update($this->getMainTable(), $bind, $where); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Store/Group/Collection.php b/app/code/core/Mage/Core/Model/Resource/Store/Group/Collection.php new file mode 100644 index 00000000..dca55de8 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Store/Group/Collection.php @@ -0,0 +1,132 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Store group collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Store_Group_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Load default flag + * + * @deprecated since 1.5.0.0 + * @var boolean + */ + protected $_loadDefault = false; + + /** + * Define resource model + * + */ + protected function _construct() + { + $this->setFlag('load_default_store_group', false); + $this->_init('core/store_group'); + } + + /** + * Set flag for load default (admin) store + * + * @param boolean $loadDefault + * + * @return Mage_Core_Model_Resource_Store_Group_Collection + */ + public function setLoadDefault($loadDefault) + { + return $this->setFlag('load_default_store_group', (bool)$loadDefault); + } + + /** + * Is load default (admin) store + * + * @return boolean + */ + public function getLoadDefault() + { + return $this->getFlag('load_default_store_group'); + } + + /** + * Add disable default store group filter to collection + * + * @return Mage_Core_Model_Resource_Store_Group_Collection + */ + public function setWithoutDefaultFilter() + { + return $this->addFieldToFilter('main_table.group_id', array('gt' => 0)); + } + + /** + * Filter to discard stores without views + * + * @return Mage_Core_Model_Resource_Store_Group_Collection + */ + public function setWithoutStoreViewFilter() + { + return $this->addFieldToFilter('main_table.default_store_id', array('gt' => 0)); + } + + /** + * Load collection data + * + * @return Mage_Core_Model_Resource_Store_Group_Collection + */ + public function _beforeLoad() + { + if (!$this->getLoadDefault()) { + $this->setWithoutDefaultFilter(); + } + $this->addOrder('main_table.name', self::SORT_ORDER_ASC); + return parent::_beforeLoad(); + } + + /** + * Convert collection items to array for select options + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('group_id', 'name'); + } + + /** + * Add filter by website to collection + * + * @param int|array $website + * + * @return Mage_Core_Model_Resource_Store_Group_Collection + */ + public function addWebsiteFilter($website) + { + return $this->addFieldToFilter('main_table.website_id', array('in' => $website)); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Transaction.php b/app/code/core/Mage/Core/Model/Resource/Transaction.php index 3d13ffbc..e6ae97ec 100644 --- a/app/code/core/Mage/Core/Model/Resource/Transaction.php +++ b/app/code/core/Mage/Core/Model/Resource/Transaction.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -110,9 +110,9 @@ protected function _runCallbacks() /** * Adding object for using in transaction * - * @param Mage_Core_Model_Abstract $object - * @param string $alias - * @return Mage_Core_Model_Resource_Transaction + * @param Mage_Core_Model_Abstract $object + * @param string $alias + * @return Mage_Core_Model_Resource_Transaction */ public function addObject(Mage_Core_Model_Abstract $object, $alias='') { @@ -124,10 +124,10 @@ public function addObject(Mage_Core_Model_Abstract $object, $alias='') } /** - * Add callback funtion which will be called befor commit transactions + * Add callback function which will be called before commit transactions * - * @param callback $callback - * @return Mage_Core_Model_Resource_Transaction + * @param callback $callback + * @return Mage_Core_Model_Resource_Transaction */ public function addCommitCallback($callback) { @@ -139,6 +139,7 @@ public function addCommitCallback($callback) * Initialize objects save transaction * * @return Mage_Core_Model_Resource_Transaction + * @throws Exception */ public function save() { @@ -175,11 +176,12 @@ public function save() * Initialize objects delete transaction * * @return Mage_Core_Model_Resource_Transaction + * @throws Exception */ public function delete() { $this->_startTransaction(); - $error = false; + $error = false; try { foreach ($this->_objects as $object) { diff --git a/app/code/core/Mage/Core/Model/Resource/Translate.php b/app/code/core/Mage/Core/Model/Resource/Translate.php new file mode 100644 index 00000000..f8617dae --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Translate.php @@ -0,0 +1,129 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Translation resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Translate extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/translate', 'key_id'); + } + + /** + * Retrieve translation array for store / locale code + * + * @param int $storeId + * @param string|Zend_Locale $locale + * @return array + */ + public function getTranslationArray($storeId = null, $locale = null) + { + if (!Mage::isInstalled()) { + return array(); + } + + if (is_null($storeId)) { + $storeId = Mage::app()->getStore()->getId(); + } + + $adapter = $this->_getReadAdapter(); + if (!$adapter) { + return array(); + } + + $select = $adapter->select() + ->from($this->getMainTable(), array('string', 'translate')) + ->where('store_id IN (0 , :store_id)') + ->where('locale = :locale') + ->order('store_id'); + + $bind = array( + ':locale' => (string)$locale, + ':store_id' => $storeId + ); + + return $adapter->fetchPairs($select, $bind); + + } + + /** + * Retrieve translations array by strings + * + * @param array $strings + * @param int_type $storeId + * @return array + */ + public function getTranslationArrayByStrings(array $strings, $storeId = null) + { + if (!Mage::isInstalled()) { + return array(); + } + + if (is_null($storeId)) { + $storeId = Mage::app()->getStore()->getId(); + } + + $adapter = $this->_getReadAdapter(); + if (!$adapter) { + return array(); + } + + if (empty($strings)) { + return array(); + } + + $bind = array( + ':store_id' => $storeId + ); + $select = $adapter->select() + ->from($this->getMainTable(), array('string', 'translate')) + ->where('string IN (?)', $strings) + ->where('store_id = :store_id'); + + return $adapter->fetchPairs($select, $bind); + } + + /** + * Retrieve table checksum + * + * @return int + */ + public function getMainChecksum() + { + return $this->getChecksum($this->getMainTable()); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Translate/String.php b/app/code/core/Mage/Core/Model/Resource/Translate/String.php new file mode 100644 index 00000000..c992c0e9 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Translate/String.php @@ -0,0 +1,254 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * String translate resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Translate_String extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/translate', 'key_id'); + } + + /** + * Load + * + * @param Mage_Core_Model_Abstract $object + * @param String $value + * @param String $field + * @return array + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (is_string($value)) { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where($this->getMainTable().'.string=:tr_string'); + $result = $this->_getReadAdapter()->fetchRow($select, array('tr_string'=>$value)); + $object->setData($result); + $this->_afterLoad($object); + return $result; + } else { + return parent::load($object, $value, $field); + } + } + + /** + * Retrieve select for load + * + * @param String $field + * @param String $value + * @param Mage_Core_Model_Abstract $object + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $select->where('store_id = ?', Mage_Core_Model_App::ADMIN_STORE_ID); + return $select; + } + + /** + * After translation loading + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + public function _afterLoad(Mage_Core_Model_Abstract $object) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), array('store_id', 'translate')) + ->where('string = :translate_string'); + $translations = $adapter->fetchPairs($select, array('translate_string' => $object->getString())); + $object->setStoreTranslations($translations); + return parent::_afterLoad($object); + } + + /** + * Before save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Translate_String + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $adapter = $this->_getWriteAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), 'key_id') + ->where('string = :string') + ->where('store_id = :store_id'); + + $bind = array( + 'string' => $object->getString(), + 'store_id' => Mage_Core_Model_App::ADMIN_STORE_ID + ); + + $object->setId($adapter->fetchOne($select, $bind)); + return parent::_beforeSave($object); + } + + /** + * After save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Translate_String + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $adapter = $this->_getWriteAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), array('store_id', 'key_id')) + ->where('string = :string'); + $stores = $adapter->fetchPairs($select, array('string' => $object->getString())); + + $translations = $object->getStoreTranslations(); + + if (is_array($translations)) { + foreach ($translations as $storeId => $translate) { + if (is_null($translate) || $translate=='') { + $where = array( + 'store_id = ?' => $storeId, + 'string = ?' => $object->getString() + ); + $adapter->delete($this->getMainTable(), $where); + } else { + $data = array( + 'store_id' => $storeId, + 'string' => $object->getString(), + 'translate' => $translate, + ); + + if (isset($stores[$storeId])) { + $adapter->update( + $this->getMainTable(), + $data, + array('key_id = ?' => $stores[$storeId])); + } else { + $adapter->insert($this->getMainTable(), $data); + } + } + } + } + return parent::_afterSave($object); + } + + /** + * Delete translates + * + * @param string $string + * @param string $locale + * @param int|null $storeId + * @return Mage_Core_Model_Resource_Translate_String + */ + public function deleteTranslate($string, $locale = null, $storeId = null) + { + if (is_null($locale)) { + $locale = Mage::app()->getLocale()->getLocaleCode(); + } + + $where = array( + 'locale = ?' => $locale, + 'string = ?' => $string + ); + + if ($storeId === false) { + $where['store_id > ?'] = Mage_Core_Model_App::ADMIN_STORE_ID; + } elseif ($storeId !== null) { + $where['store_id > ?'] = $storeId; + } + + $this->_getWriteAdapter()->delete($this->getMainTable(), $where); + + return $this; + } + + /** + * Save translation + * + * @param String $string + * @param String $translate + * @param String $locale + * @param int|null $storeId + * @return Mage_Core_Model_Resource_Translate_String + */ + public function saveTranslate($string, $translate, $locale = null, $storeId = null) + { + $write = $this->_getWriteAdapter(); + $table = $this->getMainTable(); + + if (is_null($locale)) { + $locale = Mage::app()->getLocale()->getLocaleCode(); + } + + if (is_null($storeId)) { + $storeId = Mage::app()->getStore()->getId(); + } + + $select = $write->select() + ->from($table, array('key_id', 'translate')) + ->where('store_id = :store_id') + ->where('locale = :locale') + ->where('string = :string') + ; + $bind = array( + 'store_id' => $storeId, + 'locale' => $locale, + 'string' => $string + ); + + if ($row = $write->fetchRow($select, $bind)) { + $original = $string; + if (strpos($original, '::') !== false) { + list($scope, $original) = explode('::', $original); + } + if ($original == $translate) { + $write->delete($table, array('key_id=?' => $row['key_id'])); + } elseif ($row['translate'] != $translate) { + $write->update($table, array('translate' => $translate), array('key_id=?' => $row['key_id'])); + } + } else { + $write->insert($table, array( + 'store_id' => $storeId, + 'locale' => $locale, + 'string' => $string, + 'translate' => $translate, + )); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Type/Abstract.php b/app/code/core/Mage/Core/Model/Resource/Type/Abstract.php index 71dbca04..870f7386 100644 --- a/app/code/core/Mage/Core/Model/Resource/Type/Abstract.php +++ b/app/code/core/Mage/Core/Model/Resource/Type/Abstract.php @@ -20,26 +20,52 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ abstract class Mage_Core_Model_Resource_Type_Abstract { + /** + * Name + * + * @var String + */ protected $_name = ''; + + /** + * Entity class + * + * @var String + */ protected $_entityClass = 'Mage_Core_Model_Resource_Entity_Abstract'; + /** + * Retrieve entity type + * + * @return String + */ public function getEntityClass() { return $this->_entityClass; } + /** + * Set name + * + * @param String $name + */ public function setName($name) { $this->_name = $name; } + /** + * Retrieve name + * + * @return String + */ public function getName() { return $this->_name; diff --git a/app/code/core/Mage/Core/Model/Resource/Type/Db.php b/app/code/core/Mage/Core/Model/Resource/Type/Db.php index 0ba41fe9..6ebb2af6 100644 --- a/app/code/core/Mage/Core/Model/Resource/Type/Db.php +++ b/app/code/core/Mage/Core/Model/Resource/Type/Db.php @@ -20,13 +20,15 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - abstract class Mage_Core_Model_Resource_Type_Db extends Mage_Core_Model_Resource_Type_Abstract { + /** + * Constructor + */ public function __construct() { $this->_entityClass = 'Mage_Core_Model_Resource_Entity_Table'; diff --git a/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli.php b/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli.php index bbd316e4..e9f4f27e 100644 --- a/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli.php +++ b/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,6 +34,12 @@ */ class Mage_Core_Model_Resource_Type_Db_Mysqli extends Mage_Core_Model_Resource_Type_Db { + /** + * Get Connection + * + * @param Array $config + * @return Varien_Db_Adapter_Mysqli + */ public function getConnection($config) { $configArr = (array)$config; diff --git a/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli/Setup.php b/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli/Setup.php index 8021f353..50e165d9 100644 --- a/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli/Setup.php +++ b/app/code/core/Mage/Core/Model/Resource/Type/Db/Mysqli/Setup.php @@ -20,13 +20,19 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Core_Model_Resource_Type_Db_Mysqli_Setup extends Mage_Core_Model_Resource_Type_Db { + /** + * Get connection + * + * @param Array $config + * @return Varien_Db_Adapter_Mysqli + */ public function getConnection($config) { $conn = new Varien_Db_Adapter_Mysqli((array)$config); diff --git a/app/code/core/Mage/Core/Model/Resource/Type/Db/Pdo/Mysql.php b/app/code/core/Mage/Core/Model/Resource/Type/Db/Pdo/Mysql.php index bbbc74f9..32e99cb1 100644 --- a/app/code/core/Mage/Core/Model/Resource/Type/Db/Pdo/Mysql.php +++ b/app/code/core/Mage/Core/Model/Resource/Type/Db/Pdo/Mysql.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,7 +29,7 @@ class Mage_Core_Model_Resource_Type_Db_Pdo_Mysql extends Mage_Core_Model_Resourc { /** - * Enter description here... + * Get connection * * @param array $config Connection config * @return Varien_Db_Adapter_Pdo_Mysql diff --git a/app/code/core/Mage/Core/Model/Resource/Url/Rewrite.php b/app/code/core/Mage/Core/Model/Resource/Url/Rewrite.php new file mode 100644 index 00000000..6294e779 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Url/Rewrite.php @@ -0,0 +1,182 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Url rewrite resource model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Url_Rewrite extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Tag table + * + * @var string + */ + protected $_tagTable; + + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/url_rewrite', 'url_rewrite_id'); + $this->_tagTable = $this->getTable('url_rewrite_tag'); + } + + /** + * Initialize array fields + * + * @return Mage_Core_Model_Resource_Url_Rewrite + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array( + array( + 'field' => array('id_path','store_id','is_system'), + 'title' => Mage::helper('core')->__('ID Path for Specified Store') + ), + array( + 'field' => array('request_path','store_id'), + 'title' => Mage::helper('core')->__('Request Path for Specified Store'), + ) + ); + return $this; + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Url_Rewrite $object + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + /** @var $select Varien_Db_Select */ + $select = parent::_getLoadSelect($field, $value, $object); + + if (!is_null($object->getStoreId())) { + $select->where('store_id IN(?)', array(Mage_Core_Model_App::ADMIN_STORE_ID, $object->getStoreId())); + $select->order('store_id ' . Varien_Db_Select::SQL_DESC); + $select->limit(1); + } + + return $select; + } + + /** + * Retrieve request_path using id_path and current store's id. + * + * @param string $idPath + * @param int|Mage_Core_Model_Store $store + * @return string|false + */ + public function getRequestPathByIdPath($idPath, $store) + { + if ($store instanceof Mage_Core_Model_Store) { + $storeId = (int)$store->getId(); + } else { + $storeId = (int)$store; + } + + $select = $this->_getReadAdapter()->select(); + /** @var $select Varien_Db_Select */ + $select->from(array('main_table' => $this->getMainTable()), 'request_path') + ->where('main_table.store_id = :store_id') + ->where('main_table.id_path = :id_path') + ->limit(1); + + $bind = array( + 'store_id' => $storeId, + 'id_path' => $idPath + ); + + return $this->_getReadAdapter()->fetchOne($select, $bind); + } + + /** + * Load rewrite information for request + * If $path is array - we must load all possible records and choose one matching earlier record in array + * + * @param Mage_Core_Model_Url_Rewrite $object + * @param array|string $path + * @return Mage_Core_Model_Resource_Url_Rewrite + */ + public function loadByRequestPath(Mage_Core_Model_Url_Rewrite $object, $path) + { + if (!is_array($path)) { + $path = array($path); + } + + $pathBind = array(); + foreach ($path as $key => $url) { + $pathBind['path' . $key] = $url; + } + // Form select + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('request_path IN (:' . implode(', :', array_flip($pathBind)) . ')') + ->where('store_id IN(?)', array(Mage_Core_Model_App::ADMIN_STORE_ID, (int)$object->getStoreId())); + + $items = $adapter->fetchAll($select, $pathBind); + + // Go through all found records and choose one with lowest penalty - earlier path in array, concrete store + $mapPenalty = array_flip(array_values($path)); // we got mapping array(path => index), lower index - better + $currentPenalty = null; + $foundItem = null; + foreach ($items as $item) { + if (!array_key_exists($item['request_path'], $mapPenalty)) { + continue; + } + $penalty = $mapPenalty[$item['request_path']] << 1 + ($item['store_id'] ? 0 : 1); + if (!$foundItem || $currentPenalty > $penalty) { + $foundItem = $item; + $currentPenalty = $penalty; + if (!$currentPenalty) { + break; // Found best matching item with zero penalty, no reason to continue + } + } + } + + // Set data and finish loading + if ($foundItem) { + $object->setData($foundItem); + } + + // Finish + $this->unserializeFields($object); + $this->_afterLoad($object); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Url/Rewrite/Collection.php b/app/code/core/Mage/Core/Model/Resource/Url/Rewrite/Collection.php new file mode 100644 index 00000000..553bfc1e --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Url/Rewrite/Collection.php @@ -0,0 +1,115 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Url rewrite resource collection model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Url_Rewrite_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model + * + */ + protected function _construct() + { + $this->_init('core/url_rewrite'); + } + + /** + * Add filter for tags (combined by OR) + * + * @param string|array $tags + * @return Mage_Core_Model_Resource_Url_Rewrite_Collection + */ + public function addTagsFilter($tags) + { + $tags = is_array($tags) ? $tags : explode(',', $tags); + + if (!$this->getFlag('tag_table_joined')) { + $this->join( + array('curt' => $this->getTable('core/url_rewrite_tag')), + 'main_table.url_rewrite_id = curt.url_rewrite_id', + array()); + $this->setFlag('tag_table_joined', true); + } + + $this->addFieldToFilter('curt.tag', array('in' => $tags)); + return $this; + } + + /** + * Filter collections by stores + * + * @param mixed $store + * @param bool $withAdmin + * @return Mage_Core_Model_Resource_Url_Rewrite_Collection + */ + public function addStoreFilter($store, $withAdmin = true) + { + if (!is_array($store)) { + $store = array(Mage::app()->getStore($store)->getId()); + } + if ($withAdmin) { + $store[] = 0; + } + + $this->addFieldToFilter('store_id', array('in' => $store)); + + return $this; + } + + /** + * Add filter by catalog product Id + * + * @param int $productId + * @return Mage_Core_Model_Resource_Url_Rewrite_Collection + */ + public function filterAllByProductId($productId) + { + $this->getSelect() + ->where('id_path = ?', "product/{$productId}") + ->orWhere('id_path LIKE ?', "product/{$productId}/%"); + + return $this; + } + + /** + * Add filter by all catalog category + * + * @return Mage_Core_Model_Resource_Url_Rewrite_Collection + */ + public function filterAllByCategory() + { + $this->getSelect() + ->where('id_path LIKE ?', "category/%"); + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Variable.php b/app/code/core/Mage/Core/Model/Resource/Variable.php new file mode 100644 index 00000000..d91c35bc --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Variable.php @@ -0,0 +1,160 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Custom variable resource model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Variable extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Constructor + * + */ + protected function _construct() + { + $this->_init('core/variable', 'variable_id'); + } + + /** + * Load variable by code + * + * @param Mage_Core_Model_Variable $object + * @param string $code + * @return Mage_Core_Model_Resource_Variable + */ + public function loadByCode(Mage_Core_Model_Variable $object, $code) + { + if ($result = $this->getVariableByCode($code, true, $object->getStoreId())) { + $object->setData($result); + } + return $this; + } + + /** + * Retrieve variable data by code + * + * @param string $code + * @param boolean $withValue + * @param integer $storeId + * @return array + */ + public function getVariableByCode($code, $withValue = false, $storeId = 0) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where($this->getMainTable() . '.code = ?', $code); + if ($withValue) { + $this->_addValueToSelect($select, $storeId); + } + return $this->_getReadAdapter()->fetchRow($select); + } + + /** + * Perform actions after object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Variable + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + parent::_afterSave($object); + if ($object->getUseDefaultValue()) { + /* + * remove store value + */ + $this->_getWriteAdapter()->delete( + $this->getTable('core/variable_value'), array( + 'variable_id = ?' => $object->getId(), + 'store_id = ?' => $object->getStoreId() + )); + } else { + $data = array( + 'variable_id' => $object->getId(), + 'store_id' => $object->getStoreId(), + 'plain_value' => $object->getPlainValue(), + 'html_value' => $object->getHtmlValue() + ); + $data = $this->_prepareDataForTable(new Varien_Object($data), $this->getTable('core/variable_value')); + $this->_getWriteAdapter()->insertOnDuplicate( + $this->getTable('core/variable_value'), + $data, + array('plain_value', 'html_value') + ); + } + return $this; + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Abstract $object + * @return Zend_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $this->_addValueToSelect($select, $object->getStoreId()); + return $select; + } + + /** + * Add variable store and default value to select + * + * @param Zend_Db_Select $select + * @param integer $storeId + * @return Mage_Core_Model_Resource_Variable + */ + protected function _addValueToSelect(Zend_Db_Select $select, $storeId = Mage_Core_Model_App::ADMIN_STORE_ID) + { + $adapter = $this->_getReadAdapter(); + $ifNullPlainValue = $adapter->getCheckSql('store.plain_value IS NULL', 'def.plain_value', 'store.plain_value'); + $ifNullHtmlValue = $adapter->getCheckSql('store.html_value IS NULL', 'def.html_value', 'store.html_value'); + + $select->joinLeft( + array('def' => $this->getTable('core/variable_value')), + 'def.variable_id = '.$this->getMainTable().'.variable_id AND def.store_id = 0', + array()) + ->joinLeft( + array('store' => $this->getTable('core/variable_value')), + 'store.variable_id = def.variable_id AND store.store_id = ' . $adapter->quote($storeId), + array()) + ->columns(array( + 'plain_value' => $ifNullPlainValue, + 'html_value' => $ifNullHtmlValue, + 'store_plain_value' => 'store.plain_value', + 'store_html_value' => 'store.html_value' + )); + + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Variable/Collection.php b/app/code/core/Mage/Core/Model/Resource/Variable/Collection.php new file mode 100644 index 00000000..f355bf36 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Variable/Collection.php @@ -0,0 +1,101 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Custom variabel collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Variable_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Store Id + * + * @var int + */ + protected $_storeId = 0; + + /** + * Define resource model + * + */ + protected function _construct() + { + parent::_construct(); + $this->_init('core/variable'); + } + + /** + * Setter + * + * @param integer $storeId + * @return Mage_Core_Model_Resource_Variable_Collection + */ + public function setStoreId($storeId) + { + $this->_storeId = $storeId; + return $this; + } + + /** + * Getter + * + * @return integer + */ + public function getStoreId() + { + return $this->_storeId; + } + + /** + * Add store values to result + * + * @return Mage_Core_Model_Resource_Variable_Collection + */ + public function addValuesToResult() + { + $this->getSelect() + ->join( + array('value_table' => $this->getTable('core/variable_value')), + 'value_table.variable_id = main_table.variable_id', + array('value_table.value')); + $this->addFieldToFilter('value_table.store_id', array('eq' => $this->getStoreId())); + return $this; + } + + /** + * Retrieve option array + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('code', 'name'); + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Website.php b/app/code/core/Mage/Core/Model/Resource/Website.php new file mode 100644 index 00000000..f1e11100 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Website.php @@ -0,0 +1,136 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Core Website Resource Model + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Website extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('core/website', 'website_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Core_Model_Resource_Website + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array(array( + 'field' => 'code', + 'title' => Mage::helper('core')->__('Website with the same code') + )); + return $this; + } + + /** + * Validate website code before object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Website + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!preg_match('/^[a-z]+[a-z0-9_]*$/', $object->getCode())) { + Mage::throwException(Mage::helper('core')->__('Website code may only contain letters (a-z), numbers (0-9) or underscore(_), the first character must be a letter')); + } + + return parent::_beforeSave($object); + } + + /** + * Perform actions after object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Website + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->getIsDefault()) { + $this->_getWriteAdapter()->update($this->getMainTable(), array('is_default' => 0)); + $where = array('website_id = ?' => $object->getId()); + $this->_getWriteAdapter()->update($this->getMainTable(), array('is_default' => 1), $where); + } + return parent::_afterSave($object); + } + + /** + * Remove core configuration data after delete website + * + * @param Mage_Core_Model_Abstract $model + * @return Mage_Core_Model_Resource_Website + */ + protected function _afterDelete(Mage_Core_Model_Abstract $model) + { + $where = array( + 'scope = ?' => 'websites', + 'scope_id = ?' => $model->getWebsiteId() + ); + + $this->_getWriteAdapter()->delete($this->getTable('core/config_data'), $where); + + return $this; + + } + + /** + * Retrieve default stores select object + * Select fields website_id, store_id + * + * @param boolean $withDefault include/exclude default admin website + * @return Varien_Db_Select + */ + public function getDefaultStoresSelect($withDefault = false) + { + $ifNull = $this->_getReadAdapter() + ->getCheckSql('store_group_table.default_store_id IS NULL', '0', 'store_group_table.default_store_id'); + $select = $this->_getReadAdapter()->select() + ->from( + array('website_table' => $this->getTable('core/website')), + array('website_id')) + ->joinLeft( + array('store_group_table' => $this->getTable('core/store_group')), + 'website_table.website_id=store_group_table.website_id' + . ' AND website_table.default_group_id = store_group_table.group_id', + array('store_id' => $ifNull) + ); + if (!$withDefault) { + $select->where('website_table.website_id <> ?', 0); + } + return $select; + } +} diff --git a/app/code/core/Mage/Core/Model/Resource/Website/Collection.php b/app/code/core/Mage/Core/Model/Resource/Website/Collection.php new file mode 100644 index 00000000..d1484e49 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Resource/Website/Collection.php @@ -0,0 +1,187 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Websites collection + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Resource_Website_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * @deprecated since 1.5.0.0 + */ + protected $_loadDefault = false; + + /** + * Map field to alias + * + * @var array + */ + protected $_map = array('fields' => array('website_id' => 'main_table.website_id')); + + /** + * Define resource model + * + */ + protected function _construct() + { + $this->setFlag('load_default_website', false); + $this->_init('core/website'); + } + + /** + * Set flag for load default (admin) website + * + * @param boolean $loadDefault + * @return Mage_Core_Model_Resource_Website_Collection + */ + public function setLoadDefault($loadDefault) + { + $this->setFlag('load_default_website', (bool)$loadDefault); + return $this; + } + + /** + * Is load default (admin) website + * + * @return boolean + */ + public function getLoadDefault() + { + return $this->getFlag('load_default_website'); + } + + /** + * Convert items array to array for select options + * + * @return Array + */ + public function toOptionArray() + { + return $this->_toOptionArray('website_id', 'name'); + } + + /** + * Convert items array to hash for select options + * + * @return Array + */ + public function toOptionHash() + { + return $this->_toOptionHash('website_id', 'name'); + } + + + /** + * Add website filter to collection + * + * @param int $ids|array + * @return Mage_Core_Model_Resource_Website_Collection + */ + public function addIdFilter($ids) + { + if (is_array($ids)) { + if (empty($ids)) { + $this->addFieldToFilter('website_id', null); + } else { + $this->addFieldToFilter('website_id', array('in' => $ids)); + } + } else { + $this->addFieldToFilter('website_id', $ids); + } + return $this; + } + + /** + * Load collection data + * + * @param boolean $printQuery + * @param boolean $logQuery + * @return Mage_Core_Model_Resource_Website_Collection + */ + public function load($printQuery = false, $logQuery = false) + { + if (!$this->getLoadDefault()) { + $this->getSelect()->where('main_table.website_id > ?', 0); + } + $this->unshiftOrder('main_table.name', Varien_Db_Select::SQL_ASC) // website name SECOND + ->unshiftOrder('main_table.sort_order', Varien_Db_Select::SQL_ASC); // website sort order FIRST + + return parent::load($printQuery, $logQuery); + + } + + /** + * Join group and store info from appropriate tables. + * Defines new _idFiledName as 'website_group_store' bc for + * one website can be more then one row in collection. + * Sets extra combined ordering by group's name, defined + * sort ordering and store's name. + * + * @return Mage_Core_Model_Resource_Website_Collection + */ + public function joinGroupAndStore() + { + if (!$this->getFlag('groups_and_stores_joined')) { + $this->_idFieldName = 'website_group_store'; + $this->getSelect()->joinLeft( + array('group_table' => $this->getTable('core/store_group')), + 'main_table.website_id = group_table.website_id', + array('group_id' => 'group_id', 'group_title' => 'name') + )->joinLeft( + array('store_table' => $this->getTable('core/store')), + 'group_table.group_id = store_table.group_id', + array('store_id' => 'store_id', 'store_title' => 'name') + ); + $this->addOrder('group_table.name', Varien_Db_Select::SQL_ASC) // store name + ->addOrder('CASE WHEN store_table.store_id = 0 THEN 0 ELSE 1 END', Varien_Db_Select::SQL_ASC) // view is admin + ->addOrder('store_table.sort_order', Varien_Db_Select::SQL_ASC) // view sort order + ->addOrder('store_table.name', Varien_Db_Select::SQL_ASC) // view name + ; + $this->setFlag('groups_and_stores_joined', true); + } + return $this; + } + + /** + * Adding filter by group id or array of ids but only if + * tables with appropriate information were joined before. + * + * @param int|array $groupIds + * @return Mage_Core_Model_Resource_Website_Collection + */ + public function addFilterByGroupIds($groupIds) + { + if ($this->getFlag('groups_and_stores_joined')) { + $this->addFieldToFilter('group_table.group_id', $groupIds); + } + return $this; + } +} diff --git a/app/code/core/Mage/Core/Model/Session.php b/app/code/core/Mage/Core/Model/Session.php index 3f708da3..5abbc92b 100644 --- a/app/code/core/Mage/Core/Model/Session.php +++ b/app/code/core/Mage/Core/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,6 +30,9 @@ * * @todo extend from Mage_Core_Model_Session_Abstract * + * @method null|bool getCookieShouldBeReceived() + * @method Mage_Core_Model_Session setCookieShouldBeReceived(bool $flag) + * @method Mage_Core_Model_Session unsCookieShouldBeReceived() */ class Mage_Core_Model_Session extends Mage_Core_Model_Session_Abstract { diff --git a/app/code/core/Mage/Core/Model/Session/Abstract.php b/app/code/core/Mage/Core/Model/Session/Abstract.php index b15dd199..4210ea0a 100644 --- a/app/code/core/Mage/Core/Model/Session/Abstract.php +++ b/app/code/core/Mage/Core/Model/Session/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -213,13 +213,14 @@ public function getMessages($clear=false) if ($clear) { $messages = clone $this->getData('messages'); $this->getData('messages')->clear(); + Mage::dispatchEvent('core_session_abstract_clear_messages'); return $messages; } return $this->getData('messages'); } /** - * Not Mage exeption handling + * Not Mage exception handling * * @param Exception $exception * @param string $alternativeText @@ -248,6 +249,7 @@ public function addException(Exception $exception, $alternativeText) public function addMessage(Mage_Core_Model_Message_Abstract $message) { $this->getMessages()->add($message); + Mage::dispatchEvent('core_session_abstract_add_message'); return $this; } @@ -276,7 +278,7 @@ public function addWarning($message) } /** - * Adding new nitice message + * Adding new notice message * * @param string $message * @return Mage_Core_Model_Session_Abstract @@ -315,6 +317,56 @@ public function addMessages($messages) return $this; } + /** + * Adds messages array to message collection, but doesn't add duplicates to it + * + * @param array|string|Mage_Core_Model_Message_Abstract $messages + * @return Mage_Core_Model_Session_Abstract + */ + public function addUniqueMessages($messages) + { + if (!is_array($messages)) { + $messages = array($messages); + } + if (!$messages) { + return $this; + } + + $messagesAlready = array(); + $items = $this->getMessages()->getItems(); + foreach ($items as $item) { + if ($item instanceof Mage_Core_Model_Message_Abstract) { + $text = $item->getText(); + } else if (is_string($item)) { + $text = $item; + } else { + continue; // Some unknown object, do not put it in already existing messages + } + $messagesAlready[$text] = true; + } + + foreach ($messages as $message) { + if ($message instanceof Mage_Core_Model_Message_Abstract) { + $text = $message->getText(); + } else if (is_string($message)) { + $text = $message; + } else { + $text = null; // Some unknown object, add it anyway + } + + // Check for duplication + if ($text !== null) { + if (isset($messagesAlready[$text])) { + continue; + } + $messagesAlready[$text] = true; + } + $this->addMessage($message); + } + + return $this; + } + /** * Specify session identifier * @@ -325,14 +377,8 @@ public function setSessionId($id=null) { if (is_null($id) && $this->useSid()) { $_queryParam = $this->getSessionIdQueryParam(); - if (isset($_GET[$_queryParam])) { + if (isset($_GET[$_queryParam]) && Mage::getSingleton('core/url')->isOwnOriginUrl()) { $id = $_GET[$_queryParam]; - /** - * No reason use crypt key for session - */ -// if ($tryId = Mage::helper('core')->decrypt($_GET[self::SESSION_ID_QUERY_PARAM])) { -// $id = $tryId; -// } } } @@ -341,20 +387,14 @@ public function setSessionId($id=null) } /** - * Get ecrypted session identifuer - * No reason use crypt key for session id encryption - * we can use session identifier as is + * Get encrypted session identifier. + * No reason use crypt key for session id encryption, we can use session identifier as is. * * @return string */ public function getEncryptedSessionId() { if (!self::$_encryptedSessionId) { -// $helper = Mage::helper('core'); -// if (!$helper) { -// return $this; -// } -// self::$_encryptedSessionId = $helper->encrypt($this->getSessionId()); self::$_encryptedSessionId = $this->getSessionId(); } return self::$_encryptedSessionId; @@ -392,7 +432,7 @@ public function getSkipSessionIdFlag() } /** - * If the host was switched but session cookie won't recognize it - add session id to query + * If session cookie is not applicable due to host or path mismatch - add session id to query * * @param string $urlHost can be host or url * @return string {session_id_key}={session_id_encrypted} @@ -403,7 +443,8 @@ public function getSessionIdForHost($urlHost) return ''; } - if (!$httpHost = Mage::app()->getFrontController()->getRequest()->getHttpHost()) { + $httpHost = Mage::app()->getFrontController()->getRequest()->getHttpHost(); + if (!$httpHost) { return ''; } @@ -411,23 +452,22 @@ public function getSessionIdForHost($urlHost) if (!empty($urlHostArr[2])) { $urlHost = $urlHostArr[2]; } + $urlPath = empty($urlHostArr[3]) ? '' : $urlHostArr[3]; if (!isset(self::$_urlHostCache[$urlHost])) { $urlHostArr = explode(':', $urlHost); $urlHost = $urlHostArr[0]; - - if ($httpHost !== $urlHost && !$this->isValidForHost($urlHost)) { - $sessionId = $this->getEncryptedSessionId(); - } else { - $sessionId = ''; - } + $sessionId = $httpHost !== $urlHost && !$this->isValidForHost($urlHost) + ? $this->getEncryptedSessionId() : ''; self::$_urlHostCache[$urlHost] = $sessionId; } - return self::$_urlHostCache[$urlHost]; + + return Mage::app()->getStore()->isAdmin() || $this->isValidForPath($urlPath) ? self::$_urlHostCache[$urlHost] + : $this->getEncryptedSessionId(); } /** - * Check is valid session for hostname + * Check if session is valid for given hostname * * @param string $host * @return bool @@ -436,7 +476,25 @@ public function isValidForHost($host) { $hostArr = explode(':', $host); $hosts = $this->getSessionHosts(); - return (!empty($hosts[$hostArr[0]])); + return !empty($hosts[$hostArr[0]]); + } + + /** + * Check if session is valid for given path + * + * @param string $path + * @return bool + */ + public function isValidForPath($path) + { + $cookiePath = trim($this->getCookiePath(), '/') . '/'; + if ($cookiePath == '/') { + return true; + } + + $urlPath = trim($path, '/') . '/'; + + return strpos($urlPath, $cookiePath) === 0; } /** @@ -487,7 +545,7 @@ public function getSessionSaveMethod() } /** - * Get sesssion save path + * Get session save path * * @return string */ @@ -498,4 +556,28 @@ public function getSessionSavePath() } return parent::getSessionSavePath(); } + + /** + * Renew session id and update session cookie + * + * @return Mage_Core_Model_Session_Abstract + */ + public function renewSession() + { + $this->getCookie()->delete($this->getSessionName()); + $this->regenerateSessionId(); + + $sessionHosts = $this->getSessionHosts(); + $currentCookieDomain = $this->getCookie()->getDomain(); + if (is_array($sessionHosts)) { + foreach (array_keys($sessionHosts) as $host) { + // Delete cookies with the same name for parent domains + if (strpos($currentCookieDomain, $host) > 0) { + $this->getCookie()->delete($this->getSessionName(), null, $host); + } + } + } + + return $this; + } } diff --git a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php index 5deff72e..74166ea9 100644 --- a/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php +++ b/app/code/core/Mage/Core/Model/Session/Abstract/Varien.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,7 +34,7 @@ class Mage_Core_Model_Session_Abstract_Varien extends Varien_Object const VALIDATOR_REMOTE_ADDR_KEY = 'remote_addr'; /** - * Conigure and start session + * Configure and start session * * @param string $sessionName * @return Mage_Core_Model_Session_Abstract_Varien @@ -56,19 +56,27 @@ public function start($sessionName=null) ini_set('session.save_handler', 'memcache'); session_save_path($this->getSessionSavePath()); break; + case 'memcached': + ini_set('session.save_handler', 'memcached'); + session_save_path($this->getSessionSavePath()); + break; case 'eaccelerator': ini_set('session.save_handler', 'eaccelerator'); break; default: - session_module_name('files'); - if (is_writable(Mage::getBaseDir('session'))) { + session_module_name($this->getSessionSaveMethod()); + if (is_writable($this->getSessionSavePath())) { session_save_path($this->getSessionSavePath()); } break; } $cookie = $this->getCookie(); if (Mage::app()->getStore()->isAdmin()) { + $sessionMaxLifetime = Mage_Core_Model_Resource_Session::SEESION_MAX_COOKIE_LIFETIME; $adminSessionLifetime = (int)Mage::getStoreConfig('admin/security/session_cookie_lifetime'); + if ($adminSessionLifetime > $sessionMaxLifetime) { + $adminSessionLifetime = $sessionMaxLifetime; + } if ($adminSessionLifetime > 60) { $cookie->setLifetime($adminSessionLifetime); } @@ -113,10 +121,13 @@ public function start($sessionName=null) } session_start(); + /** - * Renew cookie expiration time - */ - $cookie->renew(session_name()); + * Renew cookie expiration time if session id did not change + */ + if ($cookie->get(session_name()) == $this->getSessionId()) { + $cookie->renew(session_name()); + } Varien_Profiler::stop(__METHOD__.'/start'); return $this; @@ -352,18 +363,30 @@ protected function _validate() $sessionData = $this->_data[self::VALIDATOR_KEY]; $validatorData = $this->getValidatorData(); - if ($this->useValidateRemoteAddr() && $sessionData[self::VALIDATOR_REMOTE_ADDR_KEY] != $validatorData[self::VALIDATOR_REMOTE_ADDR_KEY]) { + if ($this->useValidateRemoteAddr() + && $sessionData[self::VALIDATOR_REMOTE_ADDR_KEY] != $validatorData[self::VALIDATOR_REMOTE_ADDR_KEY]) { return false; } - if ($this->useValidateHttpVia() && $sessionData[self::VALIDATOR_HTTP_VIA_KEY] != $validatorData[self::VALIDATOR_HTTP_VIA_KEY]) { + if ($this->useValidateHttpVia() + && $sessionData[self::VALIDATOR_HTTP_VIA_KEY] != $validatorData[self::VALIDATOR_HTTP_VIA_KEY]) { return false; } - if ($this->useValidateHttpXForwardedFor() && $sessionData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY] != $validatorData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY]) { + + $sessionValidateHttpXForwardedForKey = $sessionData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY]; + $validatorValidateHttpXForwardedForKey = $validatorData[self::VALIDATOR_HTTP_X_FORVARDED_FOR_KEY]; + if ($this->useValidateHttpXForwardedFor() + && $sessionValidateHttpXForwardedForKey != $validatorValidateHttpXForwardedForKey ) { return false; } if ($this->useValidateHttpUserAgent() && $sessionData[self::VALIDATOR_HTTP_USER_AGENT_KEY] != $validatorData[self::VALIDATOR_HTTP_USER_AGENT_KEY] - && !in_array($validatorData[self::VALIDATOR_HTTP_USER_AGENT_KEY], $this->getValidateHttpUserAgentSkip())) { + ) { + $userAgentValidated = $this->getValidateHttpUserAgentSkip(); + foreach ($userAgentValidated as $agent) { + if (preg_match('/' . $agent . '/iu', $validatorData[self::VALIDATOR_HTTP_USER_AGENT_KEY])) { + return true; + } + } return false; } @@ -402,4 +425,15 @@ public function getValidatorData() return $parts; } + + /** + * Regenerate session Id + * + * @return Mage_Core_Model_Session_Abstract_Varien + */ + public function regenerateSessionId() + { + session_regenerate_id(true); + return $this; + } } diff --git a/app/code/core/Mage/Core/Model/Session/Abstract/Zend.php b/app/code/core/Mage/Core/Model/Session/Abstract/Zend.php index 31e25ddd..0eacf9ab 100644 --- a/app/code/core/Mage/Core/Model/Session/Abstract/Zend.php +++ b/app/code/core/Mage/Core/Model/Session/Abstract/Zend.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -51,6 +51,7 @@ public function start() $options = array( 'save_path'=>Mage::getBaseDir('session'), 'use_only_cookies'=>'off', + 'throw_startup_exceptions' => E_ALL ^ E_NOTICE, ); if ($this->getCookieDomain()) { $options['cookie_domain'] = $this->getCookieDomain(); @@ -161,4 +162,15 @@ public function setSessionId($id=null) } return $this; } + + /** + * Regenerate session Id + * + * @return Mage_Core_Model_Session_Abstract_Zend + */ + public function regenerateSessionId() + { + Zend_Session::regenerateId(); + return $this; + } } diff --git a/app/code/core/Mage/Core/Model/Session/Exception.php b/app/code/core/Mage/Core/Model/Session/Exception.php index be6e52ff..07bfa653 100644 --- a/app/code/core/Mage/Core/Model/Session/Exception.php +++ b/app/code/core/Mage/Core/Model/Session/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Source/Email/Variables.php b/app/code/core/Mage/Core/Model/Source/Email/Variables.php index f4bbf783..31c8f359 100644 --- a/app/code/core/Mage/Core/Model/Source/Email/Variables.php +++ b/app/code/core/Mage/Core/Model/Source/Email/Variables.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,6 +34,11 @@ */ class Mage_Core_Model_Source_Email_Variables { + /** + * Assoc array of configuration variables + * + * @var array + */ protected $_configVariables = array(); /** @@ -43,19 +48,59 @@ class Mage_Core_Model_Source_Email_Variables public function __construct() { $this->_configVariables = array( - array('value' => Mage_Core_Model_Url::XML_PATH_UNSECURE_URL, 'label' => 'Base Unsecure URL'), - array('value' => Mage_Core_Model_Url::XML_PATH_SECURE_URL, 'label' => 'Base Secure URL'), - array('value' => 'trans_email/ident_general/name', 'label' => 'General Contact Name'), - array('value' => 'trans_email/ident_general/email', 'label' => 'General Contact Email'), - array('value' => 'trans_email/ident_sales/name', 'label' => 'Sales Representative Contact Name'), - array('value' => 'trans_email/ident_sales/email', 'label' => 'Sales Representative Contact Email'), - array('value' => 'trans_email/ident_custom1/name', 'label' => 'Custom1 Contact Name'), - array('value' => 'trans_email/ident_custom1/email', 'label' => 'Custom1 Contact Email'), - array('value' => 'trans_email/ident_custom2/name', 'label' => 'Custom2 Contact Name'), - array('value' => 'trans_email/ident_custom2/email', 'label' => 'Custom2 Contact Email'), - array('value' => 'general/store_information/name', 'label' => 'Store Name'), - array('value' => 'general/store_information/phone', 'label' => 'Store Contact Telephone'), - array('value' => 'general/store_information/address', 'label' => 'Store Contact Address')); + array( + 'value' => Mage_Core_Model_Url::XML_PATH_UNSECURE_URL, + 'label' => Mage::helper('core')->__('Base Unsecure URL') + ), + array( + 'value' => Mage_Core_Model_Url::XML_PATH_SECURE_URL, + 'label' => Mage::helper('core')->__('Base Secure URL') + ), + array( + 'value' => 'trans_email/ident_general/name', + 'label' => Mage::helper('core')->__('General Contact Name') + ), + array( + 'value' => 'trans_email/ident_general/email', + 'label' => Mage::helper('core')->__('General Contact Email') + ), + array( + 'value' => 'trans_email/ident_sales/name', + 'label' => Mage::helper('core')->__('Sales Representative Contact Name') + ), + array( + 'value' => 'trans_email/ident_sales/email', + 'label' => Mage::helper('core')->__('Sales Representative Contact Email') + ), + array( + 'value' => 'trans_email/ident_custom1/name', + 'label' => Mage::helper('core')->__('Custom1 Contact Name') + ), + array( + 'value' => 'trans_email/ident_custom1/email', + 'label' => Mage::helper('core')->__('Custom1 Contact Email') + ), + array( + 'value' => 'trans_email/ident_custom2/name', + 'label' => Mage::helper('core')->__('Custom2 Contact Name') + ), + array( + 'value' => 'trans_email/ident_custom2/email', + 'label' => Mage::helper('core')->__('Custom2 Contact Email') + ), + array( + 'value' => 'general/store_information/name', + 'label' => Mage::helper('core')->__('Store Name') + ), + array( + 'value' => 'general/store_information/phone', + 'label' => Mage::helper('core')->__('Store Contact Telephone') + ), + array( + 'value' => 'general/store_information/address', + 'label' => Mage::helper('core')->__('Store Contact Address') + ) + ); } /** diff --git a/app/code/core/Mage/Core/Model/Store.php b/app/code/core/Mage/Core/Model/Store.php index ef0f99ff..18163e84 100644 --- a/app/code/core/Mage/Core/Model/Store.php +++ b/app/code/core/Mage/Core/Model/Store.php @@ -20,57 +20,118 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - /** * Store model * - * @author Magento Core Team <core@magentocommerce.com> - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Store _getResource() + * @method Mage_Core_Model_Resource_Store getResource() + * @method Mage_Core_Model_Store setCode(string $value) + * @method Mage_Core_Model_Store setWebsiteId(int $value) + * @method Mage_Core_Model_Store setGroupId(int $value) + * @method Mage_Core_Model_Store setName(string $value) + * @method int getSortOrder() + * @method Mage_Core_Model_Store setSortOrder(int $value) + * @method Mage_Core_Model_Store setIsActive(int $value) + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> */ class Mage_Core_Model_Store extends Mage_Core_Model_Abstract { + /** + * Entity name + */ const ENTITY = 'core_store'; - const XML_PATH_STORE_IN_URL = 'web/url/use_store'; - const XML_PATH_USE_REWRITES = 'web/seo/use_rewrites'; - const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; - const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; - const XML_PATH_SECURE_IN_FRONTEND = 'web/secure/use_in_frontend'; - const XML_PATH_SECURE_IN_ADMINHTML = 'web/secure/use_in_adminhtml'; + /** + * Configuration pathes + */ + const XML_PATH_STORE_STORE_NAME = 'general/store_information/name'; + const XML_PATH_STORE_STORE_PHONE = 'general/store_information/phone'; + const XML_PATH_STORE_IN_URL = 'web/url/use_store'; + const XML_PATH_USE_REWRITES = 'web/seo/use_rewrites'; + const XML_PATH_UNSECURE_BASE_URL = 'web/unsecure/base_url'; + const XML_PATH_SECURE_BASE_URL = 'web/secure/base_url'; + const XML_PATH_SECURE_IN_FRONTEND = 'web/secure/use_in_frontend'; + const XML_PATH_SECURE_IN_ADMINHTML = 'web/secure/use_in_adminhtml'; + const XML_PATH_SECURE_BASE_LINK_URL = 'web/secure/base_link_url'; + const XML_PATH_UNSECURE_BASE_LINK_URL = 'web/unsecure/base_link_url'; + const XML_PATH_OFFLOADER_HEADER = 'web/secure/offloader_header'; + const XML_PATH_PRICE_SCOPE = 'catalog/price/scope'; - const XML_PATH_PRICE_SCOPE = 'catalog/price/scope'; - const PRICE_SCOPE_GLOBAL = 0; - const PRICE_SCOPE_WEBSITE = 1; + /** + * Price scope constants + */ + const PRICE_SCOPE_GLOBAL = 0; + const PRICE_SCOPE_WEBSITE = 1; + + /** + * Possible URL types + */ + const URL_TYPE_LINK = 'link'; + const URL_TYPE_DIRECT_LINK = 'direct_link'; + const URL_TYPE_WEB = 'web'; + const URL_TYPE_SKIN = 'skin'; + const URL_TYPE_JS = 'js'; + const URL_TYPE_MEDIA = 'media'; + + /** + * Code constants + */ + const DEFAULT_CODE = 'default'; + const ADMIN_CODE = 'admin'; + + /** + * Cache tag + */ + const CACHE_TAG = 'store'; - const URL_TYPE_LINK = 'link'; - const URL_TYPE_DIRECT_LINK = 'direct_link'; - const URL_TYPE_WEB = 'web'; - const URL_TYPE_SKIN = 'skin'; - const URL_TYPE_JS = 'js'; - const URL_TYPE_MEDIA = 'media'; + /** + * Cookie name + */ + const COOKIE_NAME = 'store'; - const DEFAULT_CODE = 'default'; - const ADMIN_CODE = 'admin'; + /** + * Cookie currency key + */ + const COOKIE_CURRENCY = 'currency'; - const CACHE_TAG = 'store'; + /** + * Script name, which returns all the images + */ + const MEDIA_REWRITE_SCRIPT = 'get.php/'; + /** + * Cache flag + * + * @var boolean + */ protected $_cacheTag = true; /** + * Event prefix for model events + * * @var string */ protected $_eventPrefix = 'store'; /** + * Event object name + * * @var string */ protected $_eventObject = 'store'; + /** + * Price filter + * + * @var Mage_Directory_Model_Currency_Filter + */ protected $_priceFilter; /** @@ -87,27 +148,79 @@ class Mage_Core_Model_Store extends Mage_Core_Model_Abstract */ protected $_group; + /** + * Store configuration cache + * + * @var array|null + */ protected $_configCache = null; + + /** + * Base nodes of store configuration cache + * + * @var array + */ protected $_configCacheBaseNodes = array(); + /** + * Directory cache + * + * @var array + */ protected $_dirCache = array(); + /** + * URL cache + * + * @var array + */ protected $_urlCache = array(); + /** + * Base URL cache + * + * @var array + */ protected $_baseUrlCache = array(); + /** + * Session entity + * + * @var Mage_Core_Model_Session_Abstract + */ protected $_session; + /** + * Flag that shows that backend URLs are secure + * + * @var boolean|null + */ protected $_isAdminSecure = null; + + /** + * Flag that shows that frontend URLs are secure + * + * @var boolean|null + */ protected $_isFrontSecure = null; + /** + * Store frontend name + * + * @var string|null + */ protected $_frontendName = null; /** + * Readonly flag + * * @var bool */ private $_isReadOnly = false; + /** + * Initialize object + */ protected function _construct() { $this->_init('core/store'); @@ -119,8 +232,8 @@ protected function _construct() self::XML_PATH_STORE_IN_URL, self::XML_PATH_UNSECURE_BASE_URL, self::XML_PATH_USE_REWRITES, - 'web/unsecure/base_link_url', - 'web/secure/base_link_url', + self::XML_PATH_UNSECURE_BASE_LINK_URL, + self::XML_PATH_SECURE_BASE_LINK_URL, 'general/locale/code' ); } @@ -164,8 +277,8 @@ public function load($id, $field=null) public function loadConfig($code) { if (is_numeric($code)) { - foreach (Mage::getConfig()->getNode()->stores->children() as $storeCode=>$store) { - if ((int)$store->system->store->id==$code) { + foreach (Mage::getConfig()->getNode()->stores->children() as $storeCode => $store) { + if ((int) $store->system->store->id == $code) { $code = $storeCode; break; } @@ -175,9 +288,9 @@ public function loadConfig($code) } if (!empty($store)) { $this->setCode($code); - $id = (int)$store->system->store->id; + $id = (int) $store->system->store->id; $this->setId($id)->setStoreId($id); - $this->setWebsiteId((int)$store->system->website->id); + $this->setWebsiteId((int) $store->system->website->id); } return $this; } @@ -196,7 +309,6 @@ public function getCode() * Retrieve store configuration data * * @param string $path - * @param string $scope * @return string|null */ public function getConfig($path) @@ -207,7 +319,7 @@ public function getConfig($path) $config = Mage::getConfig(); - $fullPath = 'stores/'.$this->getCode().'/'.$path; + $fullPath = 'stores/' . $this->getCode() . '/' . $path; $data = $config->getNode($fullPath); if (!$data && !Mage::isInstalled()) { $data = $config->getNode('default/' . $path); @@ -220,13 +332,13 @@ public function getConfig($path) /** * Initialize base store configuration data + * * Method provide cache configuration data without loading store config XML * * @return Mage_Core_Model_Config */ public function initConfigCache() { -// return $this; /** * Funtionality related with config separation */ @@ -243,11 +355,10 @@ public function initConfigCache() foreach ($this->_configCacheBaseNodes as $node) { $data[$node] = $this->getConfig($node); } - Mage::app()->saveCache( - serialize($data), - $cacheId, - array(self::CACHE_TAG, Mage_Core_Model_Config::CACHE_TAG) - ); + Mage::app()->saveCache(serialize($data), $cacheId, array( + self::CACHE_TAG, + Mage_Core_Model_Config::CACHE_TAG + )); } $this->_configCache = $data; } @@ -258,6 +369,7 @@ public function initConfigCache() /** * Set config value for CURRENT model + * * This value don't save in config * * @param string $path @@ -269,7 +381,7 @@ public function setConfig($path, $value) if (isset($this->_configCache[$path])) { $this->_configCache[$path] = $value; } - $fullPath = 'stores/'.$this->getCode().'/'.$path; + $fullPath = 'stores/' . $this->getCode() . '/' . $path; Mage::getConfig()->setNode($fullPath, $value); return $this; @@ -302,7 +414,7 @@ public function getWebsite() } /** - * Enter description here... + * Process config value * * @param string $fullPath * @param string $path @@ -317,28 +429,28 @@ protected function _processConfigValue($fullPath, $path, $node) if ($node->hasChildren()) { $aValue = array(); - foreach ($node->children() as $k=>$v) { - $aValue[$k] = $this->_processConfigValue($fullPath.'/'.$k, $path.'/'.$k, $v); + foreach ($node->children() as $k => $v) { + $aValue[$k] = $this->_processConfigValue($fullPath . '/' . $k, $path . '/' . $k, $v); } $this->_configCache[$path] = $aValue; return $aValue; } - $sValue = (string)$node; + $sValue = (string) $node; if (!empty($node['backend_model']) && !empty($sValue)) { - $backend = Mage::getModel((string)$node['backend_model']); + $backend = Mage::getModel((string) $node['backend_model']); $backend->setPath($path)->setValue($sValue)->afterLoad(); $sValue = $backend->getValue(); } - if (is_string($sValue) && strpos($sValue, '{{')!==false) { - if (strpos($sValue, '{{unsecure_base_url}}')!==false) { - $unsecureBaseUrl = $this->getConfig('web/unsecure/base_url'); + if (is_string($sValue) && strpos($sValue, '{{') !== false) { + if (strpos($sValue, '{{unsecure_base_url}}') !== false) { + $unsecureBaseUrl = $this->getConfig(self::XML_PATH_UNSECURE_BASE_URL); $sValue = str_replace('{{unsecure_base_url}}', $unsecureBaseUrl, $sValue); - } elseif (strpos($sValue, '{{secure_base_url}}')!==false) { - $secureBaseUrl = $this->getConfig('web/secure/base_url'); + } elseif (strpos($sValue, '{{secure_base_url}}') !== false) { + $secureBaseUrl = $this->getConfig(self::XML_PATH_SECURE_BASE_URL); $sValue = str_replace('{{secure_base_url}}', $secureBaseUrl, $sValue); - } else { + } elseif (strpos($sValue, '{{base_url}}') !== false) { $sValue = Mage::getConfig()->substDistroServerVars($sValue); } } @@ -349,9 +461,9 @@ protected function _processConfigValue($fullPath, $path, $node) } /** - * Enter description here... + * Convert config values for url pathes * - * @todo check and delete this if it is not used anymore + * @deprecated after 1.4.2.0 * @param string $value * @return string */ @@ -361,25 +473,30 @@ public function processSubst($value) return $value; } - if (strpos($value, '{{unsecure_base_url}}')!==false) { - $unsecureBaseUrl = $this->getConfig('web/unsecure/base_url'); + if (strpos($value, '{{unsecure_base_url}}') !== false) { + $unsecureBaseUrl = $this->getConfig(self::XML_PATH_UNSECURE_BASE_URL); $value = str_replace('{{unsecure_base_url}}', $unsecureBaseUrl, $value); - } elseif (strpos($value, '{{secure_base_url}}')!==false) { - $secureBaseUrl = $this->getConfig('web/secure/base_url'); + } elseif (strpos($value, '{{secure_base_url}}') !== false) { + $secureBaseUrl = $this->getConfig(self::XML_PATH_SECURE_BASE_URL); $value = str_replace('{{secure_base_url}}', $secureBaseUrl, $value); - } elseif (strpos($value, '{{')!==false) { + } elseif (strpos($value, '{{') !== false && strpos($value, '{{base_url}}') === false) { $value = Mage::getConfig()->substDistroServerVars($value); } return $value; } + /** + * Retrieve default base path + * + * @return string + */ public function getDefaultBasePath() { if (!isset($_SERVER['SCRIPT_NAME'])) { return '/'; } - return rtrim(Mage::app()->getRequest()->getBasePath().'/').'/'; + return rtrim(Mage::app()->getRequest()->getBasePath() . '/') . '/'; } /** @@ -391,7 +508,7 @@ public function getDefaultBasePath() */ public function getUrl($route = '', $params = array()) { - /* @var $url Mage_Core_Model_Url */ + /** @var $url Mage_Core_Model_Url */ $url = Mage::getModel('core/url') ->setStore($this); if (Mage::app()->getStore()->getId() != $this->getId()) { @@ -401,41 +518,56 @@ public function getUrl($route = '', $params = array()) return $url->getUrl($route, $params); } - public function getBaseUrl($type=self::URL_TYPE_LINK, $secure=null) + /** + * Retrieve base URL + * + * @param string $type + * @param boolean|null $secure + * @return string + */ + public function getBaseUrl($type = self::URL_TYPE_LINK, $secure = null) { - $cacheKey = $type.'/'.(is_null($secure) ? 'null' : ($secure ? 'true' : 'false')); + $cacheKey = $type . '/' . (is_null($secure) ? 'null' : ($secure ? 'true' : 'false')); if (!isset($this->_baseUrlCache[$cacheKey])) { switch ($type) { case self::URL_TYPE_WEB: $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure; - $url = $this->getConfig('web/'.($secure ? 'secure' : 'unsecure').'/base_url'); + $url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_url'); break; case self::URL_TYPE_LINK: - $secure = (bool)$secure; - $url = $this->getConfig('web/'.($secure ? 'secure' : 'unsecure').'/base_link_url'); + $secure = (bool) $secure; + $url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_link_url'); $url = $this->_updatePathUseRewrites($url); $url = $this->_updatePathUseStoreView($url); break; case self::URL_TYPE_DIRECT_LINK: - $secure = (bool)$secure; - $url = $this->getConfig('web/'.($secure ? 'secure' : 'unsecure').'/base_link_url'); + $secure = (bool) $secure; + $url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_link_url'); $url = $this->_updatePathUseRewrites($url); break; case self::URL_TYPE_SKIN: - case self::URL_TYPE_MEDIA: case self::URL_TYPE_JS: - $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool)$secure; - $url = $this->getConfig('web/'.($secure ? 'secure' : 'unsecure').'/base_'.$type.'_url'); + $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool) $secure; + $url = $this->getConfig('web/' . ($secure ? 'secure' : 'unsecure') . '/base_' . $type . '_url'); + break; + + case self::URL_TYPE_MEDIA: + $url = $this->_updateMediaPathUseRewrites($secure); break; default: throw Mage::exception('Mage_Core', Mage::helper('core')->__('Invalid base url type')); } - $this->_baseUrlCache[$cacheKey] = rtrim($url, '/').'/'; + if (false !== strpos($url, '{{base_url}}')) { + $baseUrl = Mage::getConfig()->substDistroServerVars('{{base_url}}'); + $url = str_replace('{{base_url}}', $baseUrl, $url); + } + + $this->_baseUrlCache[$cacheKey] = rtrim($url, '/') . '/'; } return $this->_baseUrlCache[$cacheKey]; @@ -451,8 +583,48 @@ protected function _updatePathUseRewrites($url) { if ($this->isAdmin() || !$this->getConfig(self::XML_PATH_USE_REWRITES) - || !Mage::isInstalled()) { - $url .= basename($_SERVER['SCRIPT_FILENAME']).'/'; + || !Mage::isInstalled() + ) { + if ($this->_isCustomEntryPoint()) { + $indexFileName = 'index.php'; + } else { + $indexFileName = basename($_SERVER['SCRIPT_FILENAME']); + } + $url .= $indexFileName . '/'; + } + return $url; + } + + /** + * Check if used entry point is custom + * + * @return bool + */ + protected function _isCustomEntryPoint() + { + return (bool)Mage::registry('custom_entry_point'); + } + + /** + * Retrieve URL for media catalog + * + * If we use Database file storage and server doesn't support rewrites (.htaccess in media folder) + * we have to put name of fetching media script exactly into URL + * + * @param null|boolean $secure + * @param string $type + * @return string + */ + protected function _updateMediaPathUseRewrites($secure = null, $type = self::URL_TYPE_MEDIA) + { + $secure = is_null($secure) ? $this->isCurrentlySecure() : (bool) $secure; + $secureStringFlag = $secure ? 'secure' : 'unsecure'; + $url = $this->getConfig('web/' . $secureStringFlag . '/base_' . $type . '_url'); + if (!$this->getConfig(self::XML_PATH_USE_REWRITES) + && Mage::helper('core/file_storage_database')->checkDbUsage() + ) { + $urlStart = $this->getConfig('web/' . $secureStringFlag . '/base_url'); + $url = str_replace($urlStart, $urlStart . self::MEDIA_REWRITE_SCRIPT, $url); } return $url; } @@ -465,12 +637,22 @@ protected function _updatePathUseRewrites($url) */ protected function _updatePathUseStoreView($url) { - if (Mage::isInstalled() && $this->getConfig(self::XML_PATH_STORE_IN_URL)) { - $url .= $this->getCode().'/'; + if ($this->getStoreInUrl()) { + $url .= $this->getCode() . '/'; } return $url; } + /** + * Returns whether url forming scheme prepends url path with store view code + * + * @return bool + */ + public function getStoreInUrl() + { + return Mage::isInstalled() && $this->getConfig(self::XML_PATH_STORE_IN_URL); + } + /** * Get store identifier * @@ -492,43 +674,60 @@ public function isAdmin() } + /** + * Check if backend URLs should be secure + * + * @return boolean + */ public function isAdminUrlSecure() { if ($this->_isAdminSecure === null) { - $this->_isAdminSecure = Mage::getStoreConfigFlag( - Mage_Core_Model_Url::XML_PATH_SECURE_IN_ADMIN, - $this->getId() - ); + $this->_isAdminSecure = (boolean) (int) (string) Mage::getConfig() + ->getNode(Mage_Core_Model_Url::XML_PATH_SECURE_IN_ADMIN); } return $this->_isAdminSecure; } + /** + * Check if frontend URLs should be secure + * + * @return boolean + */ public function isFrontUrlSecure() { if ($this->_isFrontSecure === null) { - $this->_isFrontSecure = Mage::getStoreConfigFlag( - Mage_Core_Model_Url::XML_PATH_SECURE_IN_FRONT, - $this->getId() - ); + $this->_isFrontSecure = Mage::getStoreConfigFlag(Mage_Core_Model_Url::XML_PATH_SECURE_IN_FRONT, + $this->getId()); } return $this->_isFrontSecure; } + /** + * Check if request was secure + * + * @return boolean + */ public function isCurrentlySecure() { - if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') { + $standardRule = !empty($_SERVER['HTTPS']) && ('off' != $_SERVER['HTTPS']); + $offloaderHeader = trim((string) Mage::getConfig()->getNode(self::XML_PATH_OFFLOADER_HEADER, 'default')); + + if ((!empty($offloaderHeader) && !empty($_SERVER[$offloaderHeader])) || $standardRule) { return true; } if (Mage::isInstalled()) { - $secureBaseUrl = Mage::getStoreConfig('web/secure/base_route_url'); + $secureBaseUrl = Mage::getStoreConfig(Mage_Core_Model_Url::XML_PATH_SECURE_URL); + if (!$secureBaseUrl) { return false; } + $uri = Zend_Uri::factory($secureBaseUrl); - $isSecure = ($uri->getScheme() == 'https' ) + $port = $uri->getPort(); + $isSecure = ($uri->getScheme() == 'https') && isset($_SERVER['SERVER_PORT']) - && ($uri->getPort() == $_SERVER['SERVER_PORT']); + && ($port == $_SERVER['SERVER_PORT']); return $isSecure; } else { $isSecure = isset($_SERVER['SERVER_PORT']) && (443 == $_SERVER['SERVER_PORT']); @@ -547,7 +746,8 @@ public function isCurrentlySecure() */ public function getBaseCurrencyCode() { - if ($this->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE) == Mage_Core_Model_Store::PRICE_SCOPE_GLOBAL) { + $configValue = $this->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE); + if ($configValue == Mage_Core_Model_Store::PRICE_SCOPE_GLOBAL) { return Mage::app()->getBaseCurrencyCode(); } else { return $this->getConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE); @@ -607,9 +807,9 @@ public function setCurrentCurrencyCode($code) if (in_array($code, $this->getAvailableCurrencyCodes())) { $this->_getSession()->setCurrencyCode($code); if ($code == $this->getDefaultCurrency()) { - Mage::app()->getCookie()->delete('currency', $code); + Mage::app()->getCookie()->delete(self::COOKIE_CURRENCY, $code); } else { - Mage::app()->getCookie()->set('currency', $code); + Mage::app()->getCookie()->set(self::COOKIE_CURRENCY, $code); } } return $this; @@ -701,6 +901,11 @@ public function getCurrentCurrency() return $currency; } + /** + * Retrieve current currency rate + * + * @return float + */ public function getCurrentCurrencyRate() { return $this->getBaseCurrency()->getRate($this->getCurrentCurrency()); @@ -714,14 +919,13 @@ public function getCurrentCurrencyRate() * @param boolean $includeContainer Enclose into <span class="price"><span> * @return double */ - public function convertPrice($price, $format=false, $includeContainer = true) + public function convertPrice($price, $format = false, $includeContainer = true) { if ($this->getCurrentCurrency() && $this->getBaseCurrency()) { $value = $this->getBaseCurrency()->convert($price, $this->getCurrentCurrency()); } else { $value = $price; } - $value = $this->roundPrice($value); if ($this->getCurrentCurrency() && $format) { $value = $this->formatPrice($value, $includeContainer); @@ -778,7 +982,7 @@ public function getPriceFilter() } /** - * Enter description here... + * Retrieve root category identifier * * @return int */ @@ -816,21 +1020,41 @@ public function getGroup() return $this->_group; } + /** + * Retrieve website identifier + * + * @return string|int|null + */ public function getWebsiteId() { return $this->_getData('website_id'); } + /** + * Retrieve group identifier + * + * @return string|int|null + */ public function getGroupId() { return $this->_getData('group_id'); } + /** + * Retrieve default group identifier + * + * @return string|int|null + */ public function getDefaultGroupId() { return $this->_getData('default_group_id'); } + /** + * Check if store can be deleted + * + * @return boolean + */ public function isCanDelete() { if (!$this->getId()) { @@ -848,40 +1072,59 @@ public function isCanDelete() */ public function getCurrentUrl($fromStore = true) { - $query = Mage::getSingleton('core/url')->escape(ltrim(Mage::app()->getRequest()->getRequestString(), '/')); - - if (Mage::app()->getStore()->isCurrentlySecure()) { - $parsedUrl = parse_url($this->getUrl('', array('_secure' => true))); - } else { - $parsedUrl = parse_url($this->getUrl('')); + $sidQueryParam = $this->_getSession()->getSessionIdQueryParam(); + $requestString = Mage::getSingleton('core/url')->escape( + ltrim(Mage::app()->getRequest()->getRequestString(), '/')); + + $storeUrl = Mage::app()->getStore()->isCurrentlySecure() + ? $this->getUrl('', array('_secure' => true)) + : $this->getUrl(''); + $storeParsedUrl = parse_url($storeUrl); + + $storeParsedQuery = array(); + if (isset($storeParsedUrl['query'])) { + parse_str($storeParsedUrl['query'], $storeParsedQuery); } - $parsedQuery = array(); - if (isset($parsedUrl['query'])) { - parse_str($parsedUrl['query'], $parsedQuery); + + $currQuery = Mage::app()->getRequest()->getQuery(); + if (isset($currQuery[$sidQueryParam]) && !empty($currQuery[$sidQueryParam]) + && $this->_getSession()->getSessionIdForHost($storeUrl) != $currQuery[$sidQueryParam] + ) { + unset($currQuery[$sidQueryParam]); } - foreach (Mage::app()->getRequest()->getQuery() as $k => $v) { - $parsedQuery[$k] = $v; + foreach ($currQuery as $k => $v) { + $storeParsedQuery[$k] = $v; } if (!Mage::getStoreConfigFlag(Mage_Core_Model_Store::XML_PATH_STORE_IN_URL, $this->getCode())) { - $parsedQuery['___store'] = $this->getCode(); + $storeParsedQuery['___store'] = $this->getCode(); } if ($fromStore !== false) { - $parsedQuery['___from_store'] = $fromStore === true ? Mage::app()->getStore()->getCode() : $fromStore; + $storeParsedQuery['___from_store'] = $fromStore === true ? Mage::app()->getStore()->getCode() : $fromStore; } - return $parsedUrl['scheme'] . '://' . $parsedUrl['host'] - . (isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '') - . $parsedUrl['path'] . $query - . ($parsedQuery ? '?'.http_build_query($parsedQuery, '', '&') : ''); + return $storeParsedUrl['scheme'] . '://' . $storeParsedUrl['host'] + . (isset($storeParsedUrl['port']) ? ':' . $storeParsedUrl['port'] : '') + . $storeParsedUrl['path'] . $requestString + . ($storeParsedQuery ? '?'.http_build_query($storeParsedQuery, '', '&') : ''); } + /** + * Check if store is active + * + * @return boolean|null + */ public function getIsActive() { return $this->_getData('is_active'); } + /** + * Retrieve store name + * + * @return string|null + */ public function getName() { return $this->_getData('name'); @@ -889,6 +1132,7 @@ public function getName() /** * Protect delete from non admin area + * * Register indexing event before delete store * * @return Mage_Core_Model_Store @@ -896,9 +1140,7 @@ public function getName() protected function _beforeDelete() { $this->_protectFromNonAdmin(); - Mage::getSingleton('index/indexer')->logEvent( - $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE - ); + Mage::getSingleton('index/indexer')->logEvent($this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE); return parent::_beforeDelete(); } @@ -922,9 +1164,8 @@ protected function _afterDelete() protected function _afterDeleteCommit() { parent::_afterDeleteCommit(); - Mage::getSingleton('index/indexer')->indexEvents( - self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE - ); + Mage::getSingleton('index/indexer')->indexEvents(self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE); + return $this; } /** @@ -952,7 +1193,7 @@ public function resetConfig() public function isReadOnly($value = null) { if (null !== $value) { - $this->_isReadOnly = (bool)$value; + $this->_isReadOnly = (bool) $value; } return $this->_isReadOnly; } @@ -965,7 +1206,7 @@ public function isReadOnly($value = null) public function getFrontendName() { if (is_null($this->_frontendName)) { - $storeGroupName = (string)Mage::getStoreConfig('general/store_information/name', $this); + $storeGroupName = (string) Mage::getStoreConfig('general/store_information/name', $this); $this->_frontendName = (!empty($storeGroupName)) ? $storeGroupName : $this->getGroup()->getName(); } return $this->_frontendName; diff --git a/app/code/core/Mage/Core/Model/Store/Api.php b/app/code/core/Mage/Core/Model/Store/Api.php new file mode 100644 index 00000000..9420ee59 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Store/Api.php @@ -0,0 +1,96 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Store API + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ + +class Mage_Core_Model_Store_Api extends Mage_Api_Model_Resource_Abstract +{ + /** + * Retrieve stores list + * + * @return array + */ + public function items() + { + // Retrieve stores + $stores = Mage::app()->getStores(); + + // Make result array + $result = array(); + foreach ($stores as $store) { + $result[] = array( + 'store_id' => $store->getId(), + 'code' => $store->getCode(), + 'website_id' => $store->getWebsiteId(), + 'group_id' => $store->getGroupId(), + 'name' => $store->getName(), + 'sort_order' => $store->getSortOrder(), + 'is_active' => $store->getIsActive() + ); + } + + return $result; + } + + /** + * Retrieve store data + * + * @param string|int $storeId + * @return array + */ + public function info($storeId) + { + // Retrieve store info + try { + $store = Mage::app()->getStore($storeId); + } catch (Mage_Core_Model_Store_Exception $e) { + $this->_fault('store_not_exists'); + } + + if (!$store->getId()) { + $this->_fault('store_not_exists'); + } + + // Basic store data + $result = array(); + $result['store_id'] = $store->getId(); + $result['code'] = $store->getCode(); + $result['website_id'] = $store->getWebsiteId(); + $result['group_id'] = $store->getGroupId(); + $result['name'] = $store->getName(); + $result['sort_order'] = $store->getSortOrder(); + $result['is_active'] = $store->getIsActive(); + + return $result; + } + +} diff --git a/app/code/core/Mage/Core/Model/Store/Api/V2.php b/app/code/core/Mage/Core/Model/Store/Api/V2.php new file mode 100644 index 00000000..138c6975 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Store/Api/V2.php @@ -0,0 +1,36 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Directory Country Api V2 + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Store_Api_V2 extends Mage_Core_Model_Store_Api +{ +} diff --git a/app/code/core/Mage/Core/Model/Store/Exception.php b/app/code/core/Mage/Core/Model/Store/Exception.php index ce5ce66c..78f2395b 100644 --- a/app/code/core/Mage/Core/Model/Store/Exception.php +++ b/app/code/core/Mage/Core/Model/Store/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Store/Group.php b/app/code/core/Mage/Core/Model/Store/Group.php index 34d5c19c..8b1cb01f 100644 --- a/app/code/core/Mage/Core/Model/Store/Group.php +++ b/app/code/core/Mage/Core/Model/Store/Group.php @@ -20,15 +20,23 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Store group model * - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Store_Group _getResource() + * @method Mage_Core_Model_Resource_Store_Group getResource() + * @method Mage_Core_Model_Store_Group setWebsiteId(int $value) + * @method string getName() + * @method Mage_Core_Model_Store_Group setName(string $value) + * @method Mage_Core_Model_Store_Group setRootCategoryId(int $value) + * @method Mage_Core_Model_Store_Group setDefaultStoreId(int $value) + * + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ @@ -210,7 +218,7 @@ public function getStoresCount() */ public function getDefaultStore() { - if (!$this->getDefaultStoreId()) { + if (!$this->hasDefaultStoreId()) { return false; } if (is_null($this->_stores)) { @@ -219,6 +227,46 @@ public function getDefaultStore() return $this->_defaultStore; } + /** + * Get most suitable store by locale + * If no store with given locale is found - default store is returned + * If group has no stores - null is returned + * + * @param string $locale + * @return Mage_Core_Model_Store|null + */ + public function getDefaultStoreByLocale($locale) + { + if ($this->getDefaultStore() && $this->getDefaultStore()->getLocaleCode() == $locale) { + return $this->getDefaultStore(); + } else { + $stores = $this->getStoresByLocale($locale); + if (count($stores)) { + return $stores[0]; + } else { + return $this->getDefaultStore() ? $this->getDefaultStore() : null; + } + } + } + + /** + * Retrieve list of stores with given locale + * + * @param $locale + * @return array + */ + public function getStoresByLocale($locale) + { + $stores = array(); + foreach ($this->getStores() as $store) { + /* @var $store Mage_Core_Model_Store */ + if ($store->getLocaleCode() == $locale) { + array_push($stores, $store); + } + } + return $stores; + } + /** * Set website model * diff --git a/app/code/core/Mage/Core/Model/Template.php b/app/code/core/Mage/Core/Model/Template.php new file mode 100644 index 00000000..1f04d477 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Template.php @@ -0,0 +1,183 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +/** + * Template model class + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +abstract class Mage_Core_Model_Template extends Mage_Core_Model_Abstract +{ + /** + * Types of template + */ + const TYPE_TEXT = 1; + const TYPE_HTML = 2; + + /** + * Default design area for emulation + */ + const DEFAULT_DESIGN_AREA = 'frontend'; + + /** + * Configuration of desing package for template + * + * @var Varien_Object + */ + protected $_designConfig; + + + /** + * Configuration of emulated desing package. + * + * @var Varien_Object|boolean + */ + protected $_emulatedDesignConfig = false; + + /** + * Initial environment information + * @see self::_applyDesignConfig() + * + * @var Varien_Object|null + */ + protected $_initialEnvironmentInfo = null; + + /** + * Applying of design config + * + * @return Mage_Core_Model_Template + */ + protected function _applyDesignConfig() + { + $designConfig = $this->getDesignConfig(); + $store = $designConfig->getStore(); + $storeId = is_object($store) ? $store->getId() : $store; + $area = $designConfig->getArea(); + if (!is_null($storeId)) { + $appEmulation = Mage::getSingleton('core/app_emulation'); + $this->_initialEnvironmentInfo = $appEmulation->startEnvironmentEmulation($storeId, $area); + } + return $this; + } + + /** + * Revert design settings to previous + * + * @return Mage_Core_Model_Template + */ + protected function _cancelDesignConfig() + { + if (!empty($this->_initialEnvironmentInfo)) { + $appEmulation = Mage::getSingleton('core/app_emulation'); + $appEmulation->stopEnvironmentEmulation($this->_initialEnvironmentInfo); + $this->_initialEnvironmentInfo = null; + } + return $this; + } + + /** + * Get design configuration data + * + * @return Varien_Object + */ + protected function getDesignConfig() + { + if(is_null($this->_designConfig)) { + $store = Mage::getDesign()->getStore(); + $storeId = is_object($store) ? $store->getId() : $store; + $this->_designConfig = new Varien_Object(array( + 'area' => Mage::getDesign()->getArea(), + 'store' => $storeId + )); + } + return $this->_designConfig; + } + + /** + * Initialize design information for template processing + * + * @param array $config + * @return Mage_Core_Model_Template + */ + public function setDesignConfig(array $config) + { + $this->getDesignConfig()->setData($config); + return $this; + } + + /** + * Save current design config and replace with design config from specified store + * Event is not dispatched. + * + * @param int|string $storeId + */ + public function emulateDesign($storeId, $area=self::DEFAULT_DESIGN_AREA) + { + if ($storeId) { + // save current design settings + $this->_emulatedDesignConfig = clone $this->getDesignConfig(); + if ($this->getDesignConfig()->getStore() != $storeId) { + $this->setDesignConfig(array('area' => $area, 'store' => $storeId)); + $this->_applyDesignConfig(); + } + } else { + $this->_emulatedDesignConfig = false; + } + } + + /** + * Revert to last design config, used before emulation + * + */ + public function revertDesign() + { + if ($this->_emulatedDesignConfig) { + $this->setDesignConfig($this->_emulatedDesignConfig->getData()); + $this->_cancelDesignConfig(); + $this->_emulatedDesignConfig = false; + } + } + + /** + * Return true if template type eq text + * + * @return boolean + */ + public function isPlain() + { + return $this->getType() == self::TYPE_TEXT; + } + + /** + * Getter for template type + * + * @return int|string + */ + abstract public function getType(); +} diff --git a/app/code/core/Mage/Core/Model/Translate.php b/app/code/core/Mage/Core/Model/Translate.php index 352fd91b..2e784729 100644 --- a/app/code/core/Mage/Core/Model/Translate.php +++ b/app/code/core/Mage/Core/Model/Translate.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Translate/Expr.php b/app/code/core/Mage/Core/Model/Translate/Expr.php index 06c9c1a3..b5a785e5 100644 --- a/app/code/core/Mage/Core/Model/Translate/Expr.php +++ b/app/code/core/Mage/Core/Model/Translate/Expr.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Translate/Inline.php b/app/code/core/Mage/Core/Model/Translate/Inline.php index c9908749..c5ebb2e8 100644 --- a/app/code/core/Mage/Core/Model/Translate/Inline.php +++ b/app/code/core/Mage/Core/Model/Translate/Inline.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -69,6 +69,13 @@ class Mage_Core_Model_Translate_Inline */ protected $_isJson = false; + /** + * Get max translate block in same tag + * + * @var int + */ + protected $_maxTranslateBlocks = 7; + /** * List of global tags * @@ -87,7 +94,6 @@ class Mage_Core_Model_Translate_Inline protected $_allowedTagsSimple = array( 'legend' => 'Caption for the fieldset element', 'label' => 'Label for an input element.', - 'option' => 'Drop-down list option', 'button' => 'Push button', 'a' => 'Link label', 'b' => 'Bold text', @@ -108,7 +114,6 @@ class Mage_Core_Model_Translate_Inline 'dd' => 'Item description in a definition list.', 'caption' => 'Table caption', 'th' => 'Header cell in a table', - 'td' => 'Standard cell in a table', 'abbr' => 'Abbreviated phrase', 'acronym' => 'An acronym', 'var' => 'Variable part of a text', @@ -124,7 +129,8 @@ class Mage_Core_Model_Translate_Inline 'h6' => 'Heading level 6', 'p' => 'Paragraph', 'pre' => 'Preformatted text', - 'center' => 'Centered text' + 'center' => 'Centered text', + 'select' => 'List options', ); /** @@ -201,7 +207,7 @@ public function stripInlineTranslations(&$body) $this->stripInlineTranslations($part); } } else if (is_string($body)) { - $body = preg_replace('#'.$this->_tokenRegex.'#', '$1', $body); + $body = preg_replace('#' . $this->_tokenRegex . '#', '$1', $body); } return $this; } @@ -228,8 +234,8 @@ public function processResponseBody(&$body) } else if (is_string($body)) { $this->_content = $body; - $this->_tagAttributes(); $this->_specialTags(); + $this->_tagAttributes(); $this->_otherText(); $this->_insertInlineScriptsHtml(); @@ -239,6 +245,9 @@ public function processResponseBody(&$body) return $this; } + /** + * Add translate js to body + */ protected function _insertInlineScriptsHtml() { if ($this->_isScriptInserted || stripos($this->_content, '</body>')===false) { @@ -246,27 +255,31 @@ protected function _insertInlineScriptsHtml() } $baseJsUrl = Mage::getBaseUrl('js'); - $ajaxUrl = Mage::getUrl('core/ajax/translate', array('_secure'=>Mage::app()->getStore()->isCurrentlySecure())); + $url_prefix = Mage::app()->getStore()->isAdmin() ? 'adminhtml' : 'core'; + $ajaxUrl = Mage::getUrl($url_prefix . '/ajax/translate', + array('_secure'=>Mage::app()->getStore()->isCurrentlySecure())); $trigImg = Mage::getDesign()->getSkinUrl('images/fam_book_open.png'); ob_start(); + $magentoSkinUrl = Mage::getDesign()->getSkinUrl('lib/prototype/windows/themes/magento.css'); ?> <!-- script type="text/javascript" src="<?php echo $baseJsUrl ?>prototype/effects.js"></script --> <script type="text/javascript" src="<?php echo $baseJsUrl ?>prototype/window.js"></script> <link rel="stylesheet" type="text/css" href="<?php echo $baseJsUrl ?>prototype/windows/themes/default.css"/> -<link rel="stylesheet" type="text/css" href="<?php echo $baseJsUrl ?>prototype/windows/themes/magento.css"/> +<link rel="stylesheet" type="text/css" href="<?php echo $magentoSkinUrl; ?>"/> <script type="text/javascript" src="<?php echo $baseJsUrl ?>mage/translate_inline.js"></script> <link rel="stylesheet" type="text/css" href="<?php echo $baseJsUrl ?>mage/translate_inline.css"/> <div id="translate-inline-trig"><img src="<?php echo $trigImg ?>" alt="[TR]"/></div> <script type="text/javascript"> - new TranslateInline('translate-inline-trig', '<?php echo $ajaxUrl ?>', '<?php echo Mage::getDesign()->getArea() ?>'); + new TranslateInline('translate-inline-trig', '<?php echo $ajaxUrl ?>', '<?php + echo Mage::getDesign()->getArea() ?>'); </script> <?php $html = ob_get_clean(); - $this->_content = str_ireplace('</body>', $html.'</body>', $this->_content); + $this->_content = str_ireplace('</body>', $html . '</body>', $this->_content); $this->_isScriptInserted = true; } @@ -282,137 +295,267 @@ protected function _escape($string) return str_replace("'", "\\'", htmlspecialchars($string)); } + /** + * Get attribute location + * + * @param array $matches + * @param array $options + * @return string + */ + protected function _getAttributeLocation($matches, $options) + { + return 'Tag attribute (ALT, TITLE, etc.)'; + } + + /** + * Get tag location + * + * @param array $matches + * @param array $options + * @return string + */ + protected function _getTagLocation($matches, $options) + { + $tagName = strtolower($options['tagName']); + + if (isset($options['tagList'][$tagName])) { + return $options['tagList'][$tagName]; + } + + return ucfirst($tagName) . ' Text'; + } + + /** + * Get translate data by regexp + * + * @param string $regexp + * @param string $text + * @param string|array $locationCallback + * @param array $options + * @return array + */ + protected function _getTranslateData($regexp, &$text, $locationCallback, $options = array()) + { + $trArr = array(); + $next = 0; + while (preg_match($regexp, $text, $m, PREG_OFFSET_CAPTURE, $next)) { + $trArr[] = json_encode(array( + 'shown' => $m[1][0], + 'translated' => $m[2][0], + 'original' => $m[3][0], + 'location' => call_user_func($locationCallback, $m, $options), + 'scope' => $m[4][0], + )); + $text = substr_replace($text, $m[1][0], $m[0][1], strlen($m[0][0])); + $next = $m[0][1]; + } + return $trArr; + } + + /** * Prepare tags inline translates * */ protected function _tagAttributes() + { + $this->_prepareTagAttributesForContent($this->_content); + } + + /** + * Prepare tags inline translates for the content + * + * @param string $content + */ + protected function _prepareTagAttributesForContent(&$content) { if ($this->getIsJson()) { - $quotePatern = '\\\\"'; $quoteHtml = '\"'; } else { - $quotePatern = '"'; $quoteHtml = '"'; } $tagMatch = array(); $nextTag = 0; - $tagRegExp = '#<([a-z]+)\s*?[^>]+?(('.$this->_tokenRegex.')[^/>]*?)+(/?(>))#i'; - while (preg_match($tagRegExp, $this->_content, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) { + $tagRegExp = '#<([a-z]+)\s*?[^>]+?((' . $this->_tokenRegex . ')[^>]*?)+/?>#i'; + while (preg_match($tagRegExp, $content, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) { $next = 0; $tagHtml = $tagMatch[0][0]; - $trArr = array(); $m = array(); - $attrRegExp = '#'.$this->_tokenRegex.'#'; - - while (preg_match($attrRegExp, $tagHtml, $m, PREG_OFFSET_CAPTURE, $next)) { - $trArr[] = '{shown:\''.$this->_escape($m[1][0]).'\',' - . 'translated:\''.$this->_escape($m[2][0]).'\',' - . 'original:\''.$this->_escape($m[3][0]).'\',' - . 'location:\'Tag attribute (ALT, TITLE, etc.)\',' - . 'scope:\''.$this->_escape($m[4][0]).'\'}'; - $tagHtml = substr_replace($tagHtml, $m[1][0], $m[0][1], strlen($m[0][0])); - $next = $m[0][1]; - } - - $transRegExp = '# translate='.$quotePatern.'\[(.+?)\]'.$quotePatern.'#i'; - if (preg_match($transRegExp, $tagHtml, $m, PREG_OFFSET_CAPTURE)) { - foreach ($trArr as $i => $tr) { - if (strpos($m[1][0], $tr) !== false) { - unset($trArr[$i]); - } + $attrRegExp = '#' . $this->_tokenRegex . '#'; + $trArr = $this->_getTranslateData($attrRegExp, $tagHtml, array($this, '_getAttributeLocation')); + if ($trArr) { + $transRegExp = '# translate=' . $quoteHtml . '\[([^'.preg_quote($quoteHtml).']*)]' . $quoteHtml . '#i'; + if (preg_match($transRegExp, $tagHtml, $m)) { + $tagHtml = str_replace($m[0], '', $tagHtml); //remove tra + $trAttr = ' translate=' . $quoteHtml + . htmlspecialchars('[' . $m[1] . ',' . join(',', $trArr) . ']') . $quoteHtml; + } else { + $trAttr = ' translate=' . $quoteHtml + . htmlspecialchars('[' . join(',', $trArr) . ']') . $quoteHtml; } - array_unshift($trArr, $m[1][0]); - $tagHtml = substr_replace($tagHtml, '', $m[0][1], strlen($m[0][0])); + $content = substr_replace($content, $tagHtml, $tagMatch[0][1], strlen($tagMatch[0][0])); } + $nextTag = $tagMatch[0][1] + strlen($tagHtml); + } + } - $trAttr = ' translate='.$quoteHtml.'['.join(',', $trArr).']'.$quoteHtml; - $tagHtml = preg_replace('#/?>$#', $trAttr . '$0', $tagHtml); - - $this->_content = substr_replace($this->_content, $tagHtml, $tagMatch[0][1], - $tagMatch[9][1]+1-$tagMatch[0][1]); - $nextTag = $tagMatch[0][1]; + /** + * Get html quote symbol + * + * @return string + */ + protected function _getHtmlQuote() + { + if ($this->getIsJson()) { + return '\"'; + } else { + return '"'; } } /** * Prepare special tags + */ + protected function _specialTags() { + $this->_translateTags($this->_content, $this->_allowedTagsGlobal, '_applySpecialTagsFormat', false); + $this->_translateTags($this->_content, $this->_allowedTagsSimple, '_applySimpleTagsFormat', true); + } + + /** + * Format translate for special tags + * + * @param string $tagHtml + * @param string $tagName + * @param array $trArr + * @return string + */ + protected function _applySpecialTagsFormat($tagHtml, $tagName, $trArr) + { + return $tagHtml . '<span class="translate-inline-' . $tagName + . '" translate=' + . $this->_getHtmlQuote() + . htmlspecialchars('[' . join(',', $trArr) . ']') + . $this->_getHtmlQuote() . '>' + . strtoupper($tagName) . '</span>'; + } + + /** + * Format translate for simple tags + * + * @param string $tagHtml + * @param string $tagName + * @param array $trArr + * @return string + */ + protected function _applySimpleTagsFormat($tagHtml, $tagName, $trArr) + { + return substr($tagHtml, 0, strlen($tagName) + 1) + . ' translate=' + . $this->_getHtmlQuote() . htmlspecialchars( '[' . join(',', $trArr) . ']') + . $this->_getHtmlQuote() + . substr($tagHtml, strlen($tagName) + 1); + } + + /** + * Prepare simple tags * + * @param string $body + * @param array $tagsList + * @param string|array $formatCallback + * @param bool $isNeedTranslateAttributes */ - protected function _specialTags() + protected function _translateTags(&$body, $tagsList, $formatCallback, $isNeedTranslateAttributes) { if ($this->getIsJson()) { - $quotePatern = '\\\\"'; $quoteHtml = '\"'; } else { - $quotePatern = '"'; $quoteHtml = '"'; } $nextTag = 0; - $location = array_merge($this->_allowedTagsGlobal, $this->_allowedTagsSimple); - $tags = implode('|', array_merge(array_keys($this->_allowedTagsGlobal), array_keys($this->_allowedTagsSimple))); - $tagRegExp = '#<(' . $tags . ')(\s+[^>]*|)(>)#i'; + $tags = implode('|', array_keys($tagsList)); + $tagRegExp = '#<(' . $tags . ')(\s*[^>]*>)#i'; $tagMatch = array(); - while (preg_match($tagRegExp, $this->_content, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) { - $tagClosure = '</'.$tagMatch[1][0].'>'; - $tagLength = stripos($this->_content, $tagClosure, $tagMatch[0][1])-$tagMatch[0][1]+strlen($tagClosure); + while (preg_match($tagRegExp, $body, $tagMatch, PREG_OFFSET_CAPTURE, $nextTag)) { + $tagName = strtolower($tagMatch[1][0]); + $tagClosurePos = $this->findEndOfTag($body, $tagName, $tagMatch[0][1]); + if ($tagClosurePos === false) { + $nextTag += strlen($tagMatch[0][0]); + continue; + } - $next = 0; - $tagHtml = substr($this->_content, $tagMatch[0][1], $tagLength); - $trArr = array(); - $m = array(); - while (preg_match('#'.$this->_tokenRegex.'#i', $tagHtml, $m, PREG_OFFSET_CAPTURE, $next)) { - $trArr[] = '{shown:\''.$this->_escape($m[1][0]).'\',' - .'translated:\''.$this->_escape($m[2][0]).'\',' - .'original:\''.$this->_escape($m[3][0]).'\',' - .'location:\''.$location[strtolower($tagMatch[1][0])].'\',' - .'scope:\''.$this->_escape($m[4][0]).'\'}'; - - $tagHtml = substr_replace($tagHtml, $m[1][0], $m[0][1], strlen($m[0][0])); - $next = $m[0][1]; + $tagLength = $tagClosurePos - $tagMatch[0][1]; + + $tagStartLength = strlen($tagMatch[0][0]); + + $tagHtml = $tagMatch[0][0] ; + $tagEnd = substr($body, $tagMatch[0][1] + $tagStartLength, $tagLength - $tagStartLength); + + if ($isNeedTranslateAttributes + && preg_match_all('#' . $this->_tokenRegex . '#', $tagEnd, $m) + && count($m[0]) > $this->_maxTranslateBlocks + ) { + $this->_translateTags($tagEnd, $tagsList, $formatCallback, $isNeedTranslateAttributes); + } + + if ($isNeedTranslateAttributes) { + $this->_prepareTagAttributesForContent($tagEnd); } + $tagHtml .= $tagEnd; + + $trArr = $this->_getTranslateData( + '#' . $this->_tokenRegex . '#i', + $tagHtml, + array($this, '_getTagLocation'), + array( + 'tagName' => $tagName, + 'tagList' => $tagsList + ) + ); + if (!empty($trArr)) { $trArr = array_unique($trArr); - $tag = strtolower($tagMatch[1][0]); + $tagHtml = call_user_func(array($this, $formatCallback), $tagHtml, $tagName, $trArr); - if (in_array($tag, array_keys($this->_allowedTagsGlobal))) { - $tagHtml .= '<span class="translate-inline-'.$tag - .'" translate='.$quoteHtml.'['.join(',', $trArr).']'.$quoteHtml.'>'.strtoupper($tag).'</span>'; - } - $this->_content = substr_replace($this->_content, $tagHtml, $tagMatch[0][1], $tagLength); - - if (in_array($tag, array_keys($this->_allowedTagsSimple))) { - if (preg_match('# translate='.$quotePatern.'\[(.+?)\]'.$quotePatern.'#i', $tagMatch[0][0], $m, PREG_OFFSET_CAPTURE)) { - foreach ($trArr as $i=>$tr) { - if (strpos($m[1][0], $tr)!==false) { - unset($trArr[$i]); - } - } - array_unshift($trArr, $m[1][0]); - $start = $tagMatch[0][1]+$m[0][1]; - $len = strlen($m[0][0]); - } else { - $start = $tagMatch[2][1]; - $len = 0; - } - - $this->_content = substr_replace($this->_content, - ' translate='.$quoteHtml.'['.join(',', $trArr).']'.$quoteHtml, $start, $len); - } + $body = substr_replace($body, $tagHtml, $tagMatch[0][1], $tagLength); } - - $nextTag = $tagMatch[0][1]+10; + $nextTag = $tagClosurePos; } + } + /** + * Find end of tag + * + * @param $body + * @param $tagName + * @param $from + * @return bool|int return false if end of tag is not found + */ + private function findEndOfTag($body, $tagName, $from) + { + $openTag = '<' . $tagName; + $closeTag = '</' . $tagName; + $end = $from + strlen($openTag); + $length = $end - $from; + while (substr_count($body, $openTag, $from, $length) != substr_count($body, $closeTag, $from, $length)) { + $end = strpos($body, $closeTag, $end + strlen($closeTag) - 1); + if ($end === false) { + return false; + } + $length = $end - $from + strlen($closeTag); + } + if (preg_match('#<\/' . $tagName .'\s*?>#i', $body, $tagMatch, null, $end)) { + return $end + strlen($tagMatch[0]); + } else { + return false; + } } /** * Prepare other text inline translates - * */ protected function _otherText() { @@ -424,23 +567,19 @@ protected function _otherText() $next = 0; $m = array(); - while (preg_match('#(>|title=\")*('.$this->_tokenRegex.')#', $this->_content, $m, PREG_OFFSET_CAPTURE, $next)) { - if(-1 == $m[1][1])//title was not found - this is not an attribute - { - - $tr = '{shown:\''.$this->_escape($m[3][0]).'\',' - .'translated:\''.$this->_escape($m[4][0]).'\',' - .'original:\''.$this->_escape($m[5][0]).'\',' - .'location:\'Text\',' - .'scope:\''.$this->_escape($m[6][0]).'\'}'; - $spanHtml = '<span translate='.$quoteHtml.'['.$tr.']'.$quoteHtml.'>'.$m[3][0].'</span>'; - } - else - { - $spanHtml = $m[3][0]; - } - $this->_content = substr_replace($this->_content, $spanHtml, $m[2][1], strlen($m[2][0]) ); - $next = $m[0][1]; + while (preg_match('#' . $this->_tokenRegex . '#', $this->_content, $m, PREG_OFFSET_CAPTURE, $next)) { + $tr = json_encode(array( + 'shown' => $m[1][0], + 'translated' => $m[2][0], + 'original' => $m[3][0], + 'location' => 'Text', + 'scope' => $m[4][0], + )); + + $spanHtml = '<span translate=' . $quoteHtml . htmlspecialchars('[' . $tr . ']') . $quoteHtml + . '>' . $m[1][0] . '</span>'; + $this->_content = substr_replace($this->_content, $spanHtml, $m[0][1], strlen($m[0][0])); + $next = $m[0][1] + strlen($spanHtml) - 1; } } @@ -491,4 +630,3 @@ public function setIsJson($flag) return $this; } } - diff --git a/app/code/core/Mage/Core/Model/Translate/String.php b/app/code/core/Mage/Core/Model/Translate/String.php index 2d8a9578..606eb190 100644 --- a/app/code/core/Mage/Core/Model/Translate/String.php +++ b/app/code/core/Mage/Core/Model/Translate/String.php @@ -20,15 +20,24 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * String translation model * - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Translate_String _getResource() + * @method Mage_Core_Model_Resource_Translate_String getResource() + * @method int getStoreId() + * @method Mage_Core_Model_Translate_String setStoreId(int $value) + * @method string getTranslate() + * @method Mage_Core_Model_Translate_String setTranslate(string $value) + * @method string getLocale() + * @method Mage_Core_Model_Translate_String setLocale(string $value) + * + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ class Mage_Core_Model_Translate_String extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Core/Model/Url.php b/app/code/core/Mage/Core/Model/Url.php index 5ae085f8..c87bf481 100644 --- a/app/code/core/Mage/Core/Model/Url.php +++ b/app/code/core/Mage/Core/Model/Url.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -78,20 +78,40 @@ */ class Mage_Core_Model_Url extends Varien_Object { + /** + * Default controller name + */ const DEFAULT_CONTROLLER_NAME = 'index'; + + /** + * Default action name + */ const DEFAULT_ACTION_NAME = 'index'; + /** + * Configuration paths + */ const XML_PATH_UNSECURE_URL = 'web/unsecure/base_url'; const XML_PATH_SECURE_URL = 'web/secure/base_url'; - - const XML_PATH_SECURE_IN_ADMIN = 'web/secure/use_in_adminhtml'; + const XML_PATH_SECURE_IN_ADMIN = 'default/web/secure/use_in_adminhtml'; const XML_PATH_SECURE_IN_FRONT = 'web/secure/use_in_frontend'; + /** + * Configuration data cache + * + * @var array + */ static protected $_configDataCache; + + /** + * Encrypted session identifier + * + * @var string|null + */ static protected $_encryptedSessionId; /** - * Reserved Route parametr keys + * Reserved Route parameter keys * * @var array */ @@ -115,6 +135,9 @@ class Mage_Core_Model_Url extends Varien_Object */ protected $_useSession; + /** + * Initialize object + */ protected function _construct() { $this->setStore(null); @@ -128,18 +151,18 @@ protected function _construct() */ public function parseUrl($url) { - $data = parse_url($url); - $parts = array( - 'scheme'=>'setScheme', - 'host' =>'setHost', - 'port' =>'setPort', - 'user' =>'setUser', - 'pass' =>'setPassword', - 'path' =>'setPath', - 'query' =>'setQuery', - 'fragment'=>'setFragment'); - - foreach ($parts as $component=>$method) { + $data = parse_url($url); + $parts = array( + 'scheme' => 'setScheme', + 'host' => 'setHost', + 'port' => 'setPort', + 'user' => 'setUser', + 'pass' => 'setPassword', + 'path' => 'setPath', + 'query' => 'setQuery', + 'fragment' => 'setFragment'); + + foreach ($parts as $component => $method) { if (isset($data[$component])) { $this->$method($data[$component]); } @@ -157,6 +180,12 @@ public function getDefaultControllerName() return self::DEFAULT_CONTROLLER_NAME; } + /** + * Set use_url_cache flag + * + * @param boolean $flag + * @return Mage_Core_Model_Url + */ public function setUseUrlCache($flag) { $this->setData('use_url_cache', $flag); @@ -171,10 +200,16 @@ public function setUseUrlCache($flag) */ public function setUseSession($useSession) { - $this->_useSession = (bool)$useSession; + $this->_useSession = (bool) $useSession; return $this; } + /** + * Set route front name + * + * @param string $name + * @return Mage_Core_Model_Url + */ public function setRouteFrontName($name) { $this->setData('route_front_name', $name); @@ -204,14 +239,21 @@ public function getDefaultActionName() return self::DEFAULT_ACTION_NAME; } - public function getConfigData($key, $prefix=null) + /** + * Retrieve configuration data + * + * @param string $key + * @param string|null $prefix + * @return string + */ + public function getConfigData($key, $prefix = null) { if (is_null($prefix)) { - $prefix = 'web/'.($this->getSecure() ? 'secure' : 'unsecure').'/'; + $prefix = 'web/' . ($this->getSecure() ? 'secure' : 'unsecure').'/'; } - $path = $prefix.$key; + $path = $prefix . $key; - $cacheId = $this->getStore()->getCode().'/'.$path; + $cacheId = $this->getStore()->getCode() . '/' . $path; if (!isset(self::$_configDataCache[$cacheId])) { $data = $this->getStore()->getConfig($path); self::$_configDataCache[$cacheId] = $data; @@ -220,6 +262,12 @@ public function getConfigData($key, $prefix=null) return self::$_configDataCache[$cacheId]; } + /** + * Set request + * + * @param Zend_Controller_Request_Http $request + * @return Mage_Core_Model_Url + */ public function setRequest(Zend_Controller_Request_Http $request) { $this->_request = $request; @@ -239,6 +287,11 @@ public function getRequest() return $this->_request; } + /** + * Retrieve URL type + * + * @return string + */ public function getType() { if (!$this->hasData('type')) { @@ -255,29 +308,35 @@ public function getType() public function getSecure() { if ($this->hasData('secure_is_forced')) { - return $this->getData('secure'); + return (bool)$this->getData('secure'); } $store = $this->getStore(); - if ($store->isAdmin() && !$store->isAdminUrlSecure()) { //!Mage::getStoreConfigFlag(self::XML_PATH_SECURE_IN_ADMIN, $this->getStore()->getId()) + if ($store->isAdmin() && !$store->isAdminUrlSecure()) { return false; } - if (!$store->isAdmin() && !$store->isFrontUrlSecure()) {//!Mage::getStoreConfigFlag(self::XML_PATH_SECURE_IN_FRONT + if (!$store->isAdmin() && !$store->isFrontUrlSecure()) { return false; } if (!$this->hasData('secure')) { - if ($this->getType() == Mage_Core_Model_Store::URL_TYPE_LINK) { - $pathSecure = Mage::getConfig()->shouldUrlBeSecure('/'.$this->getActionPath()); + if ($this->getType() == Mage_Core_Model_Store::URL_TYPE_LINK && !$store->isAdmin()) { + $pathSecure = Mage::getConfig()->shouldUrlBeSecure('/' . $this->getActionPath()); $this->setData('secure', $pathSecure); } else { - $this->setData('secure', $store->isCurrentlySecure()); + $this->setData('secure', true); } } return $this->getData('secure'); } + /** + * Set store entity + * + * @param mixed $data + * @return Mage_Core_Model_Url + */ public function setStore($data) { $this->setData('store', Mage::app()->getStore($data)); @@ -334,35 +393,35 @@ public function getBaseUrl($params = array()) */ public function setRoutePath($data) { - if ($this->_getData('route_path')==$data) { + if ($this->_getData('route_path') == $data) { return $this; } $a = explode('/', $data); $route = array_shift($a); - if ('*'===$route) { + if ('*' === $route) { $route = $this->getRequest()->getRequestedRouteName(); } $this->setRouteName($route); - $routePath = $route.'/'; + $routePath = $route . '/'; if (!empty($a)) { $controller = array_shift($a); - if ('*'===$controller) { + if ('*' === $controller) { $controller = $this->getRequest()->getRequestedControllerName(); } $this->setControllerName($controller); - $routePath .= $controller.'/'; + $routePath .= $controller . '/'; } if (!empty($a)) { $action = array_shift($a); - if ('*'===$action) { + if ('*' === $action) { $action = $this->getRequest()->getRequestedActionName(); } $this->setActionName($action); - $routePath .= $action.'/'; + $routePath .= $action . '/'; } if (!empty($a)) { @@ -372,24 +431,26 @@ public function setRoutePath($data) if (!empty($a)) { $value = array_shift($a); $this->setRouteParam($key, $value); - #$routePath .= $key.'/'.urlencode($value).'/'; - $routePath .= $key.'/'.$value.'/'; + $routePath .= $key . '/' . $value . '/'; } } } - #$this->setData('route_path', $routePath); - return $this; } + /** + * Retrieve action path + * + * @return string + */ public function getActionPath() { if (!$this->getRouteName()) { return ''; } - $hasParams = (bool)$this->getRouteParams(); + $hasParams = (bool) $this->getRouteParams(); $path = $this->getRouteFrontName() . '/'; if ($this->getControllerName()) { @@ -406,35 +467,46 @@ public function getActionPath() return $path; } - public function getRoutePath($routeParams=array()) + /** + * Retrieve route path + * + * @param array $routParams + * @return string + */ + public function getRoutePath($routeParams = array()) { if (!$this->hasData('route_path')) { $routePath = $this->getRequest()->getAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS); - if (!empty($routeParams['_use_rewrite']) - && ($routePath !== null)) { + if (!empty($routeParams['_use_rewrite']) && ($routePath !== null)) { $this->setData('route_path', $routePath); return $routePath; } $routePath = $this->getActionPath(); if ($this->getRouteParams()) { foreach ($this->getRouteParams() as $key=>$value) { - if (is_null($value) || false===$value || ''===$value || !is_scalar($value)) { + if (is_null($value) || false === $value || '' === $value || !is_scalar($value)) { continue; } - $routePath .= $key.'/'.$value.'/'; + $routePath .= $key . '/' . $value . '/'; } } if ($routePath != '' && substr($routePath, -1, 1) !== '/') { - $routePath.= '/'; + $routePath .= '/'; } $this->setData('route_path', $routePath); } return $this->_getData('route_path'); } + /** + * Set route name + * + * @param string $data + * @return Mage_Core_Model_Url + */ public function setRouteName($data) { - if ($this->_getData('route_name')==$data) { + if ($this->_getData('route_name') == $data) { return $this; } $this->unsetData('route_front_name') @@ -445,6 +517,11 @@ public function setRouteName($data) return $this->setData('route_name', $data); } + /** + * Retrieve route front name + * + * @return string + */ public function getRouteFrontName() { if (!$this->hasData('route_front_name')) { @@ -458,6 +535,11 @@ public function getRouteFrontName() return $this->_getData('route_front_name'); } + /** + * Retrieve route name + * + * @return string|null + */ public function getRouteName() { return $this->_getData('route_name'); @@ -465,6 +547,7 @@ public function getRouteName() /** * Set Controller Name + * * Reset action name and route path if has change * * @param string $data @@ -472,13 +555,18 @@ public function getRouteName() */ public function setControllerName($data) { - if ($this->_getData('controller_name')==$data) { + if ($this->_getData('controller_name') == $data) { return $this; } $this->unsetData('route_path')->unsetData('action_name')->unsetData('secure'); return $this->setData('controller_name', $data); } + /** + * Retrieve controller name + * + * @return string|null + */ public function getControllerName() { return $this->_getData('controller_name'); @@ -500,12 +588,24 @@ public function setActionName($data) return $this->setData('action_name', $data)->unsetData('secure'); } + /** + * Retrieve action name + * + * @return string|null + */ public function getActionName() { return $this->_getData('action_name'); } - public function setRouteParams(array $data, $unsetOldParams=true) + /** + * Set route params + * + * @param array $data + * @param boolean $unsetOldParams + * @return Mage_Core_Model_Url + */ + public function setRouteParams(array $data, $unsetOldParams = true) { if (isset($data['_type'])) { $this->setType($data['_type']); @@ -521,11 +621,9 @@ public function setRouteParams(array $data, $unsetOldParams=true) $this->setSecure((bool)$data['_forced_secure']); $this->setSecureIsForced(true); unset($data['_forced_secure']); - } else { - if (isset($data['_secure'])) { - $this->setSecure((bool)$data['_secure']); - unset($data['_secure']); - } + } elseif (isset($data['_secure'])) { + $this->setSecure((bool)$data['_secure']); + unset($data['_secure']); } if (isset($data['_absolute'])) { @@ -546,13 +644,13 @@ public function setRouteParams(array $data, $unsetOldParams=true) $data[$key] = $this->getRequest()->getUserParam($key); } } elseif ($data['_current']) { - foreach ($this->getRequest()->getUserParams() as $key=>$value) { + foreach ($this->getRequest()->getUserParams() as $key => $value) { if (array_key_exists($key, $data) || $this->getRouteParam($key)) { continue; } $data[$key] = $value; } - foreach ($this->getRequest()->getQuery() as $key=>$value) { + foreach ($this->getRequest()->getQuery() as $key => $value) { $this->setQueryParam($key, $value); } $this->setUseUrlCache(false); @@ -573,22 +671,34 @@ public function setRouteParams(array $data, $unsetOldParams=true) } unset($data['_store_to_url']); - foreach ($data as $k=>$v) { + foreach ($data as $k => $v) { $this->setRouteParam($k, $v); } return $this; } + /** + * Retrieve route params + * + * @return array + */ public function getRouteParams() { return $this->_getData('route_params'); } + /** + * Set route param + * + * @param string $key + * @param mixed $data + * @return Mage_Core_Model_Url + */ public function setRouteParam($key, $data) { $params = $this->_getData('route_params'); - if (isset($params[$key]) && $params[$key]==$data) { + if (isset($params[$key]) && $params[$key] == $data) { return $this; } $params[$key] = $data; @@ -596,12 +706,26 @@ public function setRouteParam($key, $data) return $this->setData('route_params', $params); } + /** + * Retrieve route params + * + * @param string $key + * @return mixed + */ public function getRouteParam($key) { - return $this->_getData('route_params', $key); + return $this->getData('route_params', $key); } - public function getRouteUrl($routePath=null, $routeParams=null) + /** + * Retrieve route URL + * + * @param string $routePath + * @param array $routeParams + * + * @return string + */ + public function getRouteUrl($routePath = null, $routeParams = null) { $this->unsetData('route_params'); @@ -609,7 +733,7 @@ public function getRouteUrl($routePath=null, $routeParams=null) if (is_array($routeParams)) { $this->setRouteParams($routeParams, false); } - return $this->getBaseUrl().$routeParams['_direct']; + return $this->getBaseUrl() . $routeParams['_direct']; } if (!is_null($routePath)) { @@ -619,19 +743,19 @@ public function getRouteUrl($routePath=null, $routeParams=null) $this->setRouteParams($routeParams, false); } - $url = $this->getBaseUrl().$this->getRoutePath($routeParams); + $url = $this->getBaseUrl() . $this->getRoutePath($routeParams); return $url; } /** * If the host was switched but session cookie won't recognize it - add session id to query * - * @return unknown + * @return Mage_Core_Model_Url */ public function checkCookieDomains() { $hostArr = explode(':', $this->getRequest()->getServer('HTTP_HOST')); - if ($hostArr[0]!==$this->getHost()) { + if ($hostArr[0] !== $this->getHost()) { $session = Mage::getSingleton('core/session'); if (!$session->isValidForHost($this->getHost())) { if (!self::$_encryptedSessionId) { @@ -641,15 +765,17 @@ public function checkCookieDomains() } self::$_encryptedSessionId = $session->getEncryptedSessionId(); } - $this->setQueryParam( - $session->getSessionIdQueryParam(), - self::$_encryptedSessionId - ); + $this->setQueryParam($session->getSessionIdQueryParam(), self::$_encryptedSessionId); } } return $this; } + /** + * Add session param + * + * @return Mage_Core_Model_Url + */ public function addSessionParam() { $session = Mage::getSingleton('core/session'); @@ -661,10 +787,7 @@ public function addSessionParam() } self::$_encryptedSessionId = $session->getEncryptedSessionId(); } - $this->setQueryParam( - $session->getSessionIdQueryParam(), - self::$_encryptedSessionId - ); + $this->setQueryParam($session->getSessionIdQueryParam(), self::$_encryptedSessionId); return $this; } @@ -741,7 +864,7 @@ public function purgeQueryParams() } /** - * Retrurn Query Params + * Return Query Params * * @return array */ @@ -760,10 +883,17 @@ public function getQueryParams() return $this->_getData('query_params'); } + /** + * Set query param + * + * @param string $key + * @param mixed $data + * @return Mage_Core_Model_Url + */ public function setQueryParam($key, $data) { $params = $this->getQueryParams(); - if (isset($params[$key]) && $params[$key]==$data) { + if (isset($params[$key]) && $params[$key] == $data) { return $this; } $params[$key] = $data; @@ -771,12 +901,18 @@ public function setQueryParam($key, $data) return $this->setData('query_params', $params); } + /** + * Retrieve query param + * + * @param string $key + * @return mixed + */ public function getQueryParam($key) { if (!$this->hasData('query_params')) { $this->getQueryParams(); } - return $this->_getData('query_params', $key); + return $this->getData('query_params', $key); } /** @@ -790,6 +926,11 @@ public function setFragment($data) return $this->setData('fragment', $data); } + /** + * Retrieve URL fragment + * + * @return string|null + */ public function getFragment() { return $this->_getData('fragment'); @@ -798,17 +939,17 @@ public function getFragment() /** * Build url by requested path and parameters * - * @param string $routePath - * @param array $routeParams + * @param string|null $routePath + * @param array|null $routeParams * @return string */ - public function getUrl($routePath=null, $routeParams=null) + public function getUrl($routePath = null, $routeParams = null) { $escapeQuery = false; /** - * All system params should be unseted before we call getRouteUrl - * this method has condition for ading default controller anr actions names + * All system params should be unset before we call getRouteUrl + * this method has condition for adding default controller and action names * in case when we have params */ if (isset($routeParams['_fragment'])) { @@ -852,12 +993,14 @@ public function getUrl($routePath=null, $routeParams=null) $this->_prepareSessionUrl($url); } - if ($query = $this->getQuery($escapeQuery)) { - $url .= '?'.$query; + $query = $this->getQuery($escapeQuery); + if ($query) { + $mark = (strpos($url, '?') === false) ? '?' : ($escapeQuery ? '&' : '&'); + $url .= $mark . $query; } if ($this->getFragment()) { - $url .= '#'.$this->getFragment(); + $url .= '#' . $this->getFragment(); } return $this->escape($url); @@ -867,32 +1010,72 @@ public function getUrl($routePath=null, $routeParams=null) * Check and add session id to URL * * @param string $url + * * @return Mage_Core_Model_Url */ protected function _prepareSessionUrl($url) + { + return $this->_prepareSessionUrlWithParams($url, array()); + } + + /** + * Check and add session id to URL, session is obtained with parameters + * + * @param string $url + * @param array $params + * + * @return Mage_Core_Model_Url + */ + protected function _prepareSessionUrlWithParams($url, array $params) { if (!$this->getUseSession()) { return $this; } - $session = Mage::getSingleton('core/session'); - /* @var $session Mage_Core_Model_Session */ - if (Mage::app()->getUseSessionVar()) { - // secure URL - if ($this->getSecure()) { - $this->setQueryParam('___SID', 'S'); - } - else { - $this->setQueryParam('___SID', 'U'); - } - } - else { - if ($sessionId = $session->getSessionIdForHost($url)) { - $this->setQueryParam($session->getSessionIdQueryParam(), $sessionId); - } + + /** @var $session Mage_Core_Model_Session */ + $session = Mage::getSingleton('core/session', $params); + + $sessionId = $session->getSessionIdForHost($url); + if (Mage::app()->getUseSessionVar() && !$sessionId) { + $this->setQueryParam('___SID', $this->getSecure() ? 'S' : 'U'); // Secure/Unsecure + } else if ($sessionId) { + $this->setQueryParam($session->getSessionIdQueryParam(), $sessionId); } return $this; } + /** + * Rebuild URL to handle the case when session ID was changed + * + * @param string $url + * @return string + */ + public function getRebuiltUrl($url) + { + $this->parseUrl($url); + $port = $this->getPort(); + if ($port) { + $port = ':' . $port; + } else { + $port = ''; + } + $url = $this->getScheme() . '://' . $this->getHost() . $port . $this->getPath(); + + $this->_prepareSessionUrl($url); + + $query = $this->getQuery(); + if ($query) { + $url .= '?' . $query; + } + + $fragment = $this->getFragment(); + if ($fragment) { + $url .= '#' . $fragment; + } + + return $this->escape($url); + } + /** * Escape (enclosure) URL string * @@ -928,7 +1111,8 @@ public function getDirectUrl($url, $params = array()) { */ public function sessionUrlVar($html) { - return preg_replace_callback('#(\?|&|&)___SID=([SU])(&|&)?#', array($this, "sessionVarCallback"), $html); + return preg_replace_callback('#(\?|&|&)___SID=([SU])(&|&)?#', + array($this, "sessionVarCallback"), $html); } /** @@ -939,15 +1123,15 @@ public function sessionUrlVar($html) */ public function useSessionIdForUrl($secure = false) { - $key = 'use_session_id_for_url_' . (int)$secure; + $key = 'use_session_id_for_url_' . (int) $secure; if (is_null($this->getData($key))) { $httpHost = Mage::app()->getFrontController()->getRequest()->getHttpHost(); - $urlHost = parse_url(Mage::app()->getStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, $secure), PHP_URL_HOST); + $urlHost = parse_url(Mage::app()->getStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, $secure), + PHP_URL_HOST); if ($httpHost != $urlHost) { $this->setData($key, true); - } - else { + } else { $this->setData($key, false); } } @@ -969,21 +1153,58 @@ public function sessionVarCallback($match) . $session->getSessionIdQueryParam() . '=' . $session->getEncryptedSessionId() . (isset($match[3]) ? $match[3] : ''); - } - else { + } else { if ($match[1] == '?' && isset($match[3])) { return '?'; - } - elseif ($match[1] == '?' && !isset($match[3])) { + } elseif ($match[1] == '?' && !isset($match[3])) { return ''; - } - elseif (($match[1] == '&' || $match[1] == '&') && !isset($match[3])) { + } elseif (($match[1] == '&' || $match[1] == '&') && !isset($match[3])) { return ''; - } - elseif (($match[1] == '&' || $match[1] == '&') && isset($match[3])) { + } elseif (($match[1] == '&' || $match[1] == '&') && isset($match[3])) { return $match[3]; } } return ''; } + + /** + * Check if users originated URL is one of the domain URLs assigned to stores + * + * @return boolean + */ + public function isOwnOriginUrl() + { + $storeDomains = array(); + $referer = parse_url(Mage::app()->getFrontController()->getRequest()->getServer('HTTP_REFERER'), PHP_URL_HOST); + foreach (Mage::app()->getStores() as $store) { + $storeDomains[] = parse_url($store->getBaseUrl(), PHP_URL_HOST); + $storeDomains[] = parse_url($store->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true), PHP_URL_HOST); + } + $storeDomains = array_unique($storeDomains); + if (empty($referer) || in_array($referer, $storeDomains)) { + return true; + } + return false; + } + + /** + * Return frontend redirect URL with SID and other session parameters if any + * + * @param string $url + * + * @return string + */ + public function getRedirectUrl($url) + { + $this->_prepareSessionUrlWithParams($url, array( + 'name' => Mage_Core_Controller_Front_Action::SESSION_NAMESPACE + )); + + $query = $this->getQuery(false); + if ($query) { + $url .= (strpos($url, '?') === false ? '?' : '&') . $query; + } + + return $url; + } } diff --git a/app/code/core/Mage/Core/Model/Url/Rewrite.php b/app/code/core/Mage/Core/Model/Url/Rewrite.php index 03622576..4af6d5c5 100644 --- a/app/code/core/Mage/Core/Model/Url/Rewrite.php +++ b/app/code/core/Mage/Core/Model/Url/Rewrite.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,9 +28,28 @@ /** * Url rewrite model class * + * @method Mage_Core_Model_Resource_Url_Rewrite _getResource() + * @method Mage_Core_Model_Resource_Url_Rewrite getResource() + * @method Mage_Core_Model_Url_Rewrite setStoreId(int $value) + * @method int getCategoryId() + * @method Mage_Core_Model_Url_Rewrite setCategoryId(int $value) + * @method int getProductId() + * @method Mage_Core_Model_Url_Rewrite setProductId(int $value) + * @method string getIdPath() + * @method Mage_Core_Model_Url_Rewrite setIdPath(string $value) + * @method string getRequestPath() + * @method Mage_Core_Model_Url_Rewrite setRequestPath(string $value) + * @method string getTargetPath() + * @method Mage_Core_Model_Url_Rewrite setTargetPath(string $value) + * @method int getIsSystem() + * @method Mage_Core_Model_Url_Rewrite setIsSystem(int $value) + * @method string getOptions() + * @method Mage_Core_Model_Url_Rewrite setOptions(string $value) + * @method string getDescription() + * @method Mage_Core_Model_Url_Rewrite setDescription(string $value) * - * @category Mage - * @package Mage_Core + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ class Mage_Core_Model_Url_Rewrite extends Mage_Core_Model_Abstract @@ -70,8 +89,7 @@ protected function _afterSave() /** * Load rewrite information for request - * - * if $path is array - that mean what we need try load for each item + * If $path is array - we must load possible records and choose one matching earlier record in array * * @param mixed $path * @return Mage_Core_Model_Url_Rewrite @@ -79,18 +97,10 @@ protected function _afterSave() public function loadByRequestPath($path) { $this->setId(null); - - if (is_array($path)) { - foreach ($path as $pathInfo) { - $this->load($pathInfo, 'request_path'); - if ($this->getId()) { - return $this; - } - } - } - else { - $this->load($path, 'request_path'); - } + $this->_getResource()->loadByRequestPath($this, $path); + $this->_afterLoad(); + $this->setOrigData(); + $this->_hasDataChanges = false; return $this; } @@ -197,20 +207,26 @@ public function rewrite(Zend_Controller_Request_Http $request=null, Zend_Control $this->setStoreId(Mage::app()->getStore()->getId()); } - $requestCases = array(); - $requestPath = trim($request->getPathInfo(), '/'); - /** - * We need try to find rewrites information for both cases - * More priority has url with query params + * We have two cases of incoming paths - with and without slashes at the end ("/somepath/" and "/somepath"). + * Each of them matches two url rewrite request paths - with and without slashes at the end ("/somepath/" and "/somepath"). + * Choose any matched rewrite, but in priority order that depends on same presence of slash and query params. */ - if ($queryString = $this->_getQueryString()) { - $requestCases[] = $requestPath .'?'.$queryString; - $requestCases[] = $requestPath; - } - else { - $requestCases[] = $requestPath; + $requestCases = array(); + $pathInfo = $request->getPathInfo(); + $origSlash = (substr($pathInfo, -1) == '/') ? '/' : ''; + $requestPath = trim($pathInfo, '/'); + + // If there were final slash - add nothing to less priority paths. And vice versa. + $altSlash = $origSlash ? '' : '/'; + + $queryString = $this->_getQueryString(); // Query params in request, matching "path + query" has more priority + if ($queryString) { + $requestCases[] = $requestPath . $origSlash . '?' . $queryString; + $requestCases[] = $requestPath . $altSlash . '?' . $queryString; } + $requestCases[] = $requestPath . $origSlash; + $requestCases[] = $requestPath . $altSlash; $this->loadByRequestPath($requestCases); @@ -229,7 +245,13 @@ public function rewrite(Zend_Controller_Request_Http $request=null, Zend_Control if (!$this->getId()) { return false; } - $this->setStoreId(Mage::app()->getStore()->getId())->loadByIdPath($this->getIdPath()); + $currentStore = Mage::app()->getStore(); + $this->setStoreId($currentStore->getId())->loadByIdPath($this->getIdPath()); + + Mage::app()->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $currentStore->getCode(), true); + $targetUrl = $request->getBaseUrl(). '/' . $this->getRequestPath(); + + $this->_sendRedirectHeaders($targetUrl, true); } if (!$this->getId()) { @@ -241,11 +263,10 @@ public function rewrite(Zend_Controller_Request_Http $request=null, Zend_Control $external = substr($this->getTargetPath(), 0, 6); $isPermanentRedirectOption = $this->hasOption('RP'); if ($external === 'http:/' || $external === 'https:') { - if ($isPermanentRedirectOption) { - header('HTTP/1.1 301 Moved Permanently'); - } - header("Location: ".$this->getTargetPath()); - exit; + $destinationStoreCode = Mage::app()->getStore($this->getStoreId())->getCode(); + Mage::app()->getCookie()->set(Mage_Core_Model_Store::COOKIE_NAME, $destinationStoreCode, true); + + $this->_sendRedirectHeaders($this->getTargetPath(), $isPermanentRedirectOption); } else { $targetUrl = $request->getBaseUrl(). '/' . $this->getTargetPath(); } @@ -254,18 +275,16 @@ public function rewrite(Zend_Controller_Request_Http $request=null, Zend_Control if (Mage::getStoreConfig('web/url/use_store') && $storeCode = Mage::app()->getStore()->getCode()) { $targetUrl = $request->getBaseUrl(). '/' . $storeCode . '/' .$this->getTargetPath(); } - if ($isPermanentRedirectOption) { - header('HTTP/1.1 301 Moved Permanently'); - } - header('Location: '.$targetUrl); - exit; + + $this->_sendRedirectHeaders($targetUrl, $isPermanentRedirectOption); } if (Mage::getStoreConfig('web/url/use_store') && $storeCode = Mage::app()->getStore()->getCode()) { $targetUrl = $request->getBaseUrl(). '/' . $storeCode . '/' .$this->getTargetPath(); } - if ($queryString = $this->_getQueryString()) { + $queryString = $this->_getQueryString(); + if ($queryString) { $targetUrl .= '?'.$queryString; } @@ -302,4 +321,21 @@ public function getStoreId() return $this->_getData('store_id'); } + /** + * Add location header and disable browser page caching + * + * @param string $url + * @param bool $isPermanent + */ + protected function _sendRedirectHeaders($url, $isPermanent = false) + { + if ($isPermanent) { + header('HTTP/1.1 301 Moved Permanently'); + } + + header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); + header('Pragma: no-cache'); + header('Location: ' . $url); + exit; + } } diff --git a/app/code/core/Mage/Core/Model/Url/Validator.php b/app/code/core/Mage/Core/Model/Url/Validator.php new file mode 100644 index 00000000..6cad2be8 --- /dev/null +++ b/app/code/core/Mage/Core/Model/Url/Validator.php @@ -0,0 +1,78 @@ +<?php +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Open Software License (OSL 3.0) + * that is bundled with this package in the file LICENSE.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/osl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Validate URL + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ +class Mage_Core_Model_Url_Validator extends Zend_Validate_Abstract +{ + /**#@+ + * Error keys + */ + const INVALID_URL = 'invalidUrl'; + /**#@-*/ + + /** + * Object constructor + */ + public function __construct() + { + // set translated message template + $this->setMessage(Mage::helper('core')->__("Invalid URL '%value%'."), self::INVALID_URL); + } + + /** + * Validation failure message template definitions + * + * @var array + */ + protected $_messageTemplates = array( + self::INVALID_URL => "Invalid URL '%value%'.", + ); + + /** + * Validate value + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $this->_setValue($value); + + //check valid URL + if (!Zend_Uri::check($value)) { + $this->_error(self::INVALID_URL); + return false; + } + + return true; + } +} diff --git a/app/code/core/Mage/Core/Model/Variable.php b/app/code/core/Mage/Core/Model/Variable.php index 1c9587bf..56f163a9 100644 --- a/app/code/core/Mage/Core/Model/Variable.php +++ b/app/code/core/Mage/Core/Model/Variable.php @@ -20,13 +20,20 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Custom variable model * + * @method Mage_Core_Model_Resource_Variable _getResource() + * @method Mage_Core_Model_Resource_Variable getResource() + * @method string getCode() + * @method Mage_Core_Model_Variable setCode(string $value) + * @method string getName() + * @method Mage_Core_Model_Variable setName(string $value) + * * @category Mage * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> diff --git a/app/code/core/Mage/Core/Model/Variable/Config.php b/app/code/core/Mage/Core/Model/Variable/Config.php index 7b358bb0..4df1d81e 100644 --- a/app/code/core/Mage/Core/Model/Variable/Config.php +++ b/app/code/core/Mage/Core/Model/Variable/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Variable/Observer.php b/app/code/core/Mage/Core/Model/Variable/Observer.php index 8e5d663f..a08b4c0c 100644 --- a/app/code/core/Mage/Core/Model/Variable/Observer.php +++ b/app/code/core/Mage/Core/Model/Variable/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/Model/Website.php b/app/code/core/Mage/Core/Model/Website.php index b1cb54d6..7d91cf7a 100644 --- a/app/code/core/Mage/Core/Model/Website.php +++ b/app/code/core/Mage/Core/Model/Website.php @@ -20,15 +20,26 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Core Website model * - * @category Mage - * @package Mage_Core + * @method Mage_Core_Model_Resource_Website _getResource() + * @method Mage_Core_Model_Resource_Website getResource() + * @method Mage_Core_Model_Website setCode(string $value) + * @method string getName() + * @method Mage_Core_Model_Website setName(string $value) + * @method int getSortOrder() + * @method Mage_Core_Model_Website setSortOrder(int $value) + * @method Mage_Core_Model_Website setDefaultGroupId(int $value) + * @method int getIsDefault() + * @method Mage_Core_Model_Website setIsDefault(int $value) + * + * @category Mage + * @package Mage_Core * @author Magento Core Team <core@magentocommerce.com> */ @@ -307,7 +318,7 @@ public function getGroupsCount() */ public function getDefaultGroup() { - if (!$this->getDefaultGroupId()) { + if (!$this->hasDefaultGroupId()) { return false; } if (is_null($this->_groups)) { @@ -469,6 +480,8 @@ protected function _beforeDelete() */ protected function _afterDelete() { + Mage::app()->clearWebsiteCache($this->getId()); + parent::_afterDelete(); Mage::getConfig()->removeCache(); return $this; @@ -481,7 +494,9 @@ protected function _afterDelete() */ public function getBaseCurrencyCode() { - if ($this->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE) == Mage_Core_Model_Store::PRICE_SCOPE_GLOBAL) { + if ($this->getConfig(Mage_Core_Model_Store::XML_PATH_PRICE_SCOPE) + == Mage_Core_Model_Store::PRICE_SCOPE_GLOBAL + ) { return Mage::app()->getBaseCurrencyCode(); } else { return $this->getConfig(Mage_Directory_Model_Currency::XML_PATH_CURRENCY_BASE); diff --git a/app/code/core/Mage/Core/controllers/AjaxController.php b/app/code/core/Mage/Core/controllers/AjaxController.php index 54dd5d0d..833d728c 100644 --- a/app/code/core/Mage/Core/controllers/AjaxController.php +++ b/app/code/core/Mage/Core/controllers/AjaxController.php @@ -20,25 +20,37 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + +/** + * Frontend ajax controller + * + * @category Mage + * @package Mage_Core + * @author Magento Core Team <core@magentocommerce.com> + */ class Mage_Core_AjaxController extends Mage_Core_Controller_Front_Action { + /** + * Ajax action for inline translation + * + */ public function translateAction () { - if ($translate = $this->getRequest()->getPost('translate')) { - try { - if ($area = $this->getRequest()->getPost('area')) { - Mage::getDesign()->setArea($area); - } - Mage::getModel('core/translate_inline')->processAjaxPost($translate); - echo "{success:true}"; - } - catch (Exception $e) { - echo "{error:true,message:'" . $e->getMessage() . "'}"; - } + $translation = $this->getRequest()->getPost('translate'); + $area = $this->getRequest()->getPost('area'); + + //filtering + /** @var $filter Mage_Core_Model_Input_Filter_MaliciousCode */ + $filter = Mage::getModel('core/input_filter_maliciousCode'); + foreach ($translation as &$item) { + $item['custom'] = $filter->filter($item['custom']); } - exit(); + + $response = Mage::helper('core/translate')->apply($translation, $area); + $this->getResponse()->setBody($response); + $this->setFlag('', self::FLAG_NO_POST_DISPATCH, true); } } diff --git a/app/code/core/Mage/Core/controllers/IndexController.php b/app/code/core/Mage/Core/controllers/IndexController.php index f5abafff..ae0a7de8 100644 --- a/app/code/core/Mage/Core/controllers/IndexController.php +++ b/app/code/core/Mage/Core/controllers/IndexController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/etc/api.xml b/app/code/core/Mage/Core/etc/api.xml new file mode 100644 index 00000000..1d7daf37 --- /dev/null +++ b/app/code/core/Mage/Core/etc/api.xml @@ -0,0 +1,103 @@ +<?xml version="1.0"?> +<!-- +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License (AFL 3.0) + * that is bundled with this package in the file LICENSE_AFL.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/afl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package Mage_Core + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +--> +<config> + <api> + <resources> + <core_store translate="title" module="core"> + <model>core/store_api</model> + <title>Store API + core/store + + + Retrieve store list + items + core/store/list + + + Retrieve store data + core/store/info + + + + + 100 + Requested store view not found. + + + + + core/magento_api + Magento info API + core/magento + + + Get info about current Magento installation + core/magento/info + + + + + + core_store + core_magento + + + + store + magento + + + + + + + + + + Core + 1 + + Store + + Retrieve store data + + + List of stores + + + + Magento info + + Retrieve info about current Magento installation + + + + + + + diff --git a/app/code/core/Mage/Core/etc/config.xml b/app/code/core/Mage/Core/etc/config.xml index 6c0b8428..a36a5742 100644 --- a/app/code/core/Mage/Core/etc/config.xml +++ b/app/code/core/Mage/Core/etc/config.xml @@ -21,43 +21,73 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.8.26 + 1.6.0.2 - - + - core_website
    - core_store_group
    - core_store
    - core_config_field
    - core_config_data
    - core_email_template
    - core_variable
    - core_variable_value
    - core_translate
    - core_session
    - core_layout_update
    - core_layout_link
    - core_url_rewrite
    - core_url_rewrite_tag
    - core_convert_profile
    - core_convert_history
    - design_change
    - core_flag
    + + core_website
    +
    + + core_store_group
    +
    + + core_store
    +
    + + core_config_field
    +
    + + core_config_data
    +
    + + core_email_template
    +
    + + core_variable
    +
    + + core_variable_value
    +
    + + core_translate
    +
    + + core_session
    +
    + + core_layout_update
    +
    + + core_layout_link
    +
    + + core_url_rewrite
    +
    + + core_url_rewrite_tag
    +
    + + design_change
    +
    + + core_flag
    +
    + core_file_storage
    + core_directory_storage
    -
    +
    - Mage_Core_Block @@ -69,7 +99,7 @@ @@ -104,8 +134,7 @@ Shockwave Flash - Adobe Flash Player 9 - Adobe Flash Player 10 + @@ -137,13 +166,13 @@ - - - - core.xml - - - + + + + core.xml + + + @@ -164,6 +193,22 @@ + + + + core/observer + addSynchronizeNotification + + + + + + + core/observer + addSynchronizeNotification + + + @@ -177,7 +222,6 @@ - @@ -192,10 +236,9 @@ 5 - - + 0 @@ -203,6 +246,9 @@ 0 0 + + + 0 @@ -211,49 +257,57 @@ 0 - 0 - 0 localhost 25 + + 0 + default_setup + 3600 + + css + css_secure + js + + - custom1@example.com Custom 1 - custom2@example.com Custom 2 - owner@example.com Owner - sales@example.com Sales - support@example.com CustomerSupport - - - adminMage_Core_Controller_Varien_Router_Admin - frontendMage_Core_Controller_Varien_Router_Standard + + + admin + Mage_Core_Controller_Varien_Router_Admin + + + frontend + Mage_Core_Controller_Varien_Router_Standard + 0 @@ -262,7 +316,6 @@ 0 - {{base_url}} {{unsecure_base_url}} @@ -271,7 +324,6 @@ {{unsecure_base_url}}skin/ {{unsecure_base_url}}media/ - {{base_url}} {{secure_base_url}} @@ -281,13 +333,14 @@ {{secure_base_url}}media/ 0 0 + SSL_OFFLOADED - 3600 1 + 0 + 31536000 - 0 0 @@ -295,42 +348,92 @@ 0 1 - 1 1 - - dashboard 0 - + 1 - + + AT,BE,BG,CY,CZ,DK,EE,FI,FR,DE,GR,HU,IE,IT,LV,LT,LU,MT,NL,PL,PT,RO,SK,SI,ES,SE,GB + 0 0,6 + + + + php + + htaccess + + jsp + + pl + + py + + asp + + sh + + cgi + + htm + html + phtml + shtml + + + + + + + /app/*/* + + + - + - adminMage_Core_Controller_Varien_Router_Admin - frontendMage_Core_Controller_Varien_Router_Standard + + admin + Mage_Core_Controller_Varien_Router_Admin + + + frontend + Mage_Core_Controller_Varien_Router_Standard + + + + + + 30 2 * * * + + + core/observer::cleanCache + + + +
    diff --git a/app/code/core/Mage/Core/etc/jstranslator.xml b/app/code/core/Mage/Core/etc/jstranslator.xml new file mode 100644 index 00000000..4a552ea7 --- /dev/null +++ b/app/code/core/Mage/Core/etc/jstranslator.xml @@ -0,0 +1,205 @@ + + + + + + HTML tags are not allowed + + + Please select an option. + + + This is a required field. + + + Please enter a valid number in this field. + + + The value is not within the specified range. + + + Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas. + + + The value is not within the specified range. + + + Please use letters only (a-z or A-Z) in this field. + + + Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter. + + + Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed. + + + Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field. + + + Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890. + + + Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890. + + + Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890. + + + Please enter a valid date. + + + Please enter a valid email address. For example johndoe@domain.com. + + + Please use only visible characters and spaces. + + + Please enter 6 or more characters. Leading or trailing spaces will be ignored. + + + Please enter 7 or more characters. Password should contain both numeric and alphabetic characters. + + + Please make sure your passwords match. + + + Please enter a valid URL. Protocol is required (http://, https:// or ftp://) + + + Please enter a valid URL. For example http://www.example.com or www.example.com + + + Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page". + + + Please enter a valid XML-identifier. For example something_1, block5, id-4. + + + Please enter a valid social security number. For example 123-45-6789. + + + Please enter a valid zip code. For example 90602 or 90602-1234. + + + Please enter a valid zip code. + + + Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006. + + + Please enter a valid $ amount. For example $100.00. + + + Please select one of the above options. + + + Please select one of the options. + + + Please enter a valid number in this field. + + + Please select State/Province. + + + Please enter 6 or more characters. Leading or trailing spaces will be ignored. + + + Please enter a number greater than 0 in this field. + + + Please enter a number 0 or greater in this field. + + + Please enter a valid credit card number. + + + Credit card number does not match credit card type. + + + Card type does not match credit card number. + + + Incorrect credit card expiration date. + + + Please enter a valid credit card verification number. + + + Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter. + + + Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%. + + + Text length does not satisfy specified text range. + + + Please enter a number lower than 100. + + + Please select a file + + + Please enter issue number or start date for switch/solo card type. + + + + + + Please wait, loading... + + + + + + This date is a required value. + + + Please enter a valid day (1-%d). + + + Please enter a valid month (1-12). + + + Please enter a valid year (1900-%d). + + + Please enter a valid full date + + + Please enter a valid date between %s and %s + + + Please enter a valid date equal to or greater than %s + + + Please enter a valid date less than or equal to %s + + + diff --git a/app/code/core/Mage/Core/etc/system.xml b/app/code/core/Mage/Core/etc/system.xml index 4e28a1ba..37b31d43 100644 --- a/app/code/core/Mage/Core/etc/system.xml +++ b/app/code/core/Mage/Core/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -120,8 +120,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -150,8 +150,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -180,8 +180,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -210,8 +210,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -240,8 +240,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 2 1 1 @@ -291,7 +291,7 @@ adminhtml/system_config_form_field_regexceptions - adminhtml/system_config_backend_serialized_array + adminhtml/system_config_backend_design_exception 2 1 1 @@ -433,6 +433,35 @@ + + + text + 510 + 1 + 1 + 1 + + + + Allowed file types: jpg, jpeg, gif, png + image + adminhtml/system_config_backend_email_logo + email/logo + 10 + 1 + 1 + 1 + + + + text + 20 + 1 + 1 + 1 + + + @@ -500,6 +529,26 @@ + text @@ -512,6 +561,7 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_translate 10 1 1 @@ -521,11 +571,12 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_translate 20 1 0 0 - Translate cache should be disabled for both frontend and admin inline translations. + Translate, blocks and other output caches should be disabled for both frontend and admin inline translations. @@ -549,6 +600,7 @@ text + adminhtml/system_config_backend_filename 2 1 1 @@ -558,6 +610,7 @@ text + adminhtml/system_config_backend_filename 3 1 1 @@ -583,15 +636,6 @@ 1 1 - - - select - adminhtml/system_config_source_yesno - 50 - 1 - 1 - 1 - @@ -603,14 +647,13 @@ 1 - + select adminhtml/system_config_source_yesno 10 1 1 1 - Experimental. Turn this feature off if there are troubles with relative urls inside css-files. @@ -652,6 +695,15 @@ 1 1 + + + multiselect + adminhtml/system_config_source_country + 30 + 1 + 0 + 0 + @@ -666,6 +718,7 @@ select adminhtml/system_config_source_locale_timezone + adminhtml/system_config_backend_locale_timezone 1 1 1 @@ -725,6 +778,25 @@ 1 1 + + + select + adminhtml/system_config_source_country + 25 + 1 + 1 + 0 + 1 + + + + text + 27 + 1 + 1 + 0 + 1 +
    textarea @@ -821,8 +893,8 @@ text - adminhtml/system_config_backend_email_address validate-email + adminhtml/system_config_backend_email_address 80 1 0 @@ -831,6 +903,55 @@ + + + text + 900 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_storage_media_storage + 100 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_storage_media_database + adminhtml/system_config_backend_storage_media_database + 200 + 1 + 0 + 0 + 1 + + + button + adminhtml/system_config_system_storage_media_synchronize + 300 + 1 + 0 + 0 + After selecting a new media storage location, press the Synchronize button + to transfer all media to that location. Media will not be available in the new + location until the synchronization process is complete. + + + + text + 400 + 1 + 0 + 0 + + + @@ -854,20 +975,31 @@ select adminhtml/system_config_source_email_template - 1 + 10 1 - 1 - 1 + 0 + 0 select adminhtml/system_config_source_email_identity - 2 + 20 1 - 1 - 1 + 0 + 0 + + + Please enter a number 1 or greater in this field. + text + required-entry validate-digits validate-digits-range digits-range-1- + adminhtml/system_config_backend_admin_password_link_expirationperiod + 30 + 1 + 0 + 0 + @@ -911,12 +1043,35 @@ text adminhtml/system_config_backend_admin_custom + 1 2 1 0 0 Make sure that base URL ends with '/' (slash), e.g. http://yourdomain/magento/ + + + select + adminhtml/system_config_source_yesno + adminhtml/system_config_backend_admin_usecustompath + 3 + 1 + 0 + 0 + + + + text + required-entry validate-alphanum + adminhtml/system_config_backend_admin_custompath + 1 + 4 + 1 + 0 + 0 + You will have to log in after you save your custom admin path. + @@ -949,6 +1104,7 @@ Values less than 60 are ignored. Note that changes will apply after logout. + validate-digits 3 1 0 @@ -956,6 +1112,24 @@ + + + 40 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 0 + 0 + + + @@ -975,7 +1149,7 @@ 1 1 - + select adminhtml/system_config_source_yesno @@ -984,15 +1158,17 @@ 1 0 0 + Warning! When using Store Code in URLs, in some cases system may not work properly if URLs without Store Codes are specified in the third party services (e.g. PayPal etc.).]]> - + select - adminhtml/system_config_source_yesno + adminhtml/system_config_source_web_redirect 20 1 0 0 + I.e. redirect from http://example.com/store/ to http://www.example.com/store/ @@ -1059,7 +1235,7 @@ 1 1 - + text adminhtml/system_config_backend_baseurl @@ -1067,6 +1243,7 @@ 1 1 1 + <strong style="color:red">Warning!</strong> When using CDN, in some cases JavaScript may not run properly if CDN is not in your subdomain @@ -1116,7 +1293,7 @@ 1 1 - + text adminhtml/system_config_backend_baseurl @@ -1124,11 +1301,13 @@ 1 1 1 + <strong style="color:red">Warning!</strong> When using CDN, in some cases JavaScript may not run properly if CDN is not in your subdomain select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_secure 60 1 1 @@ -1138,11 +1317,20 @@ select adminhtml/system_config_source_yesno + adminhtml/system_config_backend_secure 65 1 0 0 + + + text + 75 + 1 + 0 + 0 + @@ -1220,6 +1408,16 @@ 1 1 + + + select + adminhtml/system_config_source_yesno + adminhtml/system_config_backend_cookie + 50 + 1 + 1 + 0 + @@ -1227,7 +1425,7 @@ text 60 1 - 0 + 1 0 diff --git a/app/code/core/Mage/Core/etc/wsdl.xml b/app/code/core/Mage/Core/etc/wsdl.xml new file mode 100644 index 00000000..8f3116b7 --- /dev/null +++ b/app/code/core/Mage/Core/etc/wsdl.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of stores + + + + + Store view info + + + + + Info about current Magento installation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/core/Mage/Core/etc/wsi.xml b/app/code/core/Mage/Core/etc/wsi.xml new file mode 100644 index 00000000..7e2b3b71 --- /dev/null +++ b/app/code/core/Mage/Core/etc/wsi.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of stores + + + + + Store view info + + + + + Info about current Magento installation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/core/Mage/Core/functions.php b/app/code/core/Mage/Core/functions.php index cd8ef85e..fbd0acc6 100644 --- a/app/code/core/Mage/Core/functions.php +++ b/app/code/core/Mage/Core/functions.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/install-1.6.0.0.php b/app/code/core/Mage/Core/sql/core_setup/install-1.6.0.0.php new file mode 100644 index 00000000..e34461cb --- /dev/null +++ b/app/code/core/Mage/Core/sql/core_setup/install-1.6.0.0.php @@ -0,0 +1,666 @@ +startSetup(); + +/** + * Create table 'core/resource' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/resource')) + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => false, + 'primary' => true, + ), 'Resource Code') + ->addColumn('version', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + ), 'Resource Version') + ->addColumn('data_version', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + ), 'Data Version') + ->setComment('Resources'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/website' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/website')) + ->addColumn('website_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Website Id') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'Code') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Website Name') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addColumn('default_group_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Default Group Id') + ->addColumn('is_default', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'default' => '0', + ), 'Defines Is Website Default') + ->addIndex($installer->getIdxName('core/website', array('code'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/website', array('sort_order')), + array('sort_order')) + ->addIndex($installer->getIdxName('core/website', array('default_group_id')), + array('default_group_id')) + ->setComment('Websites'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/store_group' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/store_group')) + ->addColumn('group_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Group Id') + ->addColumn('website_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Website Id') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Store Group Name') + ->addColumn('root_category_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Root Category Id') + ->addColumn('default_store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Default Store Id') + ->addIndex($installer->getIdxName('core/store_group', array('website_id')), + array('website_id')) + ->addIndex($installer->getIdxName('core/store_group', array('default_store_id')), + array('default_store_id')) + ->addForeignKey($installer->getFkName('core/store_group', 'website_id', 'core/website', 'website_id'), + 'website_id', $installer->getTable('core/website'), 'website_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Store Groups'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/store' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/store')) + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Store Id') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'Code') + ->addColumn('website_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Website Id') + ->addColumn('group_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Group Id') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Store Name') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Sort Order') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Activity') + ->addIndex($installer->getIdxName('core/store', array('code'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/store', array('website_id')), + array('website_id')) + ->addIndex($installer->getIdxName('core/store', array('is_active', 'sort_order')), + array('is_active', 'sort_order')) + ->addIndex($installer->getIdxName('core/store', array('group_id')), + array('group_id')) + ->addForeignKey($installer->getFkName('core/store', 'group_id', 'core/store_group', 'group_id'), + 'group_id', $installer->getTable('core/store_group'), 'group_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('core/store', 'website_id', 'core/website', 'website_id'), + 'website_id', $installer->getTable('core/website'), 'website_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Stores'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/config_data' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/config_data')) + ->addColumn('config_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Config Id') + ->addColumn('scope', Varien_Db_Ddl_Table::TYPE_TEXT, 8, array( + 'nullable' => false, + 'default' => 'default', + ), 'Config Scope') + ->addColumn('scope_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Config Scope Id') + ->addColumn('path', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + 'default' => 'general', + ), 'Config Path') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array(), 'Config Value') + ->addIndex($installer->getIdxName('core/config_data', array('scope', 'scope_id', 'path'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('scope', 'scope_id', 'path'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Config Data'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/email_template' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/email_template')) + ->addColumn('template_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Template Id') + ->addColumn('template_code', Varien_Db_Ddl_Table::TYPE_TEXT, 150, array( + 'nullable' => false + ), 'Template Name') + ->addColumn('template_text', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => false + ), 'Template Content') + ->addColumn('template_styles', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Templste Styles') + ->addColumn('template_type', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + ), 'Template Type') + ->addColumn('template_subject', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + 'nullable' => false, + ), 'Template Subject') + ->addColumn('template_sender_name', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + ), 'Template Sender Name') + ->addColumn('template_sender_email', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + ), 'Template Sender Email') + ->addColumn('added_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Date of Template Creation') + ->addColumn('modified_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Date of Template Modification') + ->addColumn('orig_template_code', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + ), 'Original Template Code') + ->addColumn('orig_template_variables', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Original Template Variables') + ->addIndex($installer->getIdxName('core/email_template', array('template_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('template_code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/email_template', array('added_at')), + array('added_at')) + ->addIndex($installer->getIdxName('core/email_template', array('modified_at')), + array('modified_at')) + ->setComment('Email Templates'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/layout_update' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/layout_update')) + ->addColumn('layout_update_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Layout Update Id') + ->addColumn('handle', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Handle') + ->addColumn('xml', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Xml') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex($installer->getIdxName('core/layout_update', array('handle')), + array('handle')) + ->setComment('Layout Updates'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/layout_link' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/layout_link')) + ->addColumn('layout_link_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Link Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('area', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Area') + ->addColumn('package', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Package') + ->addColumn('theme', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Theme') + ->addColumn('layout_update_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Layout Update Id') + ->addIndex($installer->getIdxName('core/layout_link', array('store_id', 'package', 'theme', 'layout_update_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('store_id', 'package', 'theme', 'layout_update_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/layout_link', array('layout_update_id')), + array('layout_update_id')) + ->addForeignKey($installer->getFkName('core/layout_link', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('core/layout_link', 'layout_update_id', 'core/layout_update', 'layout_update_id'), + 'layout_update_id', $installer->getTable('core/layout_update'), 'layout_update_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE + ) + ->setComment('Layout Link'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/session' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/session')) + ->addColumn('session_id', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + 'primary' => true, + ), 'Session Id') + ->addColumn('session_expires', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Date of Session Expiration') + ->addColumn('session_data', Varien_Db_Ddl_Table::TYPE_BLOB, '2M', array( + 'nullable' => false, + ), 'Session Data') + ->setComment('Database Sessions Storage'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/translate' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/translate')) + ->addColumn('key_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Key Id of Translation') + ->addColumn('string', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + 'default' => 'Translate String', + ), 'Translation String') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('translate', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Translate') + ->addColumn('locale', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + 'nullable' => false, + 'default' => 'en_US', + ), 'Locale') + ->addIndex($installer->getIdxName('core/translate', array('store_id', 'locale', 'string'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('store_id', 'locale', 'string'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/translate', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('core/translate', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Translations'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/url_rewrite' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/url_rewrite')) + ->addColumn('url_rewrite_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Rewrite Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('id_path', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Id Path') + ->addColumn('request_path', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Request Path') + ->addColumn('target_path', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Target Path') + ->addColumn('is_system', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'default' => '1', + ), 'Defines is Rewrite System') + ->addColumn('options', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + ), 'Options') + ->addColumn('description', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Deascription') + ->addIndex($installer->getIdxName('core/url_rewrite', array('request_path', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('request_path', 'store_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/url_rewrite', array('id_path', 'is_system', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('id_path', 'is_system', 'store_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/url_rewrite', array('target_path', 'store_id')), + array('target_path', 'store_id')) + ->addIndex($installer->getIdxName('core/url_rewrite', array('id_path')), + array('id_path')) + ->addIndex($installer->getIdxName('core/url_rewrite', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('core/url_rewrite', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Url Rewrites'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/design_change' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/design_change')) + ->addColumn('design_change_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Design Change Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('design', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Design') + ->addColumn('date_from', Varien_Db_Ddl_Table::TYPE_DATE, null, array( + ), 'First Date of Design Activity') + ->addColumn('date_to', Varien_Db_Ddl_Table::TYPE_DATE, null, array( + ), 'Last Date of Design Activity') + ->addIndex($installer->getIdxName('core/design_change', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('core/design_change', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Design Changes'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/variable' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/variable')) + ->addColumn('variable_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Variable Id') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Variable Code') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Variable Name') + ->addIndex($installer->getIdxName('core/variable', array('code'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Variables'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/variable_value' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/variable_value')) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Variable Value Id') + ->addColumn('variable_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Variable Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('plain_value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Plain Text Value') + ->addColumn('html_value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Html Value') + ->addIndex($installer->getIdxName('core/variable_value', array('variable_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('variable_id', 'store_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('core/variable_value', array('variable_id')), + array('variable_id')) + ->addIndex($installer->getIdxName('core/variable_value', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('core/variable_value', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('core/variable_value', 'variable_id', 'core/variable', 'variable_id'), + 'variable_id', $installer->getTable('core/variable'), 'variable_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Variable Value'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/cache' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/cache')) + ->addColumn('id', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + 'nullable' => false, + 'primary' => true, + ), 'Cache Id') + ->addColumn('data', Varien_Db_Ddl_Table::TYPE_BLOB, '2M', array( + ), 'Cache Data') + ->addColumn('create_time', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + ), 'Cache Creation Time') + ->addColumn('update_time', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + ), 'Time of Cache Updating') + ->addColumn('expire_time', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + ), 'Cache Expiration Time') + ->addIndex($installer->getIdxName('core/cache', array('expire_time')), + array('expire_time')) + ->setComment('Caches'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/cache_tag' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/cache_tag')) + ->addColumn('tag', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'nullable' => false, + 'primary' => true, + ), 'Tag') + ->addColumn('cache_id', Varien_Db_Ddl_Table::TYPE_TEXT, 200, array( + 'nullable' => false, + 'primary' => true, + ), 'Cache Id') + ->addIndex($installer->getIdxName('core/cache_tag', array('cache_id')), + array('cache_id')) + ->setComment('Tag Caches'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/cache_option' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/cache_option')) + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + 'nullable' => false, + 'primary' => true, + ), 'Code') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + ), 'Value') + ->setComment('Cache Options'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'core/flag' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('core/flag')) + ->addColumn('flag_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Flag Id') + ->addColumn('flag_code', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Flag Code') + ->addColumn('state', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Flag State') + ->addColumn('flag_data', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Flag Data') + ->addColumn('last_update', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT_UPDATE, + ), 'Date of Last Flag Update') + ->addIndex($installer->getIdxName('core/flag', array('last_update')), + array('last_update')) + ->setComment('Flag'); +$installer->getConnection()->createTable($table); + + +/** + * Insert core websites + */ +$installer->getConnection()->insertForce($installer->getTable('core/website'), array( + 'website_id' => 0, + 'code' => 'admin', + 'name' => 'Admin', + 'sort_order' => 0, + 'default_group_id' => 0, + 'is_default' => 0, +)); +$installer->getConnection()->insertForce($installer->getTable('core/website'), array( + 'website_id' => 1, + 'code' => 'base', + 'name' => 'Main Website', + 'sort_order' => 0, + 'default_group_id' => 1, + 'is_default' => 1, +)); + +/** + * Insert core store groups + */ +$installer->getConnection()->insertForce($installer->getTable('core/store_group'), array( + 'group_id' => 0, + 'website_id' => 0, + 'name' => 'Default', + 'root_category_id' => 0, + 'default_store_id' => 0 +)); +$installer->getConnection()->insertForce($installer->getTable('core/store_group'), array( + 'group_id' => 1, + 'website_id' => 1, + 'name' => 'Main Website Store', + 'root_category_id' => 2, + 'default_store_id' => 1 +)); + +/** + * Insert core stores + */ +$installer->getConnection()->insertForce($installer->getTable('core/store'), array( + 'store_id' => 0, + 'code' => 'admin', + 'website_id' => 0, + 'group_id' => 0, + 'name' => 'Admin', + 'sort_order' => 0, + 'is_active' => 1, +)); +$installer->getConnection()->insertForce($installer->getTable('core/store'), array( + 'store_id' => 1, + 'code' => 'default', + 'website_id' => 1, + 'group_id' => 1, + 'name' => 'Default Store View', + 'sort_order' => 0, + 'is_active' => 1, +)); + +$installer->endSetup(); + diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.7.0.php index 81de8a32..010bec10 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.8.0.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.8.0.php index 522a5a25..a8886121 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.8.0.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-install-0.8.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.6.26-0.7.0.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.6.26-0.7.0.php index 8439c0fe..03db81f3 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.6.26-0.7.0.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.6.26-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.1-0.7.2.php index 8802162f..1d2eff78 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.2-0.7.3.php index 6c616145..2269b5b9 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.2-0.7.3.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.3-0.7.4.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.3-0.7.4.php index 3deb8320..4c9b6036 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.3-0.7.4.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.3-0.7.4.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.4-0.7.5.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.4-0.7.5.php index b2d1c13d..87a52158 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.4-0.7.5.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.4-0.7.5.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.5-0.7.6.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.5-0.7.6.php index 90877f13..d60474e8 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.5-0.7.6.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.5-0.7.6.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.6-0.7.7.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.6-0.7.7.php index 271ef076..26fef510 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.6-0.7.7.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.6-0.7.7.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.7-0.7.8.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.7-0.7.8.php index 91333873..04282d43 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.7-0.7.8.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.7-0.7.8.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.8-0.7.9.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.8-0.7.9.php index 395d2351..c2d3209e 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.8-0.7.9.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.0-0.8.1.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.0-0.8.1.php index 86a27e7b..ad5e42ca 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.0-0.8.1.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.0-0.8.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.1-0.8.2.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.1-0.8.2.php index 366345f2..207f623c 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.1-0.8.2.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.1-0.8.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.10-0.8.11.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.10-0.8.11.php index baea4dea..6021b0c7 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.10-0.8.11.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.10-0.8.11.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.11-0.8.12.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.11-0.8.12.php index 6b643c67..3ec4611d 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.11-0.8.12.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.11-0.8.12.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.12-0.8.13.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.12-0.8.13.php index 731d607b..12d2bd98 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.12-0.8.13.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.12-0.8.13.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.13-0.8.14.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.13-0.8.14.php index 9a860428..957477ae 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.13-0.8.14.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.13-0.8.14.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.14-0.8.15.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.14-0.8.15.php index 58a4d1d9..74498156 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.14-0.8.15.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.14-0.8.15.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.15-0.8.16.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.15-0.8.16.php index a5958bbd..244068ae 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.15-0.8.16.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.15-0.8.16.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.16-0.8.17.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.16-0.8.17.php index 50de906f..afda2a0b 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.16-0.8.17.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.16-0.8.17.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.17-0.8.18.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.17-0.8.18.php index 339eb64d..374b70b9 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.17-0.8.18.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.17-0.8.18.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.18-0.8.19.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.18-0.8.19.php index 32f0f552..6f8adf92 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.18-0.8.19.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.18-0.8.19.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.19-0.8.20.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.19-0.8.20.php index f2abccb5..b41c1cfc 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.19-0.8.20.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.19-0.8.20.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.2-0.8.3.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.2-0.8.3.php index 7dfafaf4..eebb5a8b 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.2-0.8.3.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.2-0.8.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.20-0.8.21.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.20-0.8.21.php index c0602333..028213f9 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.20-0.8.21.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.20-0.8.21.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,4 +28,11 @@ $installer = $this; $installer->getConnection()->addColumn($installer->getTable('core/resource'), 'data_version', 'varchar(50)'); - +/* + * Update core_resource table to prevent running data upgrade install scripts, + * New 'data_version' column will contain value from 'version' column + */ +$installer->getConnection()->update( + $this->getTable('core/resource'), + array('data_version' => new Zend_Db_Expr('version')) +); diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.21-0.8.22.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.21-0.8.22.php index 972fb443..0ac7b520 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.21-0.8.22.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.21-0.8.22.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.22-0.8.23.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.22-0.8.23.php index b995c6b8..d7e05ef5 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.22-0.8.23.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.22-0.8.23.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.23-0.8.24.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.23-0.8.24.php index 22ef4719..9f919168 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.23-0.8.24.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.23-0.8.24.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.24-0.8.25.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.24-0.8.25.php index 275692b2..7af090a2 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.24-0.8.25.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.24-0.8.25.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,7 +30,7 @@ $installer->startSetup(); $installer->getConnection()->modifyColumn( - $this->getTable('core_flag'), 'flag_id', 'INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT' + $this->getTable('core/flag'), 'flag_id', 'INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT' ); $installer->endSetup(); diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.25-0.8.26.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.25-0.8.26.php index bb29dd99..e9c25cdd 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.25-0.8.26.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.25-0.8.26.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.26-0.8.27.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.26-0.8.27.php new file mode 100644 index 00000000..72d5d9e6 --- /dev/null +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.26-0.8.27.php @@ -0,0 +1,29 @@ +getConnection()->dropForeignKey($installer->getTable('core/cache_tag'), 'FK_CORE_CACHE_TAG'); diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.27-0.8.28.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.27-0.8.28.php new file mode 100644 index 00000000..7b7869ae --- /dev/null +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.27-0.8.28.php @@ -0,0 +1,35 @@ +getTable('core/cache_tag'); +$installer->getConnection()->truncate($tagsTableName); +$installer->getConnection()->modifyColumn($tagsTableName, 'tag', 'VARCHAR(100)'); +$installer->getConnection()->modifyColumn($tagsTableName, 'cache_id', 'VARCHAR(200)'); +$installer->getConnection()->addKey($tagsTableName, '', array('tag', 'cache_id'), 'PRIMARY'); +$installer->getConnection()->dropKey($tagsTableName, 'IDX_TAG'); diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.3-0.8.4.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.3-0.8.4.php index 87feb237..a5edcbcd 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.3-0.8.4.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.3-0.8.4.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.4-0.8.5.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.4-0.8.5.php index 24a5b295..e5dc2be1 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.4-0.8.5.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.4-0.8.5.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.5-0.8.6.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.5-0.8.6.php index 4bd94035..7de7d3d0 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.5-0.8.6.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.5-0.8.6.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.6-0.8.7.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.6-0.8.7.php index b1fa747b..e30a934d 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.6-0.8.7.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.6-0.8.7.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.7-0.8.8.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.7-0.8.8.php index 43296cc9..5586993f 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.7-0.8.8.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.7-0.8.8.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.8-0.8.9.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.8-0.8.9.php index 263c2cb2..b98d3f21 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.8-0.8.9.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.8-0.8.9.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.9-0.8.10.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.9-0.8.10.php index b242be65..dd578690 100644 --- a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.9-0.8.10.php +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-0.8.9-0.8.10.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Core - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..72dcfd19 --- /dev/null +++ b/app/code/core/Mage/Core/sql/core_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,1460 @@ +startSetup(); + +$usedDatabaseStorage = $installer->getConnection()->isTableExists( + $installer->getTable('core/file_storage') +); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/layout_link'), + 'FK_CORE_LAYOUT_LINK_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/layout_link'), + 'FK_CORE_LAYOUT_LINK_UPDATE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/store'), + 'FK_STORE_GROUP_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/store'), + 'FK_STORE_WEBSITE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/session'), + 'FK_SESSION_WEBSITE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/store_group'), + 'FK_STORE_GROUP_WEBSITE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/translate'), + 'FK_CORE_TRANSLATE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/url_rewrite'), + 'CORE_URL_REWRITE_IBFK_1' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_CATEGORY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_PRODUCT' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/variable_value'), + 'FK_CORE_VARIABLE_VALUE_STORE_ID' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/variable_value'), + 'FK_CORE_VARIABLE_VALUE_VARIABLE_ID' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/design_change'), + 'FK_DESIGN_CHANGE_STORE' +); + +if ($usedDatabaseStorage) { + $installer->getConnection()->dropForeignKey( + $installer->getTable('core/file_storage'), + 'FK_FILE_DIRECTORY' + ); + + $installer->getConnection()->dropForeignKey( + $installer->getTable('core/directory_storage'), + 'FK_DIRECTORY_PARENT_ID' + ); +} + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('core/cache'), + 'IDX_EXPIRE_TIME' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/cache_tag'), + 'IDX_CACHE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/config_data'), + 'CONFIG_SCOPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/email_template'), + 'TEMPLATE_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/email_template'), + 'ADDED_AT' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/email_template'), + 'MODIFIED_AT' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/flag'), + 'LAST_UPDATE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/layout_link'), + 'STORE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/layout_link'), + 'FK_CORE_LAYOUT_LINK_UPDATE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/layout_update'), + 'HANDLE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store'), + 'CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store'), + 'FK_STORE_WEBSITE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store'), + 'IS_ACTIVE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store'), + 'FK_STORE_GROUP' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store_group'), + 'FK_STORE_GROUP_WEBSITE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/store_group'), + 'DEFAULT_STORE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/translate'), + 'IDX_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/translate'), + 'FK_CORE_TRANSLATE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/session'), + 'FK_SESSION_WEBSITE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'UNQ_REQUEST_PATH' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'UNQ_PATH' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_CATEGORY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'FK_CORE_URL_REWRITE_PRODUCT' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'IDX_ID_PATH' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'IDX_CATEGORY_REWRITE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/url_rewrite'), + 'IDX_TARGET_PATH' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/variable'), + 'IDX_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/variable_value'), + 'IDX_VARIABLE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/variable_value'), + 'IDX_VARIABLE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/variable_value'), + 'IDX_STORE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/website'), + 'CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/website'), + 'SORT_ORDER' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/website'), + 'DEFAULT_GROUP_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('core/design_change'), + 'FK_DESIGN_CHANGE_STORE' +); + +if ($usedDatabaseStorage) { + $installer->getConnection()->dropIndex( + $installer->getTable('core/file_storage'), + 'IDX_FILENAME' + ); + + $installer->getConnection()->dropIndex( + $installer->getTable('core/file_storage'), + 'directory_id' + ); + + $installer->getConnection()->dropIndex( + $installer->getTable('core/directory_storage'), + 'IDX_DIRECTORY_PATH' + ); + + $installer->getConnection()->dropIndex( + $installer->getTable('core/directory_storage'), + 'parent_id' + ); +} + + +/* + * Change columns + */ +$tables = array( + $installer->getTable('core/config_data') => array( + 'columns' => array( + 'config_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Config Id' + ), + 'scope' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 8, + 'nullable' => false, + 'default' => 'default', + 'comment' => 'Config Scope' + ), + 'scope_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Config Scope Id' + ), + 'path' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'default' => 'general', + 'comment' => 'Config Path' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Config Value' + ) + ), + 'comment' => 'Config Data' + ), + $installer->getTable('core/website') => array( + 'columns' => array( + 'website_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Website Id' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'Code' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Website Name' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ), + 'default_group_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Default Group Id' + ), + 'is_default' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'default' => '0', + 'comment' => 'Defines Is Website Default' + ) + ), + 'comment' => 'Websites' + ), + $installer->getTable('core/store') => array( + 'columns' => array( + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Store Id' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'Code' + ), + 'website_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Website Id' + ), + 'group_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Group Id' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Store Name' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Sort Order' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Activity' + ) + ), + 'comment' => 'Stores' + ), + $installer->getTable('core/resource') => array( + 'columns' => array( + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Resource Code' + ), + 'version' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'comment' => 'Resource Version' + ), + 'data_version' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'comment' => 'Data Version' + ) + ), + 'comment' => 'Resources' + ), + $installer->getTable('core/cache') => array( + 'columns' => array( + 'id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Cache Id' + ), + 'data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BLOB, + 'length' => '2M', + 'comment' => 'Cache Data' + ), + 'create_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'comment' => 'Cache Creation Time' + ), + 'update_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'comment' => 'Time of Cache Updating' + ), + 'expire_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'comment' => 'Cache Expiration Time' + ) + ), + 'comment' => 'Caches' + ), + $installer->getTable('core/cache_tag') => array( + 'columns' => array( + 'tag' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Tag' + ), + 'cache_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Cache Id' + ) + ), + 'comment' => 'Tag Caches' + ), + $installer->getTable('core/cache_option') => array( + 'columns' => array( + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Code' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'comment' => 'Value' + ) + ), + 'comment' => 'Cache Options' + ), + $installer->getTable('core/store_group') => array( + 'columns' => array( + 'group_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Group Id' + ), + 'website_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Website Id' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Store Group Name' + ), + 'root_category_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Root Category Id' + ), + 'default_store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Default Store Id' + ) + ), + 'comment' => 'Store Groups' + ), + $installer->getTable('core/email_template') => array( + 'columns' => array( + 'template_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Template Id' + ), + 'template_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 150, + 'nullable' => false, + 'comment' => 'Template Name' + ), + 'template_text' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'nullable' => false, + 'comment' => 'Template Content' + ), + 'template_styles' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Templste Styles' + ), + 'template_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'comment' => 'Template Type' + ), + 'template_subject' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'nullable' => false, + 'comment' => 'Template Subject' + ), + 'template_sender_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'comment' => 'Template Sender Name' + ), + 'template_sender_email' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'comment' => 'Template Sender Email' + ), + 'added_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Date of Template Creation' + ), + 'modified_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Date of Template Modification' + ), + 'orig_template_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 200, + 'comment' => 'Original Template Code' + ), + 'orig_template_variables' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Original Template Variables' + ) + ), + 'comment' => 'Email Templates' + ), + $installer->getTable('core/variable') => array( + 'columns' => array( + 'variable_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Variable Id' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Variable Code' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Variable Name' + ) + ), + 'comment' => 'Variables' + ), + $installer->getTable('core/variable_value') => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Variable Value Id' + ), + 'variable_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Variable Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'plain_value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Plain Text Value' + ), + 'html_value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Html Value' + ) + ), + 'comment' => 'Variable Value' + ), + $installer->getTable('core/translate') => array( + 'columns' => array( + 'key_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Key Id of Translation' + ), + 'string' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'default' => 'Translate String', + 'comment' => 'Translation String' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'translate' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Translate' + ), + 'locale' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'nullable' => false, + 'default' => 'en_US', + 'comment' => 'Locale' + ) + ), + 'comment' => 'Translations' + ), + $installer->getTable('core/session') => array( + 'columns' => array( + 'session_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Session Id' + ), + 'session_expires' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Date of Session Expiration' + ), + 'session_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BLOB, + 'length' => '2M', + 'nullable' => false, + 'comment' => 'Session Data' + ) + ), + 'comment' => 'Database Sessions Storage' + ), + $installer->getTable('core/layout_update') => array( + 'columns' => array( + 'layout_update_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Layout Update Id' + ), + 'handle' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Handle' + ), + 'xml' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Xml' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Layout Updates' + ), + $installer->getTable('core/layout_link') => array( + 'columns' => array( + 'layout_link_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Link Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'area' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Area' + ), + 'package' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Package' + ), + 'theme' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Theme' + ), + 'layout_update_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Layout Update Id' + ) + ), + 'comment' => 'Layout Link' + ), + $installer->getTable('core/url_rewrite') => array( + 'columns' => array( + 'url_rewrite_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Rewrite Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'id_path' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Id Path' + ), + 'request_path' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Request Path' + ), + 'target_path' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Target Path' + ), + 'is_system' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'default' => '1', + 'comment' => 'Defines is Rewrite System' + ), + 'options' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Options' + ), + 'description' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Deascription' + ) + ), + 'comment' => 'Url Rewrites' + ), + $installer->getTable('core/design_change') => array( + 'columns' => array( + 'design_change_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Design Change Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'design' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Design' + ), + 'date_from' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATE, + 'comment' => 'First Date of Design Activity' + ), + 'date_to' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATE, + 'comment' => 'Last Date of Design Activity' + ) + ), + 'comment' => 'Design Changes' + ), + $installer->getTable('core/flag') => array( + 'columns' => array( + 'flag_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Flag Id' + ), + 'flag_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Flag Code' + ), + 'state' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Flag State' + ), + 'flag_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Flag Data' + ), + 'last_update' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT_UPDATE, + 'comment' => 'Date of Last Flag Update' + ) + ), + 'comment' => 'Flag' + ) +); + +if ($usedDatabaseStorage) { + $storageTables = array( + $installer->getTable('core/file_storage') => array( + 'columns' => array( + 'file_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'File Id' + ), + 'content' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_VARBINARY, + 'length' => Varien_Db_Ddl_Table::MAX_VARBINARY_SIZE, + 'nullable' => false, + 'comment' => 'File Content' + ), + 'upload_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT, + 'comment' => 'Upload Timestamp' + ), + 'filename' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'nullable' => false, + 'comment' => 'Filename' + ), + 'directory_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'default' => null, + 'comment' => 'Identifier of Directory where File is Located' + ), + 'directory' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'default' => null, + 'comment' => 'Directory Path' + ) + ), + 'comment' => 'File Storage' + ), + $installer->getTable('core/directory_storage') => array( + 'columns' => array( + 'directory_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Directory Id' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'nullable' => false, + 'comment' => 'Directory Name' + ), + 'path' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'default' => null, + 'comment' => 'Path to the Directory' + ), + 'upload_time' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'default' => Varien_Db_Ddl_Table::TIMESTAMP_INIT, + 'comment' => 'Upload Timestamp' + ), + 'parent_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'default' => null, + 'comment' => 'Parent Directory Id' + ) + ), + 'comment' => 'Directory Storage' + ) + ); + $tables = array_merge($tables, $storageTables); +} + +$installer->getConnection()->modifyTables($tables); + +$installer->getConnection()->dropColumn( + $installer->getTable('core/session'), + 'website_id' +); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('core/variable'), + $installer->getIdxName( + 'core/variable', + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/variable_value'), + $installer->getIdxName( + 'core/variable_value', + array('variable_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('variable_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/variable_value'), + $installer->getIdxName('core/variable_value', array('variable_id')), + array('variable_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/variable_value'), + $installer->getIdxName('core/variable_value', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/cache'), + $installer->getIdxName('core/cache', array('expire_time')), + array('expire_time') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/cache_tag'), + $installer->getIdxName('core/cache_tag', array('cache_id')), + array('cache_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/config_data'), + $installer->getIdxName( + 'core/config_data', + array('scope', 'scope_id', 'path'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('scope', 'scope_id', 'path'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/email_template'), + $installer->getIdxName( + 'core/email_template', + array('template_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('template_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/email_template'), + $installer->getIdxName('core/email_template', array('added_at')), + array('added_at') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/email_template'), + $installer->getIdxName('core/email_template', array('modified_at')), + array('modified_at') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/flag'), + $installer->getIdxName('core/flag', array('last_update')), + array('last_update') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/layout_link'), + $installer->getIdxName( + 'core/layout_link', + array('store_id', 'package', 'theme', 'layout_update_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('store_id', 'package', 'theme', 'layout_update_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/layout_link'), + $installer->getIdxName('core/layout_link', array('layout_update_id')), + array('layout_update_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/layout_update'), + $installer->getIdxName('core/layout_update', array('handle')), + array('handle') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store'), + $installer->getIdxName( + 'core/store', + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store'), + $installer->getIdxName('core/store', array('website_id')), + array('website_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store'), + $installer->getIdxName('core/store', array('is_active', 'sort_order')), + array('is_active', 'sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store'), + $installer->getIdxName('core/store', array('group_id')), + array('group_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store_group'), + $installer->getIdxName('core/store_group', array('website_id')), + array('website_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/store_group'), + $installer->getIdxName('core/store_group', array('default_store_id')), + array('default_store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/translate'), + $installer->getIdxName( + 'core/translate', + array('store_id', 'locale', 'string'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('store_id', 'locale', 'string'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/translate'), + $installer->getIdxName('core/translate', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/url_rewrite'), + $installer->getIdxName( + 'core/url_rewrite', + array('request_path', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('request_path', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/url_rewrite'), + $installer->getIdxName( + 'core/url_rewrite', + array('id_path', 'is_system', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('id_path', 'is_system', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/url_rewrite'), + $installer->getIdxName('core/url_rewrite', array('target_path', 'store_id')), + array('target_path', 'store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/url_rewrite'), + $installer->getIdxName('core/url_rewrite', array('id_path')), + array('id_path') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/url_rewrite'), + $installer->getIdxName('core/url_rewrite', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/website'), + $installer->getIdxName( + 'core/website', + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/website'), + $installer->getIdxName('core/website', array('sort_order')), + array('sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/website'), + $installer->getIdxName('core/website', array('default_group_id')), + array('default_group_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('core/design_change'), + $installer->getIdxName('core/design_change', array('store_id')), + array('store_id') +); + +if ($usedDatabaseStorage) { + $installer->getConnection()->addIndex( + $installer->getTable('core/file_storage'), + $installer->getIdxName( + 'core/file_storage', + array('filename', 'directory_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('filename', 'directory_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ); + + $installer->getConnection()->addIndex( + $installer->getTable('core/file_storage'), + $installer->getIdxName('core/file_storage', array('directory_id')), + array('directory_id') + ); + + $installer->getConnection()->addIndex( + $installer->getTable('core/directory_storage'), + $installer->getIdxName( + 'core/directory_storage', + array('name', 'parent_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('name', 'parent_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ); + + $installer->getConnection()->addIndex( + $installer->getTable('core/directory_storage'), + $installer->getIdxName('core/directory_storage', array('parent_id')), + array('parent_id') + ); +} + + +/** + * Add foreign keys + */ + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/layout_link', 'store_id', 'core/store', 'store_id'), + $installer->getTable('core/layout_link'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/layout_link', 'layout_update_id', 'core/layout_update', 'layout_update_id'), + $installer->getTable('core/layout_link'), + 'layout_update_id', + $installer->getTable('core/layout_update'), + 'layout_update_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/store', 'group_id', 'core/store_group', 'group_id'), + $installer->getTable('core/store'), + 'group_id', + $installer->getTable('core/store_group'), + 'group_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/store', 'website_id', 'core/website', 'website_id'), + $installer->getTable('core/store'), + 'website_id', + $installer->getTable('core/website'), + 'website_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/store_group', 'website_id', 'core/website', 'website_id'), + $installer->getTable('core/store_group'), + 'website_id', + $installer->getTable('core/website'), + 'website_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/translate', 'store_id', 'core/store', 'store_id'), + $installer->getTable('core/translate'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/url_rewrite', 'product_id', 'catalog/product', 'entity_id'), + $installer->getTable('core/url_rewrite'), + 'product_id', + $installer->getTable('catalog/product'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/url_rewrite', 'category_id', 'catalog/category', 'entity_id'), + $installer->getTable('core/url_rewrite'), + 'category_id', + $installer->getTable('catalog/category'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/url_rewrite', 'store_id', 'core/store', 'store_id'), + $installer->getTable('core/url_rewrite'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/variable_value', 'store_id', 'core/store', 'store_id'), + $installer->getTable('core/variable_value'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/variable_value', 'variable_id', 'core/variable', 'variable_id'), + $installer->getTable('core/variable_value'), + 'variable_id', + $installer->getTable('core/variable'), + 'variable_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('core/design_change', 'store_id', 'core/store', 'store_id'), + $installer->getTable('core/design_change'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +if ($usedDatabaseStorage) { + $installer->getConnection()->addForeignKey( + $installer->getFkName('core/file_storage', 'directory_id', 'core/directory_storage', 'directory_id'), + $installer->getTable('core/file_storage'), + 'directory_id', + $installer->getTable('core/directory_storage'), + 'directory_id' + ); + + $installer->getConnection()->addForeignKey( + $installer->getFkName('core/directory_storage', 'parent_id', 'core/directory_storage', 'directory_id'), + $installer->getTable('core/directory_storage'), + 'parent_id', + $installer->getTable('core/directory_storage'), + 'directory_id' + ); +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Core/sql/core_setup/upgrade-1.6.0.1-1.6.0.2.php b/app/code/core/Mage/Core/sql/core_setup/upgrade-1.6.0.1-1.6.0.2.php new file mode 100644 index 00000000..01ca94b2 --- /dev/null +++ b/app/code/core/Mage/Core/sql/core_setup/upgrade-1.6.0.1-1.6.0.2.php @@ -0,0 +1,37 @@ +startSetup(); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('core/cache_tag'), + $installer->getFkName('core/cache_tag', 'cache_id', 'core/cache', 'id') +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Cron/Exception.php b/app/code/core/Mage/Cron/Exception.php index 0fd10a12..15d27f92 100644 --- a/app/code/core/Mage/Cron/Exception.php +++ b/app/code/core/Mage/Cron/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cron/Helper/Data.php b/app/code/core/Mage/Cron/Helper/Data.php index 65dab58c..e4a69037 100644 --- a/app/code/core/Mage/Cron/Helper/Data.php +++ b/app/code/core/Mage/Cron/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cron/Model/Mysql4/Schedule.php b/app/code/core/Mage/Cron/Model/Mysql4/Schedule.php index 6f47faac..b5d3b268 100644 --- a/app/code/core/Mage/Cron/Model/Mysql4/Schedule.php +++ b/app/code/core/Mage/Cron/Model/Mysql4/Schedule.php @@ -20,44 +20,18 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Schedule mysql4 resource * - * @category Mage - * @package Mage_Cron + * @category Mage + * @package Mage_Cron * @author Magento Core Team */ -class Mage_Cron_Model_Mysql4_Schedule extends Mage_Core_Model_Mysql4_Abstract +class Mage_Cron_Model_Mysql4_Schedule extends Mage_Cron_Model_Resource_Schedule { - public function _construct() - { - $this->_init('cron/schedule', 'schedule_id'); - } - - /** - * If job is currently in $currentStatus, set it to $newStatus - * and return true. Otherwise, return false and do not change the job. - * - * This method is used to implement locking for cron jobs. - * - * @param String $newStatus - * @param String $currentStatus - */ - public function trySetJobStatusAtomic($scheduleId, $newStatus, $currentStatus) - { - $write = $this->_getWriteAdapter(); - $result = $write->update( - $this->getTable('cron/schedule'), - array('status' => $newStatus), - array('schedule_id = ?' => $scheduleId, 'status = ?' => $currentStatus) - ); - if ($result == 1) { - return true; - } - return false; - } } diff --git a/app/code/core/Mage/Cron/Model/Mysql4/Schedule/Collection.php b/app/code/core/Mage/Cron/Model/Mysql4/Schedule/Collection.php index 258a7dd0..d4739f34 100644 --- a/app/code/core/Mage/Cron/Model/Mysql4/Schedule/Collection.php +++ b/app/code/core/Mage/Cron/Model/Mysql4/Schedule/Collection.php @@ -20,21 +20,18 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Schedules Collection * - * @category Mage - * @package Mage_Cron + * @category Mage + * @package Mage_Cron * @author Magento Core Team */ -class Mage_Cron_Model_Mysql4_Schedule_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Cron_Model_Mysql4_Schedule_Collection extends Mage_Cron_Model_Resource_Schedule_Collection { - public function _construct() - { - $this->_init('cron/schedule'); - } } diff --git a/app/code/core/Mage/Cron/Model/Observer.php b/app/code/core/Mage/Cron/Model/Observer.php index 2e95e626..3057cb43 100644 --- a/app/code/core/Mage/Cron/Model/Observer.php +++ b/app/code/core/Mage/Cron/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -60,11 +60,15 @@ public function dispatch($observer) $scheduleLifetime = Mage::getStoreConfig(self::XML_PATH_SCHEDULE_LIFETIME) * 60; $now = time(); $jobsRoot = Mage::getConfig()->getNode('crontab/jobs'); + $defaultJobsRoot = Mage::getConfig()->getNode('default/crontab/jobs'); foreach ($schedules->getIterator() as $schedule) { $jobConfig = $jobsRoot->{$schedule->getJobCode()}; if (!$jobConfig || !$jobConfig->run) { - continue; + $jobConfig = $defaultJobsRoot->{$schedule->getJobCode()}; + if (!$jobConfig || !$jobConfig->run) { + continue; + } } $runConfig = $jobConfig->run; @@ -99,12 +103,19 @@ public function dispatch($observer) // another cron started this job intermittently, so skip it continue; } - $schedule->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time())) + /** + though running status is set in tryLockJob we must set it here because the object + was loaded with a pending status and will set it back to pending if we don't set it here + */ + $schedule + ->setStatus(Mage_Cron_Model_Schedule::STATUS_RUNNING) + ->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', time())) ->save(); call_user_func_array($callback, $arguments); - $schedule->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS) + $schedule + ->setStatus(Mage_Cron_Model_Schedule::STATUS_SUCCESS) ->setFinishedAt(strftime('%Y-%m-%d %H:%M:%S', time())); } catch (Exception $e) { diff --git a/app/code/core/Mage/Cron/Model/Resource/Schedule.php b/app/code/core/Mage/Cron/Model/Resource/Schedule.php new file mode 100755 index 00000000..b3dd22b9 --- /dev/null +++ b/app/code/core/Mage/Cron/Model/Resource/Schedule.php @@ -0,0 +1,69 @@ + + */ +class Mage_Cron_Model_Resource_Schedule extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize resource + * + */ + public function _construct() + { + $this->_init('cron/schedule', 'schedule_id'); + } + + /** + * If job is currently in $currentStatus, set it to $newStatus + * and return true. Otherwise, return false and do not change the job. + * This method is used to implement locking for cron jobs. + * + * @param unknown_type $scheduleId + * @param String $newStatus + * @param String $currentStatus + * @return unknown + */ + public function trySetJobStatusAtomic($scheduleId, $newStatus, $currentStatus) + { + $write = $this->_getWriteAdapter(); + $result = $write->update( + $this->getTable('cron/schedule'), + array('status' => $newStatus), + array('schedule_id = ?' => $scheduleId, 'status = ?' => $currentStatus) + ); + if ($result == 1) { + return true; + } + return false; + } +} diff --git a/app/code/core/Mage/Cron/Model/Resource/Schedule/Collection.php b/app/code/core/Mage/Cron/Model/Resource/Schedule/Collection.php new file mode 100755 index 00000000..48f460a8 --- /dev/null +++ b/app/code/core/Mage/Cron/Model/Resource/Schedule/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Cron_Model_Resource_Schedule_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource collection + * + */ + public function _construct() + { + $this->_init('cron/schedule'); + } +} diff --git a/app/code/core/Mage/Cron/Model/Schedule.php b/app/code/core/Mage/Cron/Model/Schedule.php index 01ea37a4..f3d91597 100644 --- a/app/code/core/Mage/Cron/Model/Schedule.php +++ b/app/code/core/Mage/Cron/Model/Schedule.php @@ -20,15 +20,32 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Crontab schedule model * - * @category Mage - * @package Mage_Cron + * @method Mage_Cron_Model_Resource_Schedule _getResource() + * @method Mage_Cron_Model_Resource_Schedule getResource() + * @method string getJobCode() + * @method Mage_Cron_Model_Schedule setJobCode(string $value) + * @method string getStatus() + * @method Mage_Cron_Model_Schedule setStatus(string $value) + * @method string getMessages() + * @method Mage_Cron_Model_Schedule setMessages(string $value) + * @method string getCreatedAt() + * @method Mage_Cron_Model_Schedule setCreatedAt(string $value) + * @method string getScheduledAt() + * @method Mage_Cron_Model_Schedule setScheduledAt(string $value) + * @method string getExecutedAt() + * @method Mage_Cron_Model_Schedule setExecutedAt(string $value) + * @method string getFinishedAt() + * @method Mage_Cron_Model_Schedule setFinishedAt(string $value) + * + * @category Mage + * @package Mage_Cron * @author Magento Core Team */ class Mage_Cron_Model_Schedule extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Cron/etc/config.xml b/app/code/core/Mage/Cron/etc/config.xml index ad328d88..e4da3ab8 100644 --- a/app/code/core/Mage/Cron/etc/config.xml +++ b/app/code/core/Mage/Cron/etc/config.xml @@ -21,14 +21,14 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.1 + 1.6.0.0 @@ -42,19 +42,19 @@ Mage_Cron_Model - cron_mysql4 + cron_resource - - Mage_Cron_Model_Mysql4 + + Mage_Cron_Model_Resource + cron_mysql4 cron_schedule
    -
    +
    - @@ -66,20 +66,7 @@ - - @@ -92,16 +79,15 @@ - - - - - - Mage_Cron.csv - - - - + + + + + Mage_Cron.csv + + + +
    diff --git a/app/code/core/Mage/Cron/etc/system.xml b/app/code/core/Mage/Cron/etc/system.xml index dbbe608d..a0ae3c29 100644 --- a/app/code/core/Mage/Cron/etc/system.xml +++ b/app/code/core/Mage/Cron/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/Cron/sql/cron_setup/install-1.6.0.0.php b/app/code/core/Mage/Cron/sql/cron_setup/install-1.6.0.0.php new file mode 100644 index 00000000..1b95da55 --- /dev/null +++ b/app/code/core/Mage/Cron/sql/cron_setup/install-1.6.0.0.php @@ -0,0 +1,74 @@ +startSetup(); + +/** + * Create table 'cron/schedule' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('cron/schedule')) + ->addColumn('schedule_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Schedule Id') + ->addColumn('job_code', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + 'default' => '0', + ), 'Job Code') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_TEXT, 7, array( + 'nullable' => false, + 'default' => 'pending', + ), 'Status') + ->addColumn('messages', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Messages') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Created At') + ->addColumn('scheduled_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => true, + ), 'Scheduled At') + ->addColumn('executed_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => true, + ), 'Executed At') + ->addColumn('finished_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => true, + ), 'Finished At') + ->addIndex($installer->getIdxName('cron/schedule', array('job_code')), + array('job_code')) + ->addIndex($installer->getIdxName('cron/schedule', array('scheduled_at', 'status')), + array('scheduled_at', 'status')) + ->setComment('Cron Schedule'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Cron/sql/cron_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-install-0.7.0.php index 484ebb15..47a17c0f 100644 --- a/app/code/core/Mage/Cron/sql/cron_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-0.7.0-0.7.1.php index 7d5401cf..238b916d 100644 --- a/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Cron - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..cc7ae41f --- /dev/null +++ b/app/code/core/Mage/Cron/sql/cron_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,118 @@ +startSetup(); + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('cron/schedule'), + 'TASK_NAME' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('cron/schedule'), + 'SCHEDULED_AT' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('cron/schedule') => array( + 'columns' => array( + 'schedule_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Schedule Id' + ), + 'job_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Job Code' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 7, + 'nullable' => false, + 'default' => 'pending', + 'comment' => 'Status' + ), + 'messages' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Messages' + ), + 'created_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Created At' + ), + 'scheduled_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Scheduled At' + ), + 'executed_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Executed At' + ), + 'finished_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Finished At' + ) + ), + 'comment' => 'Cron Schedule' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('cron/schedule'), + $installer->getIdxName('cron/schedule', array('job_code')), + array('job_code') +); + +$installer->getConnection()->addIndex( + $installer->getTable('cron/schedule'), + $installer->getIdxName('cron/schedule', array('scheduled_at', 'status')), + array('scheduled_at', 'status') +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Dataflow/Helper/Data.php b/app/code/core/Mage/Dataflow/Helper/Data.php index 1996735c..017d1cb9 100644 --- a/app/code/core/Mage/Dataflow/Helper/Data.php +++ b/app/code/core/Mage/Dataflow/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Batch.php b/app/code/core/Mage/Dataflow/Model/Batch.php index a247005f..12917787 100644 --- a/app/code/core/Mage/Dataflow/Model/Batch.php +++ b/app/code/core/Mage/Dataflow/Model/Batch.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,19 @@ /** * Dataflow Batch model * - * @category Mage - * @package Mage_Dataflow + * @method Mage_Dataflow_Model_Resource_Batch _getResource() + * @method Mage_Dataflow_Model_Resource_Batch getResource() + * @method int getProfileId() + * @method Mage_Dataflow_Model_Batch setProfileId(int $value) + * @method int getStoreId() + * @method Mage_Dataflow_Model_Batch setStoreId(int $value) + * @method string getAdapter() + * @method Mage_Dataflow_Model_Batch setAdapter(string $value) + * @method string getCreatedAt() + * @method Mage_Dataflow_Model_Batch setCreatedAt(string $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Batch extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Batch/Abstract.php b/app/code/core/Mage/Dataflow/Model/Batch/Abstract.php index aac87ebe..27e928dc 100644 --- a/app/code/core/Mage/Dataflow/Model/Batch/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Batch/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,13 +44,13 @@ abstract class Mage_Dataflow_Model_Batch_Abstract extends Mage_Core_Model_Abstra public function setBatchData($data) { if ('"libiconv"' == ICONV_IMPL) { - foreach ($data as $key => &$value) { + foreach ($data as &$value) { $value = iconv('utf-8', 'utf-8//IGNORE', $value); } } $this->setData('batch_data', serialize($data)); - + return $this; } diff --git a/app/code/core/Mage/Dataflow/Model/Batch/Export.php b/app/code/core/Mage/Dataflow/Model/Batch/Export.php index ae634057..bde82698 100644 --- a/app/code/core/Mage/Dataflow/Model/Batch/Export.php +++ b/app/code/core/Mage/Dataflow/Model/Batch/Export.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,15 @@ /** * Dataflow Batch export model * - * @category Mage - * @package Mage_Dataflow + * @method Mage_Dataflow_Model_Resource_Batch_Export _getResource() + * @method Mage_Dataflow_Model_Resource_Batch_Export getResource() + * @method int getBatchId() + * @method Mage_Dataflow_Model_Batch_Export setBatchId(int $value) + * @method int getStatus() + * @method Mage_Dataflow_Model_Batch_Export setStatus(int $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Batch_Export extends Mage_Dataflow_Model_Batch_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Batch/Import.php b/app/code/core/Mage/Dataflow/Model/Batch/Import.php index e0d3d627..76f90343 100644 --- a/app/code/core/Mage/Dataflow/Model/Batch/Import.php +++ b/app/code/core/Mage/Dataflow/Model/Batch/Import.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,15 @@ /** * Dataflow Batch import model * - * @category Mage - * @package Mage_Dataflow + * @method Mage_Dataflow_Model_Resource_Batch_Import _getResource() + * @method Mage_Dataflow_Model_Resource_Batch_Import getResource() + * @method int getBatchId() + * @method Mage_Dataflow_Model_Batch_Import setBatchId(int $value) + * @method int getStatus() + * @method Mage_Dataflow_Model_Batch_Import setStatus(int $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Batch_Import extends Mage_Dataflow_Model_Batch_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Batch/Io.php b/app/code/core/Mage/Dataflow/Model/Batch/Io.php index 5def0fde..9f4e301f 100644 --- a/app/code/core/Mage/Dataflow/Model/Batch/Io.php +++ b/app/code/core/Mage/Dataflow/Model/Batch/Io.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert.php b/app/code/core/Mage/Dataflow/Model/Convert.php index 1e383a25..b698c83c 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert.php +++ b/app/code/core/Mage/Dataflow/Model/Convert.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Action.php b/app/code/core/Mage/Dataflow/Model/Convert/Action.php index d5320847..036b33b7 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Action.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Action/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Action/Abstract.php index ca256225..4a226296 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Action/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Action/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Action/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Action/Interface.php index 8c818914..c0c4b39e 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Action/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Action/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Abstract.php index b4405077..22cf1f3b 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Db/Table.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Db/Table.php index 49c2b50c..8d9e0d0f 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Db/Table.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Db/Table.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http.php index 2c8b92a8..2a0bb002 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -74,8 +74,7 @@ public function loadFile() exit; } if (!empty($_FILES['io_file']['tmp_name'])) { - //$this->setData(file_get_contents($_FILES['io_file']['tmp_name'])); - $uploader = new Varien_File_Uploader('io_file'); + $uploader = new Mage_Core_Model_File_Uploader('io_file'); $uploader->setAllowedExtensions(array('csv','xml')); $path = Mage::app()->getConfig()->getTempVarDir().'/import/'; $uploader->save($path); diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http/Curl.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http/Curl.php index 1b8fc242..1a82e42b 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http/Curl.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Http/Curl.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -47,7 +47,7 @@ public function load() } // use Varien curl adapter - $http = new Varien_Http_Adapter_Curl; + $http = new Varien_Http_Adapter_Curl(); // send GET request $http->write('GET', $uri); @@ -55,6 +55,8 @@ public function load() // read the remote file $data = $http->read(); + $http->close(); + $data = preg_split('/^\r?$/m', $data, 2); $data = trim($data[1]); diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Interface.php index bae88268..ca19b665 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php index 693aa6de..07e3db74 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Io.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,6 +34,8 @@ */ class Mage_Dataflow_Model_Convert_Adapter_Io extends Mage_Dataflow_Model_Convert_Adapter_Abstract { + const XML_PATH_EXPORT_LOCAL_VALID_PATH = 'general/file/importexport_local_valid_paths'; + /** * @return Varien_Io_Abstract */ @@ -41,7 +43,7 @@ public function getResource($forWrite = false) { if (!$this->_resource) { $type = $this->getVar('type', 'file'); - $className = 'Varien_Io_'.ucwords($type); + $className = 'Varien_Io_' . ucwords($type); $this->_resource = new $className(); $isError = false; @@ -49,12 +51,24 @@ public function getResource($forWrite = false) $ioConfig = $this->getVars(); switch ($this->getVar('type', 'file')) { case 'file': - if (preg_match('#^'.preg_quote(DS, '#').'#', $this->getVar('path')) || - preg_match('#^[a-z]:'.preg_quote(DS, '#') .'#i', $this->getVar('path'))) { + //validate export/import path + $path = rtrim($ioConfig['path'], '\\/') + . DS . $ioConfig['filename']; + /** @var $validator Mage_Core_Model_File_Validator_AvailablePath */ + $validator = Mage::getModel('core/file_validator_availablePath'); + $validator->setPaths( Mage::getStoreConfig(self::XML_PATH_EXPORT_LOCAL_VALID_PATH) ); + if (!$validator->isValid($path)) { + foreach ($validator->getMessages() as $message) { + Mage::throwException($message); + return false; + } + } + + if (preg_match('#^' . preg_quote(DS, '#').'#', $this->getVar('path')) || + preg_match('#^[a-z]:' . preg_quote(DS, '#') . '#i', $this->getVar('path'))) { $path = $this->_resource->getCleanPath($this->getVar('path')); - } - else { + } else { $baseDir = Mage::getBaseDir(); $path = $this->_resource->getCleanPath($baseDir . DS . trim($this->getVar('path'), DS)); } @@ -66,50 +80,17 @@ public function getResource($forWrite = false) if (!$isError && $realPath === false) { $message = Mage::helper('dataflow')->__('The destination folder "%s" does not exist or there is no access to create it.', $ioConfig['path']); Mage::throwException($message); - } - elseif (!$isError && !is_dir($realPath)) { + } elseif (!$isError && !is_dir($realPath)) { $message = Mage::helper('dataflow')->__('Destination folder "%s" is not a directory.', $realPath); Mage::throwException($message); - } - elseif (!$isError) { + } elseif (!$isError) { if ($forWrite && !is_writeable($realPath)) { $message = Mage::helper('dataflow')->__('Destination folder "%s" is not writable.', $realPath); Mage::throwException($message); - } - else { + } else { $ioConfig['path'] = rtrim($realPath, DS); } } - -// $baseDir = Mage::getBaseDir(); -// $path = $this->_resource->getCleanPath($baseDir . '/' . trim($this->getVar('path'), '/')); -// $basePath = $this->_resource->getCleanPath($baseDir); -// -// if (strpos($path, $basePath) !== 0) { -// $message = Mage::helper('dataflow')->__('Access denied to destination folder "%s".', $path); -// Mage::throwException($message); -// } else { -// $this->_resource->checkAndCreateFolder($path); -// } -// -// $realPath = realpath($path); -// if (!$isError && $realPath === false) { -// $message = Mage::helper('dataflow')->__('Destination folder "%s" does not exist or there is no access to create it.', $ioConfig['path']); -// Mage::throwException($message); -// } -// elseif (!$isError && !is_dir($realPath)) { -// $message = Mage::helper('dataflow')->__('Destination folder "%s" is not a directory.', $realPath); -// Mage::throwException($message); -// } -// elseif (!$isError) { -// if ($forWrite && !is_writeable($realPath)) { -// $message = Mage::helper('dataflow')->__('Destination folder "%s" is not writable.', $realPath); -// Mage::throwException($message); -// } -// else { -// $ioConfig['path'] = rtrim($realPath, '/'); -// } -// } break; default: $ioConfig['path'] = rtrim($this->getVar('path'), '/'); @@ -158,7 +139,7 @@ public function load() } /** - * Save result to destionation file from temporary + * Save result to destination file from temporary * * @return Mage_Dataflow_Model_Convert_Adapter_Io */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Soap.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Soap.php index b5c6e6c8..5f03a703 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Soap.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Soap.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Std.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Std.php index 302726f1..a868afb4 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Std.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Std.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Cache.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Cache.php index 7bdc41c6..957af7b1 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Cache.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Cache.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Db.php b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Db.php index 27c79832..316cdf4d 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Db.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Adapter/Zend/Db.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Container/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Container/Abstract.php index a3af1ef1..39bf5b26 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Container/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Container/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Container/Collection.php b/app/code/core/Mage/Dataflow/Model/Convert/Container/Collection.php index 29bf0c37..74b783f0 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Container/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Container/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Container/Generic.php b/app/code/core/Mage/Dataflow/Model/Convert/Container/Generic.php index 3378eb91..f54f9843 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Container/Generic.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Container/Generic.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Container/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Container/Interface.php index d614b29d..10d45a85 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Container/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Container/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Exception.php b/app/code/core/Mage/Dataflow/Model/Convert/Exception.php index 13634707..0d4b0b9a 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Exception.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Iterator.php b/app/code/core/Mage/Dataflow/Model/Convert/Iterator.php index 2cc4437b..aa49f649 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Iterator.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Iterator.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Dataflow_Model_Session_Adapter_Iterator extends Mage_Dataflow_Model_Convert_Adapter_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/File/Csv.php b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/File/Csv.php index 5a460f13..78a7c74f 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/File/Csv.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/File/Csv.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Dataflow_Model_Convert_Iterator_File_Csv extends Mage_Dataflow_Model_Convert_Parser_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Http.php b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Http.php index fc38b043..9593e79b 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Http.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,8 +45,7 @@ public function load() exit; } if (!empty($_FILES['io_file']['tmp_name'])) { - //$this->setData(file_get_contents($_FILES['io_file']['tmp_name'])); - $uploader = new Varien_File_Uploader('io_file'); + $uploader = new Mage_Core_Model_File_Uploader('io_file'); $uploader->setAllowedExtensions(array('csv','xml')); $path = Mage::app()->getConfig()->getTempVarDir().'/import/'; $uploader->save($path); diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Interface.php index 9b84cd83..2fd0e1b2 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Iterator/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Abstract.php index 7ba068f0..93c1e29c 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Column.php b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Column.php index 553a97ed..af342eb2 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Column.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Column.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Interface.php index 23782163..d768a329 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Mapper/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Abstract.php index c3f6f3ef..ffacdad4 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Csv.php b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Csv.php index cdc72e98..8ee65cc0 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Csv.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Csv.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -169,7 +169,6 @@ public function parseRow($i, $line) return; } else { foreach ($line as $j=>$f) { -// $this->_fields[$j] = 'column'.($j+1); $this->_fields[$j] = $this->_mapfields[$j]; } } @@ -195,13 +194,15 @@ public function unparse() $fieldList = $this->getBatchModel()->getFieldList(); $batchExportIds = $batchExport->getIdCollection(); + $io = $this->getBatchModel()->getIoAdapter(); + $io->open(); + if (!$batchExportIds) { + $io->write(""); + $io->close(); return $this; } - $io = $this->getBatchModel()->getIoAdapter(); - $io->open(); - if ($this->getVar('fieldnames')) { $csvData = $this->getCsvString($fieldList); $io->write($csvData); diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Interface.php index 8e1bc3db..03614ec1 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Serialize.php b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Serialize.php index 8daaf2ce..bec67685 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Serialize.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Serialize.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Xml/Excel.php b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Xml/Excel.php index 4349a8d9..56042591 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Parser/Xml/Excel.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Parser/Xml/Excel.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -303,9 +303,9 @@ protected function _saveParsedRow($xmlString) foreach ($xmlElement->Row->children() as $cell) { if (is_null($this->_parseFieldNames)) { $xmlData[(string)$cell->Data] = (string)$cell->Data; - } - else { - if (($attributes = $cell->attributes('urn:schemas-microsoft-com:office:spreadsheet')) && isset($attributes['Index'])) { + } else { + $attributes = $cell->attributes('urn:schemas-microsoft-com:office:spreadsheet'); + if ($attributes && isset($attributes['Index'])) { $cellIndex = $attributes['Index'] - 1; } $xmlData[$cellIndex] = (string)$cell->Data; @@ -343,7 +343,7 @@ public function unparse() $fieldList = $this->getBatchModel()->getFieldList(); $batchExportIds = $batchExport->getIdCollection(); - if (!$batchExportIds) { + if (!is_array($batchExportIds)) { return $this; } @@ -462,7 +462,7 @@ public function unparse() */ protected function _getXmlString(array $fields = array()) { - $xmlHeader = '<'.'?xml version="1.0"?'.'>' . "\n"; + $xmlHeader = '' . "\n"; $xmlRegexp = '/^(.*)?<\/row><\/cell>\s?$/ms'; if (is_null($this->_xmlElement)) { @@ -476,15 +476,15 @@ protected function _getXmlString(array $fields = array()) $this->_xmlElement->row = htmlspecialchars($value); $value = str_replace($xmlHeader, '', $this->_xmlElement->asXML()); $value = preg_replace($xmlRegexp, '\\1', $value); - $dataType = "String"; if (is_numeric($value)) { - $dataType = "Number"; + $value = trim($value); + $dataType = 'Number'; + } else { + $dataType = 'String'; } - $value = str_replace("\r\n", ' ', $value); - $value = str_replace("\r", ' ', $value); - $value = str_replace("\n", ' ', $value); + $value = str_replace(array("\r\n", "\r", "\n"), ' ', $value); - $xmlData[] = ''.$value.''; + $xmlData[] = '' . $value . ''; } $xmlData[] = ''; diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Profile.php b/app/code/core/Mage/Dataflow/Model/Convert/Profile.php index aabf9a6f..3cb78c22 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Profile.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Profile.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Abstract.php index 1fd4f92f..ee11a250 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Collection.php b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Collection.php index 1c9f17f4..10fab3a8 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -157,16 +157,43 @@ public function importProfileXml($name) if ($action->getParam('name')) { $this->addContainer($action->getParam('name'), $container); } - foreach ($actionNode->var as $varNode) { + + $country = ''; + + /** @var $varNode Varien_Simplexml_Element */ + foreach ($actionNode->var as $key => $varNode) { if ($varNode['name'] == 'map') { $mapData = array(); foreach ($varNode->map as $mapNode) { $mapData[(string)$mapNode['name']] = (string)$mapNode; } $container->setVar((string)$varNode['name'], $mapData); - } - else { - $container->setVar((string)$varNode['name'], (string)$varNode); + } else { + $value = (string)$varNode; + + /** + * Get state name from directory by iso name + * (only for US) + */ + if ($value && 'filter/country' == (string)$varNode['name']) { + /** + * Save country for convert state iso to name (for US only) + */ + $country = $value; + } elseif ($value && 'filter/region' == (string)$varNode['name'] && 'US' == $country) { + /** + * Get state name by iso for US + */ + /** @var $region Mage_Directory_Model_Region */ + $region = Mage::getModel('directory/region'); + + $state = $region->loadByCode($value, $country)->getDefaultName(); + if ($state) { + $value = $state; + } + } + + $container->setVar((string)$varNode['name'], $value); } } } diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Interface.php index 739faa35..5f1960b4 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Profile/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Profile/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Abstract.php b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Abstract.php index 785e158f..5ff5596e 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Column.php b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Column.php index 6b312989..d4f38c9e 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Column.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Column.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,5 +34,7 @@ */ class Mage_Dataflow_Model_Convert_Validator_Column extends Mage_Dataflow_Model_Convert_Validator_Abstract { - + public function validate() + { + } } diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Dryrun.php b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Dryrun.php index 07541cdf..23d550b0 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Dryrun.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Dryrun.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Interface.php b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Interface.php index 5939e22a..5b054ee4 100644 --- a/app/code/core/Mage/Dataflow/Model/Convert/Validator/Interface.php +++ b/app/code/core/Mage/Dataflow/Model/Convert/Validator/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/Model/Import.php b/app/code/core/Mage/Dataflow/Model/Import.php index 1b58e965..81f3fe12 100644 --- a/app/code/core/Mage/Dataflow/Model/Import.php +++ b/app/code/core/Mage/Dataflow/Model/Import.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,19 @@ /** * DataFlow Import Model * - * @category Mage - * @package Mage_Dataflow + * @method Mage_Dataflow_Model_Resource_Import _getResource() + * @method Mage_Dataflow_Model_Resource_Import getResource() + * @method int getSessionId() + * @method Mage_Dataflow_Model_Import setSessionId(int $value) + * @method int getSerialNumber() + * @method Mage_Dataflow_Model_Import setSerialNumber(int $value) + * @method string getValue() + * @method Mage_Dataflow_Model_Import setValue(string $value) + * @method int getStatus() + * @method Mage_Dataflow_Model_Import setStatus(int $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Import extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch.php index 2544df8a..3c085171 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,14 +28,10 @@ /** * Dataflow Batch resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Batch extends Mage_Core_Model_Mysql4_Abstract +class Mage_Dataflow_Model_Mysql4_Batch extends Mage_Dataflow_Model_Resource_Batch { - protected function _construct() - { - $this->_init('dataflow/batch', 'batch_id'); - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Abstract.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Abstract.php index 2fc816bd..84024698 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Abstract.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,50 +28,10 @@ /** * Dataflow Batch abstract resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -abstract class Mage_Dataflow_Model_Mysql4_Batch_Abstract extends Mage_Core_Model_Mysql4_Abstract +abstract class Mage_Dataflow_Model_Mysql4_Batch_Abstract extends Mage_Dataflow_Model_Resource_Batch_Abstract { - /** - * Retrieve Id collection - * - * @param Mage_Dataflow_Model_Batch_Abstract $object - * @return array - */ - public function getIdCollection(Mage_Dataflow_Model_Batch_Abstract $object) - { - if (!$object->getBatchId()) { - return array(); - } - - $ids = array(); - $select = $this->_getWriteAdapter()->select() - ->from($this->getMainTable(), array($this->getIdFieldName())) - ->where('batch_id=?', $object->getBatchId()); - $query = $this->_getWriteAdapter()->query($select); - while ($row = $query->fetch()) { - $ids[] = $row[$this->getIdFieldName()]; - } - return $ids; - } - - /** - * Delete current Batch collection - * - * @param Mage_Dataflow_Model_Batch_Abstract $object - * @return Mage_Dataflow_Model_Mysql4_Batch_Abstract - */ - public function deleteCollection(Mage_Dataflow_Model_Batch_Abstract $object) - { - if (!$object->getBatchId()) { - return $this; - } - - $this->_getWriteAdapter()->delete($this->getMainTable(), - $this->_getWriteAdapter()->quoteInto('batch_id=?', $object->getBatchId()) - ); - return $this; - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Collection.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Collection.php index 39a2373e..c62f5ffc 100755 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,32 +28,10 @@ /** * Dataflow batch collection * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Batch_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Dataflow_Model_Mysql4_Batch_Collection extends Mage_Dataflow_Model_Resource_Batch_Collection { - /** - * Init model - * - */ - protected function _construct() - { - $this->_init('dataflow/batch'); - } - - /** - * Add expire filter (for abandoned batches) - * - */ - public function addExpireFilter() - { - $date = Mage::getSingleton('core/date'); - /* @var $date Mage_Core_Model_Date */ - $lifetime = Mage_Dataflow_Model_Batch::LIFETIME; - $expire = $date->gmtDate(null, $date->timestamp() - $lifetime); - - $this->getSelect()->where('created_at < ?', $expire); - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Export.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Export.php index 38ececd3..1e362323 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Export.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Export.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,14 +28,10 @@ /** * Dataflow Batch export resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Batch_Export extends Mage_Dataflow_Model_Mysql4_Batch_Abstract +class Mage_Dataflow_Model_Mysql4_Batch_Export extends Mage_Dataflow_Model_Resource_Batch_Export { - protected function _construct() - { - $this->_init('dataflow/batch_export', 'batch_export_id'); - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Import.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Import.php index 4d2c5354..6315c7c6 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Import.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Batch/Import.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,14 +28,10 @@ /** * Dataflow Batch import resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Batch_Import extends Mage_Dataflow_Model_Mysql4_Batch_Abstract +class Mage_Dataflow_Model_Mysql4_Batch_Import extends Mage_Dataflow_Model_Resource_Batch_Import { - protected function _construct() - { - $this->_init('dataflow/batch_import', 'batch_import_id'); - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Catalogold.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Catalogold.php index 84a847b1..6d03cc70 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Catalogold.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Catalogold.php @@ -20,11 +20,13 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - +/** + * @deprecated after 1.5.0.1 + */ class Mage_Catalog_Model_Mysql4_Convert { protected $_productsBySku; @@ -229,9 +231,6 @@ public function exportProducts() ->join(array('aov'=>$this->getTable('eav/attribute_option_value')), 'aov.option_id=ao.option_id', array('value_id', 'value')) ->where('aov.store_id=0'); - echo $select->__toString(); - die(); - $collection = Mage::getResourceModel('catalog/product_collection') ->addAttributeToSelect('*') ->load(); diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Import.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Import.php index 3b4d8889..1cff4e47 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Import.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Import.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,56 +28,10 @@ /** * DataFlow Import resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Import extends Mage_Core_Model_Mysql4_Abstract +class Mage_Dataflow_Model_Mysql4_Import extends Mage_Dataflow_Model_Resource_Import { - - protected function _construct() - { - $this->_init('dataflow/import', 'import_id'); - } - - public function select($sessionId) - { - return $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where('session_id=?', $sessionId) - ->where('status=?', 0); - } - - public function loadBySessionId($sessionId, $min = 0, $max = 100) - { - if (!is_numeric($min) || !is_numeric($max)) { - return array(); - } - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getTable('dataflow/import'), '*') - ->where('import_id between '.(int)$min.' and '.(int)$max) - ->where('status=?', '0') - ->where('session_id=?', $sessionId); - return $read->fetchAll($select); - } - - public function loadTotalBySessionId($sessionId) - { - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getTable('dataflow/import'), - array('max'=>'max(import_id)','min'=>'min(import_id)', 'cnt'=>'count(*)')) - ->where('status=?', '0') - ->where('session_id=?', $sessionId); - return $read->fetchRow($select); - } - - public function loadById($importId) - { - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getTable('dataflow/import'),'*') - ->where('status=?', 0) - ->where('import_id=?', $importId); - return $read->fetchRow($select); - } - } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Import/Collection.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Import/Collection.php index 41bfded2..60cee57f 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Import/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Import/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,16 +28,10 @@ /** * Import collection * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Import_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Dataflow_Model_Mysql4_Import_Collection extends Mage_Dataflow_Model_Resource_Import_Collection { - - protected function _construct() - { - $this->_init('dataflow/import', 'import_id'); - } - } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile.php index 49e0f4e8..6fc40835 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile.php @@ -20,42 +20,18 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Convert profile resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Profile extends Mage_Core_Model_Mysql4_Abstract +class Mage_Dataflow_Model_Mysql4_Profile extends Mage_Dataflow_Model_Resource_Profile { - protected function _construct() - { - $this->_init('dataflow/profile', 'profile_id'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (!$object->getCreatedAt()) { - $object->setCreatedAt($this->formatDate(time())); - } - $object->setUpdatedAt($this->formatDate(time())); - parent::_beforeSave($object); - } - - public function isProfileExists($name, $id = null) - { - $select = $this->_getReadAdapter()->select(); - $select - ->from($this->getMainTable(), 'count(*)') - ->where('name = ?', $name); - if ($id) - $select->where("{$this->getIdFieldName()} <> ?", $id); - - return $this->_getReadAdapter()->fetchOne($select); - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php index 530e88b9..f4db5ef5 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/Collection.php @@ -20,33 +20,18 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Convert profile collection * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Profile_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Dataflow_Model_Mysql4_Profile_Collection extends Mage_Dataflow_Model_Resource_Profile_Collection { - protected function _construct() - { - $this->_init('dataflow/profile'); - } - - /** - * Filter collection by specified store ids - * - * @param array|int $storeIds - * @return Mage_Dataflow_Model_Mysql4_Profile_Collection - */ - public function addStoreFilter($storeIds) - { - $this->getSelect()->where('main_table.store_id IN (?)', array(0, $storeIds)); - return $this; - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History.php index 5a5508be..a4ee8e34 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History.php @@ -20,30 +20,18 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Convert history resource model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Profile_History extends Mage_Core_Model_Mysql4_Abstract +class Mage_Dataflow_Model_Mysql4_Profile_History extends Mage_Dataflow_Model_Resource_Profile_History { - protected function _construct() - { - $this->_init('dataflow/profile_history', 'history_id'); - } - - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (!$object->getPerformedAt()) { - $object->setPerformedAt($this->formatDate(time())); - } - parent::_beforeSave($object); - return $this; - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History/Collection.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History/Collection.php index 1d70a155..8c056c9a 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History/Collection.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Profile/History/Collection.php @@ -20,32 +20,19 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Convert history collection * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Profile_History_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Dataflow_Model_Mysql4_Profile_History_Collection + extends Mage_Dataflow_Model_Resource_Profile_History_Collection { - protected function _construct() - { - $this->_init('dataflow/profile_history'); - parent::_construct(); - } - - public function joinAdminUser() - { - $this->getSelect()->join( - array('u' => $this->getTable('admin/user')), - 'u.user_id=main_table.user_id', - array('firstname', 'lastname') - ); - return $this; - } } diff --git a/app/code/core/Mage/Dataflow/Model/Mysql4/Session.php b/app/code/core/Mage/Dataflow/Model/Mysql4/Session.php index 56b9419d..5a91b4f1 100644 --- a/app/code/core/Mage/Dataflow/Model/Mysql4/Session.php +++ b/app/code/core/Mage/Dataflow/Model/Mysql4/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,16 +28,10 @@ /** * DataFlow Session Resource Model * - * @category Mage - * @package Mage_Dataflow + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ -class Mage_Dataflow_Model_Mysql4_Session extends Mage_Core_Model_Mysql4_Abstract +class Mage_Dataflow_Model_Mysql4_Session extends Mage_Dataflow_Model_Resource_Session { - - protected function _construct() - { - $this->_init('dataflow/session', 'session_id'); - } - } diff --git a/app/code/core/Mage/Dataflow/Model/Profile.php b/app/code/core/Mage/Dataflow/Model/Profile.php index 3f68e0a2..b6062cd2 100644 --- a/app/code/core/Mage/Dataflow/Model/Profile.php +++ b/app/code/core/Mage/Dataflow/Model/Profile.php @@ -20,13 +20,36 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Convert profile * + * @method Mage_Dataflow_Model_Resource_Profile _getResource() + * @method Mage_Dataflow_Model_Resource_Profile getResource() + * @method string getName() + * @method Mage_Dataflow_Model_Profile setName(string $value) + * @method string getCreatedAt() + * @method Mage_Dataflow_Model_Profile setCreatedAt(string $value) + * @method string getUpdatedAt() + * @method Mage_Dataflow_Model_Profile setUpdatedAt(string $value) + * @method string getActionsXml() + * @method Mage_Dataflow_Model_Profile setActionsXml(string $value) + * @method string getGuiData() + * @method Mage_Dataflow_Model_Profile setGuiData(string $value) + * @method string getDirection() + * @method Mage_Dataflow_Model_Profile setDirection(string $value) + * @method string getEntityType() + * @method Mage_Dataflow_Model_Profile setEntityType(string $value) + * @method int getStoreId() + * @method Mage_Dataflow_Model_Profile setStoreId(int $value) + * @method string getDataTransfer() + * @method Mage_Dataflow_Model_Profile setDataTransfer(string $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Profile extends Mage_Core_Model_Abstract @@ -54,35 +77,50 @@ protected function _afterLoad() protected function _beforeSave() { parent::_beforeSave(); - $actionsXML = $this->getData('actions_xml'); - if (strlen($actionsXML) < 0 && @simplexml_load_string(''.$actionsXML.'', null, LIBXML_NOERROR) === false) { - Mage::throwException(Mage::helper("dataflow")->__("Actions XML is not valid.")); + if (strlen($actionsXML) < 0 && + @simplexml_load_string('' . $actionsXML . '', null, LIBXML_NOERROR) === false) { + Mage::throwException(Mage::helper('dataflow')->__("Actions XML is not valid.")); } if (is_array($this->getGuiData())) { $data = $this->getData(); $guiData = $this->getGuiData(); + $charSingleList = array('\\', '/', '.', '!', '@', '#', '$', '%', '&', '*', '~', '^'); if (isset($guiData['file']['type']) && $guiData['file']['type'] == 'file') { - if (strlen($guiData['file']['path']) == 0 + if (empty($guiData['file']['path']) || (strlen($guiData['file']['path']) == 1 - && in_array($guiData['file']['path'], array('\\','/','.','!','@','#','$','%','&', '*','~', '^')))) { + && in_array($guiData['file']['path'], $charSingleList))) { $guiData['file']['path'] = self::DEFAULT_EXPORT_PATH; } - if (strlen($guiData['file']['filename']) == 0 ) { - $guiData['file']['filename'] = self::DEFAULT_EXPORT_FILENAME.$data['entity_type'].'.'.($guiData['parse']['type']=='csv'?$guiData['parse']['type']:'xml'); + if (empty($guiData['file']['filename'])) { + $guiData['file']['filename'] = self::DEFAULT_EXPORT_FILENAME . $data['entity_type'] + . '.' . ($guiData['parse']['type']=='csv' ? $guiData['parse']['type'] : 'xml'); } + + //validate export available path + $path = rtrim($guiData['file']['path'], '\\/') + . DS . $guiData['file']['filename']; + /** @var $validator Mage_Core_Model_File_Validator_AvailablePath */ + $validator = Mage::getModel('core/file_validator_availablePath'); + /** @var $helperImportExport Mage_ImportExport_Helper_Data */ + $helperImportExport = Mage::helper('importexport'); + $validator->setPaths($helperImportExport->getLocalValidPaths()); + if (!$validator->isValid($path)) { + foreach ($validator->getMessages() as $message) { + Mage::throwException($message); + } + } + $this->setGuiData($guiData); } -// echo '
    ';
    -//        	print_r($this->getGuiData());
                 $this->_parseGuiData();
     
                 $this->setGuiData(serialize($this->getGuiData()));
             }
     
             if ($this->_getResource()->isProfileExists($this->getName(), $this->getId())) {
    -            Mage::throwException(Mage::helper("dataflow")->__("Profile with the same name already exists."));
    +            Mage::throwException(Mage::helper('dataflow')->__("Profile with the same name already exists."));
             }
         }
     
    @@ -92,23 +130,40 @@ protected function _afterSave()
                 $this->setGuiData(unserialize($this->getGuiData()));
             }
     
    -        Mage::getModel('dataflow/profile_history')
    +        $profileHistory = Mage::getModel('dataflow/profile_history');
    +
    +        $adminUserId = $this->getAdminUserId();
    +        if($adminUserId) {
    +            $profileHistory->setUserId($adminUserId);
    +        }
    +
    +        $profileHistory
                 ->setProfileId($this->getId())
                 ->setActionCode($this->getOrigData('profile_id') ? 'update' : 'create')
                 ->save();
     
    -        if (isset($_FILES['file_1']['tmp_name']) || isset($_FILES['file_2']['tmp_name']) || isset($_FILES['file_3']['tmp_name'])) {
    +        if (isset($_FILES['file_1']['tmp_name']) || isset($_FILES['file_2']['tmp_name'])
    +        || isset($_FILES['file_3']['tmp_name'])) {
                 for ($index = 0; $index < 3; $index++) {
    -                if ($file = $_FILES['file_'.($index+1)]['tmp_name']) {
    -                    $uploader = new Varien_File_Uploader('file_'.($index+1));
    +                if ($file = $_FILES['file_' . ($index+1)]['tmp_name']) {
    +                    $uploader = new Mage_Core_Model_File_Uploader('file_' . ($index + 1));
                         $uploader->setAllowedExtensions(array('csv','xml'));
    -                    $path = Mage::app()->getConfig()->getTempVarDir().'/import/';
    +                    $path = Mage::app()->getConfig()->getTempVarDir() . '/import/';
                         $uploader->save($path);
                         if ($uploadFile = $uploader->getUploadedFileName()) {
    -                        $newFilename = 'import-'.date('YmdHis').'-'.($index+1).'_'.$uploadFile;
    -                        rename($path.$uploadFile, $path.$newFilename);
    +                        $newFilename = 'import-' . date('YmdHis') . '-' . ($index+1) . '_' . $uploadFile;
    +                        rename($path . $uploadFile, $path . $newFilename);
                         }
                     }
    +                //BOM deleting for UTF files
    +                if (isset($newFilename) && $newFilename) {
    +                    $contents = file_get_contents($path . $newFilename);
    +                    if (ord($contents[0]) == 0xEF && ord($contents[1]) == 0xBB && ord($contents[2]) == 0xBF) {
    +                        $contents = substr($contents, 3);
    +                        file_put_contents($path . $newFilename, $contents);
    +                    }
    +                    unset($contents);
    +                }
                 }
             }
             parent::_afterSave();
    @@ -132,7 +187,8 @@ public function run()
             /**
              * Prepare xml convert profile actions data
              */
    -        $xml = ''.$this->getActionsXml().'';
    +        $xml = '' . $this->getActionsXml()
    +             . '';
             $profile = Mage::getModel('core/convert')
                 ->importXml($xml)
                 ->getProfile('default');
    @@ -145,10 +201,6 @@ public function run()
                     ->save();
                 $this->setBatchId($batch->getId());
     
    -//            print '
    ';
    -//            print_r($this->getData());
    -//            print '
    '; - $profile->setDataflowProfile($this->getData()); $profile->run(); } @@ -175,7 +227,8 @@ public function _parseGuiData() // $p['file']['filename'] = $p['interactive']['filename']; // $p['file']['path'] = 'var/export'; - $interactiveXml = ''.$nl; + $interactiveXml = '' . $nl; #$interactiveXml .= ' '.$nl; $interactiveXml .= ''; @@ -183,47 +236,59 @@ public function _parseGuiData() } else { $interactiveXml = ''; - $fileXml = ''.$nl; - $fileXml .= ' '.$p['file']['type'].''.$nl; - $fileXml .= ' '.$p['file']['path'].''.$nl; - $fileXml .= ' '.$nl; + $fileXml = '' . $nl; + $fileXml .= ' ' . $p['file']['type'] . '' . $nl; + $fileXml .= ' ' . $p['file']['path'] . '' . $nl; + $fileXml .= ' ' . $nl; if ($p['file']['type']==='ftp') { $hostArr = explode(':', $p['file']['host']); - $fileXml .= ' '.$nl; + $fileXml .= ' ' . $nl; if (isset($hostArr[1])) { - $fileXml .= ' '.$nl; + $fileXml .= ' ' . $nl; } if (!empty($p['file']['passive'])) { - $fileXml .= ' true'.$nl; + $fileXml .= ' true' . $nl; + } + if ((!empty($p['file']['file_mode'])) + && ($p['file']['file_mode'] == FTP_ASCII || $p['file']['file_mode'] == FTP_BINARY) + ) { + $fileXml .= ' ' . $p['file']['file_mode'] . '' . $nl; } if (!empty($p['file']['user'])) { - $fileXml .= ' '.$nl; + $fileXml .= ' ' . $nl; } if (!empty($p['file']['password'])) { - $fileXml .= ' '.$nl; + $fileXml .= ' ' . $nl; } } if ($import) { - $fileXml .= ' '.$nl; + $fileXml .= ' ' . $nl; } - $fileXml .= ''.$nl.$nl; + $fileXml .= '' . $nl . $nl; } switch ($p['parse']['type']) { case 'excel_xml': - $parseFileXml = ''.$nl; - $parseFileXml .= ' '.$nl; + $parseFileXml = '' . $nl; + $parseFileXml .= ' ' . $nl; break; case 'csv': - $parseFileXml = ''.$nl; - $parseFileXml .= ' '.$nl; - $parseFileXml .= ' '.$nl; + $parseFileXml = '' . $nl; + $parseFileXml .= ' ' . $nl; + $parseFileXml .= ' ' . $nl; break; } - $parseFileXml .= ' '.$p['parse']['fieldnames'].''.$nl; + $parseFileXml .= ' ' . $p['parse']['fieldnames'] . '' . $nl; $parseFileXmlInter = $parseFileXml; - $parseFileXml .= ''.$nl.$nl; + $parseFileXml .= '' . $nl . $nl; $mapXml = ''; @@ -240,26 +305,26 @@ public function _parseGuiData() } } } - $mapXml .= ''.$nl; + $mapXml .= '' . $nl; $map = $p['map'][$this->getEntityType()]; - if (sizeof($map['db'])>0) { + if (sizeof($map['db']) > 0) { $from = $map[$import?'file':'db']; $to = $map[$import?'db':'file']; - $mapXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$nl; + $mapXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $nl; foreach ($from as $i=>$f) { - $mapXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$nl; + $mapXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $nl; } - $mapXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$nl; + $mapXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $nl; } if ($p['map']['only_specified']) { - $mapXml .= ' '.$p['map']['only_specified'].''.$nl; - //$mapXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$p['map']['only_specified'].''.$nl; + $mapXml .= ' ' . $p['map']['only_specified'] . '' . $nl; + //$mapXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $p['map']['only_specified'] . '' . $nl; } - $mapXml .= ''.$nl.$nl; + $mapXml .= '' . $nl . $nl; $parsers = array( 'product'=>'catalog/convert_parser_product', @@ -268,22 +333,23 @@ public function _parseGuiData() if ($import) { // if ($this->getDataTransfer()==='interactive') { - $parseFileXmlInter .= ' getStoreId().']]>'.$nl; + $parseFileXmlInter .= ' getStoreId() . ']]>' . $nl; // } else { -// $parseDataXml = ''.$nl; -// $parseDataXml = ' getStoreId().']]>'.$nl; +// $parseDataXml = '' . $nl; +// $parseDataXml = ' getStoreId() . ']]>' . $nl; // $parseDataXml .= ''.$nl.$nl; // } // $parseDataXml = ''.$nl; // $parseDataXml .= ' getStoreId().']]>'.$nl; // $parseDataXml .= ''.$nl.$nl; } else { - $parseDataXml = ''.$nl; - $parseDataXml .= ' getStoreId().']]>'.$nl; + $parseDataXml = '' . $nl; + $parseDataXml .= ' getStoreId() . ']]>' . $nl; if (isset($p['export']['add_url_field'])) { - $parseDataXml .= ' '.$nl; + $parseDataXml .= ' ' . $nl; } - $parseDataXml .= ''.$nl.$nl; + $parseDataXml .= '' . $nl . $nl; } $adapters = array( @@ -292,64 +358,66 @@ public function _parseGuiData() ); if ($import) { - $entityXml = ''.$nl; - $entityXml .= ' getStoreId().']]>'.$nl; - $entityXml .= ''.$nl.$nl; + $entityXml = '' . $nl; + $entityXml .= ' getStoreId() . ']]>' . $nl; + $entityXml .= '' . $nl . $nl; } else { - $entityXml = ''.$nl; - $entityXml .= ' getStoreId().']]>'.$nl; + $entityXml = '' . $nl; + $entityXml .= ' getStoreId() . ']]>' . $nl; foreach ($p[$this->getEntityType()]['filter'] as $f=>$v) { if (empty($v)) { continue; } if (is_scalar($v)) { - $entityXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$nl; + $entityXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $nl; } elseif (is_array($v)) { foreach ($v as $a=>$b) { if (strlen($b) == 0) { continue; } - $entityXml .= ' '.$nl; - $parseFileXmlInter .= ' '.$nl; + $entityXml .= ' ' . $nl; + $parseFileXmlInter .= ' ' . $nl; } } } - $entityXml .= ''.$nl.$nl; + $entityXml .= '' . $nl . $nl; } // Need to rewrite the whole xml action format if ($import) { $numberOfRecords = isset($p['import']['number_of_records']) ? $p['import']['number_of_records'] : 1; - $decimalSeparator = isset($p['import']['decimal_separator']) ? $p['import']['decimal_separator'] : '.'; + $decimalSeparator = isset($p['import']['decimal_separator']) ? $p['import']['decimal_separator'] : ' . '; $parseFileXmlInter .= ' ' . $numberOfRecords . '' . $nl; $parseFileXmlInter .= ' ' . $nl; if ($this->getDataTransfer()==='interactive') { $xml = $parseFileXmlInter; - $xml .= ' '.$adapters[$this->getEntityType()].''.$nl; - $xml .= ' parse'.$nl; + $xml .= ' ' . $adapters[$this->getEntityType()] . '' . $nl; + $xml .= ' parse' . $nl; $xml .= ''; } else { $xml = $fileXml; $xml .= $parseFileXmlInter; - $xml .= ' '.$adapters[$this->getEntityType()].''.$nl; - $xml .= ' parse'.$nl; + $xml .= ' ' . $adapters[$this->getEntityType()] . '' . $nl; + $xml .= ' parse' . $nl; $xml .= ''; } //$xml = $interactiveXml.$fileXml.$parseFileXml.$mapXml.$parseDataXml.$entityXml; } else { - $xml = $entityXml.$parseDataXml.$mapXml.$parseFileXml.$fileXml.$interactiveXml; + $xml = $entityXml . $parseDataXml . $mapXml . $parseFileXml . $fileXml . $interactiveXml; } $this->setGuiData($p); $this->setActionsXml($xml); -/*echo "
    ".print_r($p,1)."
    "; -echo "".$xml.""; +/*echo "
    " . print_r($p,1) . "
    "; +echo "" . $xml . ""; die;*/ return $this; } diff --git a/app/code/core/Mage/Dataflow/Model/Profile/History.php b/app/code/core/Mage/Dataflow/Model/Profile/History.php index 347876ab..65bee5e9 100644 --- a/app/code/core/Mage/Dataflow/Model/Profile/History.php +++ b/app/code/core/Mage/Dataflow/Model/Profile/History.php @@ -20,13 +20,26 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Convert history * + * @method Mage_Dataflow_Model_Resource_Profile_History _getResource() + * @method Mage_Dataflow_Model_Resource_Profile_History getResource() + * @method int getProfileId() + * @method Mage_Dataflow_Model_Profile_History setProfileId(int $value) + * @method string getActionCode() + * @method Mage_Dataflow_Model_Profile_History setActionCode(string $value) + * @method int getUserId() + * @method Mage_Dataflow_Model_Profile_History setUserId(int $value) + * @method string getPerformedAt() + * @method Mage_Dataflow_Model_Profile_History setPerformedAt(string $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Profile_History extends Mage_Core_Model_Abstract @@ -44,8 +57,9 @@ protected function _beforeSave() $this->setProfileId($profile->getId()); } } - if (!$this->getUserId()) { - $this->setUserId(Mage::getSingleton('admin/session')->getUser()->getId()); + + if(!$this->hasData('user_id')) { + $this->setUserId(0); } parent::_beforeSave(); diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Batch.php b/app/code/core/Mage/Dataflow/Model/Resource/Batch.php new file mode 100755 index 00000000..9886f2a1 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Batch.php @@ -0,0 +1,45 @@ + + */ +class Mage_Dataflow_Model_Resource_Batch extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/batch', 'batch_id'); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Batch/Abstract.php b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Abstract.php new file mode 100755 index 00000000..e9a9055a --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Abstract.php @@ -0,0 +1,72 @@ + + */ +abstract class Mage_Dataflow_Model_Resource_Batch_Abstract extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Retrieve Id collection + * + * @param Mage_Dataflow_Model_Batch_Abstract $object + * @return array + */ + public function getIdCollection(Mage_Dataflow_Model_Batch_Abstract $object) + { + if (!$object->getBatchId()) { + return array(); + } + + $ids = array(); + $select = $this->_getWriteAdapter()->select() + ->from($this->getMainTable(), array($this->getIdFieldName())) + ->where('batch_id = :batch_id'); + $ids = $this->_getWriteAdapter()->fetchCol($select, array('batch_id' => $object->getBatchId())); + return $ids; + } + + /** + * Delete current Batch collection + * + * @param Mage_Dataflow_Model_Batch_Abstract $object + * @return Mage_Dataflow_Model_Resource_Batch_Abstract + */ + public function deleteCollection(Mage_Dataflow_Model_Batch_Abstract $object) + { + if (!$object->getBatchId()) { + return $this; + } + + $this->_getWriteAdapter()->delete($this->getMainTable(), array('batch_id=?' => $object->getBatchId())); + return $this; + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Batch/Collection.php b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Collection.php new file mode 100755 index 00000000..291ad619 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Collection.php @@ -0,0 +1,59 @@ + + */ +class Mage_Dataflow_Model_Resource_Batch_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Init model + * + */ + protected function _construct() + { + $this->_init('dataflow/batch'); + } + + /** + * Add expire filter (for abandoned batches) + * + */ + public function addExpireFilter() + { + $date = Mage::getSingleton('core/date'); + /* @var $date Mage_Core_Model_Date */ + $lifetime = Mage_Dataflow_Model_Batch::LIFETIME; + $expire = $date->gmtDate(null, $date->timestamp() - $lifetime); + + $this->getSelect()->where('created_at < ?', $expire); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Batch/Export.php b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Export.php new file mode 100755 index 00000000..55126ac1 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Export.php @@ -0,0 +1,45 @@ + + */ +class Mage_Dataflow_Model_Resource_Batch_Export extends Mage_Dataflow_Model_Resource_Batch_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/batch_export', 'batch_export_id'); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Batch/Import.php b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Import.php new file mode 100755 index 00000000..6a769562 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Batch/Import.php @@ -0,0 +1,45 @@ + + */ +class Mage_Dataflow_Model_Resource_Batch_Import extends Mage_Dataflow_Model_Resource_Batch_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/batch_import', 'batch_import_id'); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Import.php b/app/code/core/Mage/Dataflow/Model/Resource/Import.php new file mode 100755 index 00000000..16dc0be3 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Import.php @@ -0,0 +1,130 @@ + + */ +class Mage_Dataflow_Model_Resource_Import extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/import', 'import_id'); + } + + /** + * Returns all import data select by session id + * + * @param int $sessionId + * @return Varien_Db_Select + */ + public function select($sessionId) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where('session_id=?', $sessionId) + ->where('status=?', 0); + return $select; + } + + /** + * Load all import data by session id + * + * @param int $sessionId + * @param int $min + * @param int $max + * @return array + */ + public function loadBySessionId($sessionId, $min = 0, $max = 100) + { + if (!is_numeric($min) || !is_numeric($max)) { + return array(); + } + $bind = array( + 'status' => 0, + 'session_id' => $sessionId, + 'min_id' => (int)$min, + 'max_id' => (int)$max, + ); + $read = $this->_getReadAdapter(); + $select = $read->select() + ->from($this->getTable('dataflow/import')) + ->where('import_id >= :min_id') + ->where('import_id >= :max_id') + ->where('status= :status') + ->where('session_id = :session_id'); + return $read->fetchAll($select, $bind); + } + + /** + * Load total import data by session id + * + * @param int $sessionId + * @return array + */ + public function loadTotalBySessionId($sessionId) + { + $bind = array( + 'status' => 0, + 'session_id' => $sessionId + ); + $read = $this->_getReadAdapter(); + $select = $read->select() + ->from($this->getTable('dataflow/import'), + array('max'=>'max(import_id)', 'min'=>'min(import_id)', 'cnt'=>'count(*)')) + ->where('status = :status') + ->where('session_id = :$session_id'); + return $read->fetchRow($select, $bind); + } + + /** + * Load import data by id + * + * @param int $importId + * @return array + */ + public function loadById($importId) + { + $bind = array( + 'status' => 0, + 'import_id' => $importId + ); + $read = $this->_getReadAdapter(); + $select = $read->select() + ->from($this->getTable('dataflow/import')) + ->where('status = :status') + ->where('import_id = :import_id'); + return $read->fetchRow($select, $bind); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Import/Collection.php b/app/code/core/Mage/Dataflow/Model/Resource/Import/Collection.php new file mode 100755 index 00000000..cd562cd2 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Import/Collection.php @@ -0,0 +1,45 @@ + + */ +class Mage_Dataflow_Model_Resource_Import_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model and model + * + */ + protected function _construct() + { + $this->_init('dataflow/import'); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Profile.php b/app/code/core/Mage/Dataflow/Model/Resource/Profile.php new file mode 100755 index 00000000..e34d387e --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Profile.php @@ -0,0 +1,81 @@ + + */ +class Mage_Dataflow_Model_Resource_Profile extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/profile', 'profile_id'); + } + + /** + * Setting up created_at and updarted_at + * + * @param Mage_Core_Model_Abstract $object + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getCreatedAt()) { + $object->setCreatedAt($this->formatDate(time())); + } + $object->setUpdatedAt($this->formatDate(time())); + parent::_beforeSave($object); + } + + /** + * Returns true if profile with name exists + * + * @param string $name + * @param int $id + * @return bool + */ + public function isProfileExists($name, $id = null) + { + $bind = array('name' => $name); + $select = $this->_getReadAdapter()->select(); + $select + ->from($this->getMainTable(), 'count(1)') + ->where('name = :name'); + if ($id) { + $select->where("{$this->getIdFieldName()} != :id"); + $bind['id'] = $id; + } + $result = $this->_getReadAdapter()->fetchOne($select, $bind) ? true : false; + return $result; + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Profile/Collection.php b/app/code/core/Mage/Dataflow/Model/Resource/Profile/Collection.php new file mode 100755 index 00000000..e43f33f2 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Profile/Collection.php @@ -0,0 +1,58 @@ + + */ +class Mage_Dataflow_Model_Resource_Profile_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model and model + * + */ + protected function _construct() + { + $this->_init('dataflow/profile'); + } + + /** + * Filter collection by specified store ids + * + * @param array|int $storeIds + * @return Mage_Dataflow_Model_Resource_Profile_Collection + */ + public function addStoreFilter($storeIds) + { + $this->getSelect() + ->where('main_table.store_id IN (?)', array(0, $storeIds)); + return $this; + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Profile/History.php b/app/code/core/Mage/Dataflow/Model/Resource/Profile/History.php new file mode 100755 index 00000000..c5255314 --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Profile/History.php @@ -0,0 +1,60 @@ + + */ +class Mage_Dataflow_Model_Resource_Profile_History extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/profile_history', 'history_id'); + } + + /** + * Sets up performed at time if needed + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Dataflow_Model_Resource_Profile_History + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getPerformedAt()) { + $object->setPerformedAt($this->formatDate(time())); + } + parent::_beforeSave($object); + return $this; + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Profile/History/Collection.php b/app/code/core/Mage/Dataflow/Model/Resource/Profile/History/Collection.php new file mode 100755 index 00000000..dafe0b6c --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Profile/History/Collection.php @@ -0,0 +1,60 @@ + + */ +class Mage_Dataflow_Model_Resource_Profile_History_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define resource model and model + * + */ + protected function _construct() + { + $this->_init('dataflow/profile_history'); + } + + /** + * Joins admin data to select + * + * @return Mage_Dataflow_Model_Resource_Profile_History_Collection + */ + public function joinAdminUser() + { + $this->getSelect()->join( + array('u' => $this->getTable('admin/user')), + 'u.user_id=main_table.user_id', + array('firstname', 'lastname') + ); + return $this; + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Resource/Session.php b/app/code/core/Mage/Dataflow/Model/Resource/Session.php new file mode 100755 index 00000000..6e34db5a --- /dev/null +++ b/app/code/core/Mage/Dataflow/Model/Resource/Session.php @@ -0,0 +1,45 @@ + + */ +class Mage_Dataflow_Model_Resource_Session extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('dataflow/session', 'session_id'); + } +} diff --git a/app/code/core/Mage/Dataflow/Model/Session.php b/app/code/core/Mage/Dataflow/Model/Session.php index e65eac0f..f688ea02 100644 --- a/app/code/core/Mage/Dataflow/Model/Session.php +++ b/app/code/core/Mage/Dataflow/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,23 @@ /** * DataFlow Session Model * - * @category Mage - * @package Mage_Dataflow + * @method Mage_Dataflow_Model_Resource_Session _getResource() + * @method Mage_Dataflow_Model_Resource_Session getResource() + * @method int getUserId() + * @method Mage_Dataflow_Model_Session setUserId(int $value) + * @method string getCreatedDate() + * @method Mage_Dataflow_Model_Session setCreatedDate(string $value) + * @method string getFile() + * @method Mage_Dataflow_Model_Session setFile(string $value) + * @method string getType() + * @method Mage_Dataflow_Model_Session setType(string $value) + * @method string getDirection() + * @method Mage_Dataflow_Model_Session setDirection(string $value) + * @method string getComment() + * @method Mage_Dataflow_Model_Session setComment(string $value) + * + * @category Mage + * @package Mage_Dataflow * @author Magento Core Team */ class Mage_Dataflow_Model_Session extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Dataflow/Model/Session/Adapter/Http.php b/app/code/core/Mage/Dataflow/Model/Session/Adapter/Http.php index 72b1cdce..c1c9ff52 100644 --- a/app/code/core/Mage/Dataflow/Model/Session/Adapter/Http.php +++ b/app/code/core/Mage/Dataflow/Model/Session/Adapter/Http.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -44,9 +44,9 @@ public function load() setData(file_get_contents($_FILES['io_file']['tmp_name'])); - $uploader = new Varien_File_Uploader('io_file'); + $uploader = new Mage_Core_Model_File_Uploader('io_file'); $uploader->setAllowedExtensions(array('csv','xml')); $path = Mage::app()->getConfig()->getTempVarDir().'/import/'; $uploader->save($path); diff --git a/app/code/core/Mage/Dataflow/Model/Session/Parser/Csv.php b/app/code/core/Mage/Dataflow/Model/Session/Parser/Csv.php index 0d8ef121..6c32f6d6 100644 --- a/app/code/core/Mage/Dataflow/Model/Session/Parser/Csv.php +++ b/app/code/core/Mage/Dataflow/Model/Session/Parser/Csv.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/etc/config.xml b/app/code/core/Mage/Dataflow/etc/config.xml index bcd4af63..a2f58710 100644 --- a/app/code/core/Mage/Dataflow/etc/config.xml +++ b/app/code/core/Mage/Dataflow/etc/config.xml @@ -21,24 +21,25 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.4 + 1.6.0.0 Mage_Dataflow_Model - dataflow_mysql4 + dataflow_resource - - Mage_Dataflow_Model_Mysql4 + + Mage_Dataflow_Model_Resource + dataflow_mysql4 dataflow_session
    @@ -62,7 +63,7 @@ dataflow_batch_import
    -
    +
    @@ -72,4 +73,13 @@
    + + + + + dataflow.xml + + + +
    diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/install-1.6.0.0.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/install-1.6.0.0.php new file mode 100644 index 00000000..6c7a6114 --- /dev/null +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/install-1.6.0.0.php @@ -0,0 +1,260 @@ +startSetup(); + +/** + * Create table 'dataflow/session' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/session')) + ->addColumn('session_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Session Id') + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + ), 'User Id') + ->addColumn('created_date', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Created Date') + ->addColumn('file', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'File') + ->addColumn('type', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'Type') + ->addColumn('direction', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + ), 'Direction') + ->addColumn('comment', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Comment') + ->setComment('Dataflow Session'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/import' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/import')) + ->addColumn('import_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Import Id') + ->addColumn('session_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + ), 'Session Id') + ->addColumn('serial_number', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Serial Number') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Value') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Status') + ->addIndex($installer->getIdxName('dataflow/import', array('session_id')), + array('session_id')) + ->addForeignKey($installer->getFkName('dataflow/import', 'session_id', 'dataflow/session', 'session_id'), + 'session_id', $installer->getTable('dataflow/session'), 'session_id', + Varien_Db_Ddl_Table::ACTION_NO_ACTION, Varien_Db_Ddl_Table::ACTION_NO_ACTION) + ->setComment('Dataflow Import Data'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/profile' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/profile')) + ->addColumn('profile_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Profile Id') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Name') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Created At') + ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Updated At') + ->addColumn('actions_xml', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Actions Xml') + ->addColumn('gui_data', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Gui Data') + ->addColumn('direction', Varien_Db_Ddl_Table::TYPE_TEXT, 6, array( + ), 'Direction') + ->addColumn('entity_type', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Entity Type') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('data_transfer', Varien_Db_Ddl_Table::TYPE_TEXT, 11, array( + ), 'Data Transfer') + ->setComment('Dataflow Profile'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/profile_history' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/profile_history')) + ->addColumn('history_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'History Id') + ->addColumn('profile_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Profile Id') + ->addColumn('action_code', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + ), 'Action Code') + ->addColumn('user_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'User Id') + ->addColumn('performed_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Performed At') + ->addIndex($installer->getIdxName('dataflow/profile_history', array('profile_id')), + array('profile_id')) + ->addForeignKey($installer->getFkName('dataflow/profile_history', 'profile_id', 'dataflow/profile', 'profile_id'), + 'profile_id', $installer->getTable('dataflow/profile'), 'profile_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Dataflow Profile History'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/batch' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/batch')) + ->addColumn('batch_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Batch Id') + ->addColumn('profile_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Profile ID') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('adapter', Varien_Db_Ddl_Table::TYPE_TEXT, 128, array( + ), 'Adapter') + ->addColumn('params', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Parameters') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Created At') + ->addIndex($installer->getIdxName('dataflow/batch', array('profile_id')), + array('profile_id')) + ->addIndex($installer->getIdxName('dataflow/batch', array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName('dataflow/batch', array('created_at')), + array('created_at')) + ->addForeignKey($installer->getFkName('dataflow/batch', 'profile_id', 'dataflow/profile', 'profile_id'), + 'profile_id', $installer->getTable('dataflow/profile'), 'profile_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_NO_ACTION) + ->addForeignKey($installer->getFkName('dataflow/batch', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_NO_ACTION) + ->setComment('Dataflow Batch'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/batch_export' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/batch_export')) + ->addColumn('batch_export_id', Varien_Db_Ddl_Table::TYPE_BIGINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Batch Export Id') + ->addColumn('batch_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Batch Id') + ->addColumn('batch_data', Varien_Db_Ddl_Table::TYPE_TEXT, '2G', array( + ), 'Batch Data') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Status') + ->addIndex($installer->getIdxName('dataflow/batch_export', array('batch_id')), + array('batch_id')) + ->addForeignKey($installer->getFkName('dataflow/batch_export', 'batch_id', 'dataflow/batch', 'batch_id'), + 'batch_id', $installer->getTable('dataflow/batch'), 'batch_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_NO_ACTION) + ->setComment('Dataflow Batch Export'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'dataflow/batch_import' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('dataflow/batch_import')) + ->addColumn('batch_import_id', Varien_Db_Ddl_Table::TYPE_BIGINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Batch Import Id') + ->addColumn('batch_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Batch Id') + ->addColumn('batch_data', Varien_Db_Ddl_Table::TYPE_TEXT, '2G', array( + ), 'Batch Data') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Status') + ->addIndex($installer->getIdxName('dataflow/batch_import', array('batch_id')), + array('batch_id')) + ->addForeignKey($installer->getFkName('dataflow/batch_import', 'batch_id', 'dataflow/batch', 'batch_id'), + 'batch_id', $installer->getTable('dataflow/batch'), 'batch_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_NO_ACTION) + ->setComment('Dataflow Batch Import'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-install-0.7.0.php index 5186e591..e3830c07 100644 --- a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.0-0.7.1.php index 4ed37a99..18c7f2fa 100644 --- a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ $this->startSetup()->run(" diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.1-0.7.2.php index e9a17d47..136a3e49 100755 --- a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ $this->startSetup()->run(" diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.2-0.7.3.php index 341d5f9b..fc5cc7d7 100644 --- a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.2-0.7.3.php +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.3-0.7.4.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.3-0.7.4.php index 2d608845..b22b0b35 100644 --- a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.3-0.7.4.php +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-0.7.3-0.7.4.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Dataflow - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..a0e8d8d9 --- /dev/null +++ b/app/code/core/Mage/Dataflow/sql/dataflow_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,492 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow/batch'), + 'FK_DATAFLOW_BATCH_PROFILE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow/batch'), + 'FK_DATAFLOW_BATCH_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow/batch_export'), + 'FK_DATAFLOW_BATCH_EXPORT_BATCH' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow/batch_import'), + 'FK_DATAFLOW_BATCH_IMPORT_BATCH' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow_import_data'), + 'FK_DATAFLOW_IMPORT_DATA' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('dataflow/profile_history'), + 'FK_DATAFLOW_PROFILE_HISTORY' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/batch'), + 'FK_DATAFLOW_BATCH_PROFILE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/batch'), + 'FK_DATAFLOW_BATCH_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/batch'), + 'IDX_CREATED_AT' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/batch_export'), + 'FK_DATAFLOW_BATCH_EXPORT_BATCH' +); +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/batch_import'), + 'FK_DATAFLOW_BATCH_IMPORT_BATCH' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/import'), + 'FK_DATAFLOW_IMPORT_DATA' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('dataflow/profile_history'), + 'FK_DATAFLOW_PROFILE_HISTORY' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('dataflow/session') => array( + 'columns' => array( + 'session_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Session Id' + ), + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'comment' => 'User Id' + ), + 'created_date' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Created Date' + ), + 'file' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'File' + ), + 'type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'Type' + ), + 'direction' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'comment' => 'Direction' + ), + 'comment' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Comment' + ) + ), + 'comment' => 'Dataflow Session' + ), + $installer->getTable('dataflow/import') => array( + 'columns' => array( + 'import_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Import Id' + ), + 'session_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'comment' => 'Session Id' + ), + 'serial_number' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Serial Number' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Value' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Status' + ) + ), + 'comment' => 'Dataflow Import Data' + ), + $installer->getTable('dataflow/profile') => array( + 'columns' => array( + 'profile_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Profile Id' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Name' + ), + 'created_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Created At' + ), + 'updated_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Updated At' + ), + 'actions_xml' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Actions Xml' + ), + 'gui_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Gui Data' + ), + 'direction' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 6, + 'comment' => 'Direction' + ), + 'entity_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Entity Type' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'data_transfer' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 11, + 'comment' => 'Data Transfer' + ) + ), + 'comment' => 'Dataflow Profile' + ), + $installer->getTable('dataflow/profile_history') => array( + 'columns' => array( + 'history_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'History Id' + ), + 'profile_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Profile Id' + ), + 'action_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Action Code' + ), + 'user_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'User Id' + ), + 'performed_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Performed At' + ) + ), + 'comment' => 'Dataflow Profile History' + ), + $installer->getTable('dataflow/batch') => array( + 'columns' => array( + 'batch_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Batch Id' + ), + 'profile_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Profile ID' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'adapter' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 128, + 'comment' => 'Adapter' + ), + 'params' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Parameters' + ), + 'created_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Created At' + ) + ), + 'comment' => 'Dataflow Batch' + ), + $installer->getTable('dataflow/batch_export') => array( + 'columns' => array( + 'batch_export_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BIGINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Batch Export Id' + ), + 'batch_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Batch Id' + ), + 'batch_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2G', + 'comment' => 'Batch Data' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Status' + ) + ), + 'comment' => 'Dataflow Batch Export' + ), + $installer->getTable('dataflow/batch_import') => array( + 'columns' => array( + 'batch_import_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BIGINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Batch Import Id' + ), + 'batch_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Batch Id' + ), + 'batch_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2G', + 'comment' => 'Batch Data' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Status' + ) + ), + 'comment' => 'Dataflow Batch Import' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/batch'), + $installer->getIdxName('dataflow/batch', array('profile_id')), + array('profile_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/batch'), + $installer->getIdxName('dataflow/batch', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/batch'), + $installer->getIdxName('dataflow/batch', array('created_at')), + array('created_at') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/batch_export'), + $installer->getIdxName('dataflow/batch_export', array('batch_id')), + array('batch_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/batch_import'), + $installer->getIdxName('dataflow/batch_import', array('batch_id')), + array('batch_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/import'), + $installer->getIdxName('dataflow/import', array('session_id')), + array('session_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('dataflow/profile_history'), + $installer->getIdxName('dataflow/profile_history', array('profile_id')), + array('profile_id') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/batch', 'profile_id', 'dataflow/profile', 'profile_id'), + $installer->getTable('dataflow/batch'), + 'profile_id', + $installer->getTable('dataflow/profile'), + 'profile_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_NO_ACTION +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/batch', 'store_id', 'core/store', 'store_id'), + $installer->getTable('dataflow/batch'), + 'store_id', + $installer->getTable('core/store'), + 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_NO_ACTION +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/batch_import', 'batch_id', 'dataflow/batch', 'batch_id'), + $installer->getTable('dataflow/batch_import'), + 'batch_id', + $installer->getTable('dataflow/batch'), + 'batch_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_NO_ACTION +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/batch_export', 'batch_id', 'dataflow/batch', 'batch_id'), + $installer->getTable('dataflow/batch_export'), + 'batch_id', + $installer->getTable('dataflow/batch'), + 'batch_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, + Varien_Db_Ddl_Table::ACTION_NO_ACTION +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/import', 'session_id', 'dataflow/session', 'session_id'), + $installer->getTable('dataflow/import'), + 'session_id', + $installer->getTable('dataflow/session'), + 'session_id', + Varien_Db_Ddl_Table::ACTION_NO_ACTION, + Varien_Db_Ddl_Table::ACTION_NO_ACTION +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('dataflow/profile_history', 'profile_id', 'dataflow/profile', 'profile_id'), + $installer->getTable('dataflow/profile_history'), + 'profile_id', + $installer->getTable('dataflow/profile'), + 'profile_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Currency/Base.php b/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Currency/Base.php index 8b939ccb..20400402 100644 --- a/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Currency/Base.php +++ b/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Currency/Base.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Region/Updater.php b/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Region/Updater.php index 8baf84cb..b519155d 100644 --- a/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Region/Updater.php +++ b/app/code/core/Mage/Directory/Block/Adminhtml/Frontend/Region/Updater.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Block/Currency.php b/app/code/core/Mage/Directory/Block/Currency.php index 440fa708..920507a6 100644 --- a/app/code/core/Mage/Directory/Block/Currency.php +++ b/app/code/core/Mage/Directory/Block/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Block/Data.php b/app/code/core/Mage/Directory/Block/Data.php index a0bb33f7..386f83a4 100644 --- a/app/code/core/Mage/Directory/Block/Data.php +++ b/app/code/core/Mage/Directory/Block/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -109,7 +109,7 @@ public function getRegionHtmlSelect() ->setTitle(Mage::helper('directory')->__('State/Province')) ->setId('state') ->setClass('required-entry validate-state') - ->setValue($this->getRegionId()) + ->setValue(intval($this->getRegionId())) ->setOptions($options) ->getHtml(); Varien_Profiler::start('TEST: '.__METHOD__); @@ -120,7 +120,7 @@ public function getCountryId() { $countryId = $this->getData('country_id'); if (is_null($countryId)) { - $countryId = Mage::getStoreConfig('general/country/default'); + $countryId = Mage::helper('core')->getDefaultCountry(); } return $countryId; } diff --git a/app/code/core/Mage/Directory/Exception.php b/app/code/core/Mage/Directory/Exception.php index ab6db418..abf8a46b 100644 --- a/app/code/core/Mage/Directory/Exception.php +++ b/app/code/core/Mage/Directory/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Helper/Data.php b/app/code/core/Mage/Directory/Helper/Data.php index 86950b4e..2ac0cf98 100644 --- a/app/code/core/Mage/Directory/Helper/Data.php +++ b/app/code/core/Mage/Directory/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,12 +31,61 @@ */ class Mage_Directory_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * Config value that lists ISO2 country codes which have optional Zip/Postal pre-configured + */ + const OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH = 'general/country/optional_zip_countries'; + + /* + * Path to config value, which lists countries, for which state is required. + */ + const XML_PATH_STATES_REQUIRED = 'general/region/state_required'; + + /* + * Path to config value, which detects whether or not display the state for the country, if it is not required + */ + const XML_PATH_DISPLAY_ALL_STATES = 'general/region/display_all'; + + /** + * Country collection + * + * @var Mage_Directory_Model_Resource_Country_Collection + */ protected $_countryCollection; + + /** + * Region collection + * + * @var Mage_Directory_Model_Resource_Region_Collection + */ protected $_regionCollection; + + /** + * Json representation of regions data + * + * @var string + */ protected $_regionJson; + + /** + * Currency cache + * + * @var array + */ protected $_currencyCache = array(); + + /** + * ISO2 country codes which have optional Zip/Postal pre-configured + * + * @var array + */ protected $_optionalZipCountries = null; + /** + * Retrieve region collection + * + * @return Mage_Directory_Model_Resource_Region_Collection + */ public function getRegionCollection() { if (!$this->_regionCollection) { @@ -47,6 +96,11 @@ public function getRegionCollection() return $this->_regionCollection; } + /** + * Retrieve country collection + * + * @return Mage_Directory_Model_Resource_Country_Collection + */ public function getCountryCollection() { if (!$this->_countryCollection) { @@ -78,14 +132,19 @@ public function getRegionJson() $collection = Mage::getModel('directory/region')->getResourceCollection() ->addCountryFilter($countryIds) ->load(); - $regions = array(); + $regions = array( + 'config' => array( + 'show_all_regions' => $this->getShowNonRequiredState(), + 'regions_required' => $this->getCountriesWithStatesRequired() + ) + ); foreach ($collection as $region) { if (!$region->getRegionId()) { continue; } $regions[$region->getCountryId()][$region->getRegionId()] = array( - 'code'=>$region->getCode(), - 'name'=>$region->getName() + 'code' => $region->getCode(), + 'name' => $this->__($region->getName()) ); } $json = Mage::helper('core')->jsonEncode($regions); @@ -101,7 +160,15 @@ public function getRegionJson() return $this->_regionJson; } - public function currencyConvert($amount, $from, $to=null) + /** + * Convert currency + * + * @param float $amount + * @param string $from + * @param string $to + * @return float + */ + public function currencyConvert($amount, $from, $to = null) { if (empty($this->_currencyCache[$from])) { $this->_currencyCache[$from] = Mage::getModel('directory/currency')->load($from); @@ -117,14 +184,13 @@ public function currencyConvert($amount, $from, $to=null) * Return ISO2 country codes, which have optional Zip/Postal pre-configured * * @param bool $asJson - * @return array + * @return array|string */ public function getCountriesWithOptionalZip($asJson = false) { if (null === $this->_optionalZipCountries) { - $this->_optionalZipCountries = preg_split('/\,/', Mage::getStoreConfig('general/country/optional_zip_countries'), - 0, PREG_SPLIT_NO_EMPTY - ); + $this->_optionalZipCountries = preg_split('/\,/', + Mage::getStoreConfig(self::OPTIONAL_ZIP_COUNTRIES_CONFIG_PATH), 0, PREG_SPLIT_NO_EMPTY); } if ($asJson) { return Mage::helper('core')->jsonEncode($this->_optionalZipCountries); @@ -136,10 +202,51 @@ public function getCountriesWithOptionalZip($asJson = false) * Check whether zip code is optional for specified country code * * @param string $countryCode + * @return boolean */ public function isZipCodeOptional($countryCode) { $this->getCountriesWithOptionalZip(); return in_array($countryCode, $this->_optionalZipCountries); } + + /** + * Returns the list of countries, for which region is required + * + * @param boolean $asJson + * @return array + */ + public function getCountriesWithStatesRequired($asJson = false) + { + $countryList = explode(',', Mage::getStoreConfig(self::XML_PATH_STATES_REQUIRED)); + if ($asJson) { + return Mage::helper('core')->jsonEncode($countryList); + } + return $countryList; + } + + /** + * Return flag, which indicates whether or not non required state should be shown + * + * @return bool + */ + public function getShowNonRequiredState() + { + return (boolean)Mage::getStoreConfig(self::XML_PATH_DISPLAY_ALL_STATES); + } + + /** + * Returns flag, which indicates whether region is required for specified country + * + * @param string $countryId + * @return bool + */ + public function isRegionRequired($countryId) + { + $countyList = $this->getCountriesWithStatesRequired(); + if(!is_array($countyList)) { + return false; + } + return in_array($countryId, $countyList); + } } diff --git a/app/code/core/Mage/Directory/Helper/Url.php b/app/code/core/Mage/Directory/Helper/Url.php index b0ee56e5..77ec935c 100644 --- a/app/code/core/Mage/Directory/Helper/Url.php +++ b/app/code/core/Mage/Directory/Helper/Url.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Country.php b/app/code/core/Mage/Directory/Model/Country.php index 1591f79a..aefed9a2 100644 --- a/app/code/core/Mage/Directory/Model/Country.php +++ b/app/code/core/Mage/Directory/Model/Country.php @@ -20,13 +20,24 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Country model * + * @method Mage_Directory_Model_Resource_Country _getResource() + * @method Mage_Directory_Model_Resource_Country getResource() + * @method string getCountryId() + * @method Mage_Directory_Model_Country setCountryId(string $value) + * @method string getIso2Code() + * @method Mage_Directory_Model_Country setIso2Code(string $value) + * @method string getIso3Code() + * @method Mage_Directory_Model_Country setIso3Code(string $value) + * + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Country extends Mage_Core_Model_Abstract @@ -104,7 +115,7 @@ public function formatAddress(Varien_Object $address, $html=false) /** * Retrive formats for * - * @return Mage_Directory_Model_Mysql4_Country_Format_Collection + * @return Mage_Directory_Model_Resource_Country_Format_Collection */ public function getFormats() { diff --git a/app/code/core/Mage/Directory/Model/Country/Api.php b/app/code/core/Mage/Directory/Model/Country/Api.php index 857ebbd9..66dbb144 100644 --- a/app/code/core/Mage/Directory/Model/Country/Api.php +++ b/app/code/core/Mage/Directory/Model/Country/Api.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Country/Api/V2.php b/app/code/core/Mage/Directory/Model/Country/Api/V2.php index 73291998..4723d03b 100644 --- a/app/code/core/Mage/Directory/Model/Country/Api/V2.php +++ b/app/code/core/Mage/Directory/Model/Country/Api/V2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Country/Format.php b/app/code/core/Mage/Directory/Model/Country/Format.php index cebebbd8..32477c39 100644 --- a/app/code/core/Mage/Directory/Model/Country/Format.php +++ b/app/code/core/Mage/Directory/Model/Country/Format.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,8 +28,17 @@ /** * Directory country format model * - * @category Mage - * @package Mage_Directory + * @method Mage_Directory_Model_Resource_Country_Format _getResource() + * @method Mage_Directory_Model_Resource_Country_Format getResource() + * @method string getCountryId() + * @method Mage_Directory_Model_Country_Format setCountryId(string $value) + * @method string getType() + * @method Mage_Directory_Model_Country_Format setType(string $value) + * @method string getFormat() + * @method Mage_Directory_Model_Country_Format setFormat(string $value) + * + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Country_Format extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Directory/Model/Currency.php b/app/code/core/Mage/Directory/Model/Currency.php index 197994bc..c5beff74 100644 --- a/app/code/core/Mage/Directory/Model/Currency.php +++ b/app/code/core/Mage/Directory/Model/Currency.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Currency/Filter.php b/app/code/core/Mage/Directory/Model/Currency/Filter.php index c1e85d3c..ba92435a 100644 --- a/app/code/core/Mage/Directory/Model/Currency/Filter.php +++ b/app/code/core/Mage/Directory/Model/Currency/Filter.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Currency/Import/Abstract.php b/app/code/core/Mage/Directory/Model/Currency/Import/Abstract.php index 1d49b17b..928d068a 100644 --- a/app/code/core/Mage/Directory/Model/Currency/Import/Abstract.php +++ b/app/code/core/Mage/Directory/Model/Currency/Import/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php b/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php index bcfeb4f6..b83bd8ab 100644 --- a/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php +++ b/app/code/core/Mage/Directory/Model/Currency/Import/Webservicex.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Country.php b/app/code/core/Mage/Directory/Model/Mysql4/Country.php index 61ed31d4..cd344018 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Country.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Country.php @@ -20,33 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Directory_Model_Mysql4_Country extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Directory + * @author Magento Core Team + */ +class Mage_Directory_Model_Mysql4_Country extends Mage_Directory_Model_Resource_Country { - protected function _construct() - { - $this->_init('directory/country', 'country_id'); - } - - public function loadByCode(Mage_Directory_Model_Country $country, $code) - { - switch (strlen($code)) { - case 2: - $field = 'iso2_code'; - break; - - case 3: - $field = 'iso3_code'; - break; - - default: - Mage::throwException(Mage::helper('directory')->__('Invalid country code: %s', $code)); - } - $this->load($country, $code, $field); - return $this; - } } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Country/Collection.php b/app/code/core/Mage/Directory/Model/Mysql4/Country/Collection.php index f24650cf..061fc3e3 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Country/Collection.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Country/Collection.php @@ -20,119 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Country collection * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Country_Collection extends Varien_Data_Collection_Db +class Mage_Directory_Model_Mysql4_Country_Collection extends Mage_Directory_Model_Resource_Country_Collection { - protected $_countryTable; - - public function __construct() - { - parent::__construct(Mage::getSingleton('core/resource')->getConnection('directory_read')); - - $this->_countryTable = Mage::getSingleton('core/resource')->getTableName('directory/country'); - - $this->_select->from(array('country'=>$this->_countryTable)); - $this->setItemObjectClass(Mage::getConfig()->getModelClassName('directory/country')); - } - - public function loadByStore() - { - $allowCountries = explode(',', (string)Mage::getStoreConfig('general/country/allow')); - if (!empty($allowCountries)) { - $this->addFieldToFilter("country.country_id", array('in'=>$allowCountries)); - } - - $this->load(); - - return $this; - } - - public function getItemById($countryId) - { - foreach ($this->_items as $country) { - if ($country->getCountryId() == $countryId) { - return $country; - } - } - return Mage::getResourceModel('directory/country'); - } - - public function addCountryCodeFilter($countryCode, $iso=array(0 => 'iso3', 'iso2')) - { - if (!empty($countryCode)) { - $where_expr = ''; - if (is_array($countryCode)) { - if (is_array($iso)) { - $i = 0; - foreach ($iso as $iso_curr) { - $where_expr .= ($i++ > 0 ? ' OR ' : ''); - $where_expr .= "country.{$iso_curr}_code IN ('".implode("','", $countryCode)."')"; - } - } else { - $where_expr = "country.{$iso}_code IN ('".implode("','", $countryCode)."')"; - } - } else { - if (is_array($iso)) { - $i = 0; - foreach ($iso as $iso_curr) { - $where_expr .= ($i++ > 0 ? ' OR ' : ''); - $where_expr = "country.{$iso_curr}_code = '{$countryCode}'"; - } - } else { - $where_expr = "country.{$iso}_code = '{$countryCode}'"; - } - } - $this->_select->where($where_expr); - } - return $this; - } - - public function addCountryIdFilter($countryId) - { - if (!empty($countryId)) { - if (is_array($countryId)) { - $this->_select->where("country.country_id IN ('".implode("','", $countryId)."')"); - } else { - $this->_select->where("country.country_id = '{$countryId}'"); - } - } - return $this; - } - - public function toOptionArray($emptyLabel = ' ') - { - $options = $this->_toOptionArray('country_id', 'name', array('title'=>'iso2_code')); - - $sort = array(); - foreach ($options as $index=>$data) { - $name = Mage::app()->getLocale()->getCountryTranslation($data['value']); - if (!empty($name)) { - $sort[$name] = $data['value']; - } - } - - ksort($sort); - $options = array(); - foreach ($sort as $label=>$value) { - $options[] = array( - 'value' => $value, - 'label' => $label - ); - } - - if (count($options)>0 && $emptyLabel !== false) { - array_unshift($options, array('value'=>'', 'label'=>$emptyLabel)); - } - return $options; - } } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Country/Format.php b/app/code/core/Mage/Directory/Model/Mysql4/Country/Format.php index f1466496..d2cdd811 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Country/Format.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Country/Format.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,28 +28,10 @@ /** * Directory country format resource model * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Country_Format extends Mage_Core_Model_Mysql4_Abstract +class Mage_Directory_Model_Mysql4_Country_Format extends Mage_Directory_Model_Resource_Country_Format { - protected function _construct() - { - $this->_init('directory/country_format', 'country_format_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array(array( - 'field' => array('country_id', 'type'), - 'title' => Mage::helper('directory')->__('Country and Format Type combination should be unique') - )); - return $this; - } } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Country/Format/Collection.php b/app/code/core/Mage/Directory/Model/Mysql4/Country/Format/Collection.php index 336253bf..c0910a53 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Country/Format/Collection.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Country/Format/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,34 +28,11 @@ /** * Directory country format resource model * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Country_Format_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Directory_Model_Mysql4_Country_Format_Collection + extends Mage_Directory_Model_Resource_Country_Format_Collection { - - protected function _construct() - { - $this->_init('directory/country_format'); - } - - /** - * Set country filter - * - * @param string|Mage_Directory_Model_Country $country - * @return Mage_Directory_Model_Mysql4_Country_Format_Collection - */ - public function setCountryFilter($country) - { - if(is_object($country)) { - $countryId = $country->getId(); - } else { - $countryId = $country; - } - - $this->addFieldToFilter('country_id', $countryId); - return $this; - } - } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Currency.php b/app/code/core/Mage/Directory/Model/Mysql4/Currency.php index 1c4a5e57..05f86e60 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Currency.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Currency.php @@ -20,219 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Currency Mysql4 resourcre model * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Currency extends Mage_Core_Model_Mysql4_Abstract +class Mage_Directory_Model_Mysql4_Currency extends Mage_Directory_Model_Resource_Currency { - /** - * Currency rate table - * - * @var string - */ - protected $_currencyRateTable; - - /** - * Currency rate cache array - * - * @var array - */ - protected static $_rateCache; - - protected function _construct() - { - $this->_init('directory/currency', 'currency_code'); - } - - public function __construct() - { - $resource = Mage::getSingleton('core/resource'); - $this->_currencyRateTable = $resource->getTableName('directory/currency_rate'); - - parent::__construct(); - } - - /** - * Retrieve currency rate (only base=>allowed) - * - * @param string $currencyFrom - * @param string $currencyTo - * @return float - */ - public function getRate($currencyFrom, $currencyTo) - { - if ($currencyFrom instanceof Mage_Directory_Model_Currency) { - $currencyFrom = $currencyFrom->getCode(); - } - - if ($currencyTo instanceof Mage_Directory_Model_Currency) { - $currencyTo = $currencyTo->getCode(); - } - - if ($currencyFrom == $currencyTo) { - return 1; - } - - if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->_currencyRateTable, 'rate') - ->where('currency_from=?', strtoupper($currencyFrom)) - ->where('currency_to=?', strtoupper($currencyTo)); - - self::$_rateCache[$currencyFrom][$currencyTo] = $read->fetchOne($select); - } - - return self::$_rateCache[$currencyFrom][$currencyTo]; - } - - /** - * Retrieve currency rate (base=>allowed or allowed=>base) - * - * @param string $currencyFrom - * @param string $currencyTo - * @return float - */ - public function getAnyRate($currencyFrom, $currencyTo) - { - if ($currencyFrom instanceof Mage_Directory_Model_Currency) { - $currencyFrom = $currencyFrom->getCode(); - } - - if ($currencyTo instanceof Mage_Directory_Model_Currency) { - $currencyTo = $currencyTo->getCode(); - } - - if ($currencyFrom == $currencyTo) { - return 1; - } - - if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->_currencyRateTable, new Zend_Db_Expr($read->quoteInto('if(currency_from=?,rate,1/rate)', strtoupper($currencyFrom)))) - ->where('currency_from=?', strtoupper($currencyFrom)) - ->where('currency_to=?', strtoupper($currencyTo)) - ->orWhere('currency_from=?', strtoupper($currencyTo)) - ->where('currency_to=?', strtoupper($currencyFrom)); - - self::$_rateCache[$currencyFrom][$currencyTo] = $read->fetchOne($select); - } - - return self::$_rateCache[$currencyFrom][$currencyTo]; - } - - /** - * Saving currency rates - * - * @param array $rates - */ - public function saveRates($rates) - { - if( is_array($rates) && sizeof($rates) > 0 ) { - $write = $this->_getWriteAdapter(); - $table = $write->quoteIdentifier($this->_currencyRateTable); - $colFrom= $write->quoteIdentifier('currency_from'); - $colTo = $write->quoteIdentifier('currency_to'); - $colRate= $write->quoteIdentifier('rate'); - - $sql = 'REPLACE INTO ' . $table . ' (' . $colFrom . ', ' . $colTo . ', ' . $colRate . ') VALUES '; - $values = array(); - foreach ($rates as $currencyCode => $rate) { - foreach( $rate as $currencyTo => $value ) { - $value = abs($value); - if( $value == 0 ) { - continue; - } - $values[] = $write->quoteInto('(?)', array($currencyCode, $currencyTo, $value)); - } - } - $sql.= implode(',', $values); - $write->query($sql); - } else { - Mage::throwException(Mage::helper('directory')->__('Invalid rates received')); - } - } - - /** - * Retrieve config currency data by config path - * - * @param object $model - * @param string $path - * @return array - */ - public function getConfigCurrencies($model, $path) - { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->getTable('core/config_data')) - ->where($read->quoteInto(' path = ? ', $path)) - //->where('inherit = 0') - ->order(' value ASC '); - - $data = $read->fetchAll($select); - $tmp_array = array(); - foreach( $data as $configRecord ) { - $tmp_array = array_merge($tmp_array, explode(',', $configRecord['value'])); - } - - $data = array_unique($tmp_array); - return $data; - } - - /** - * Retieve currency rates - * - * @param string|array $currency - * @param array $toCurrencies - * @return array - */ - public function getCurrencyRates($currency, $toCurrencies=null) - { - $rates = array(); - if( is_array($currency) ) { - foreach( $currency as $code ) { - $rates[$code] = $this->_getRatesByCode($code, $toCurrencies); - } - } else { - $rates = $this->_getRatesByCode($currency, $toCurrencies); - } - - return $rates; - } - - /** - * Protected method used by getCurrencyRates() method - * - * @param string $code - * @param array $toCurrencies - * @return array - */ - protected function _getRatesByCode($code, $toCurrencies=null) - { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->getTable('directory/currency_rate'), array('currency_to', 'rate')) - ->where($read->quoteInto('currency_from = ?', $code)) - ->where($read->quoteInto('currency_to IN(?)', $toCurrencies)); - - $data = $read->fetchAll($select); - - $tmp_array = array(); - foreach( $data as $currencyFrom => $rate ) { - $tmp_array[$rate['currency_to']] = $rate['rate']; - } - $data = $tmp_array; - - return $data; - } - } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Currency/Collection.php b/app/code/core/Mage/Directory/Model/Mysql4/Currency/Collection.php index fe528e85..6c5eb6bb 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Currency/Collection.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Currency/Collection.php @@ -20,84 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Currency Mysql4 collection model * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Currency_Collection extends Varien_Data_Collection_Db +class Mage_Directory_Model_Mysql4_Currency_Collection extends Mage_Directory_Model_Resource_Currency_Collection { - protected $_currencyTable; - protected $_currencyNameTable; - protected $_currencyRateTable; - - public function __construct() - { - $resource = Mage::getSingleton('core/resource'); - parent::__construct($resource->getConnection('directory_read')); - $this->_currencyTable = $resource->getTableName('directory/currency'); - $this->_currencyNameTable = $resource->getTableName('directory/currency_name'); - $this->_currencyRateTable = $resource->getTableName('directory/currency_rate'); - - $this->_select->from(array('main_table'=>$this->_currencyNameTable)); - /*$this->_select->join(array('name_table'=>$this->_currencyNameTable), - "main_table.currency_code=name_table.currency_code");*/ - - $this->setItemObjectClass(Mage::getConfig()->getModelClassName('directory/currency')); - } - - public function joinRates($currency) - { - $alias = $currency.'_rate'; - $this->_select->joinLeft(array($alias=>$this->_currencyRateTable), - $this->getConnection()->quoteInto("$alias.currency_to=main_table.currency_code AND $alias.currency_from=?", $currency), - 'rate'); - return $this; - } - - /** - * Set language condition by name table - * - * @param string $lang - * @return Varien_Data_Collection_Db - */ - public function addLanguageFilter($lang=null) - { - if (is_null($lang)) { - $lang = Mage::app()->getStore()->getLanguageCode(); - } - $this->addFilter('language', "main_table.language_code='$lang'", 'string'); - return $this; - } - - /** - * Add currency code condition - * - * @param string $code - * @return Varien_Data_Collection_Db - */ - public function addCodeFilter($code) - { - if (is_array($code)) { - $this->addFilter("codes", - $this->getConnection()->quoteInto("main_table.currency_code IN (?)", $code), - 'string' - ); - } - else { - $this->addFilter("code_$code", "main_table.currency_code='$code'", 'string'); - } - return $this; - } - - public function toOptionArray() - { - return $this->_toOptionArray('currency_code', 'currency_name'); - } } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Region.php b/app/code/core/Mage/Directory/Model/Mysql4/Region.php index 3e69477d..7d65b406 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Region.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Region.php @@ -20,91 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Directory_Model_Mysql4_Region +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Directory + * @author Magento Core Team + */ +class Mage_Directory_Model_Mysql4_Region extends Mage_Directory_Model_Resource_Region { - protected $_regionTable; - protected $_regionNameTable; - - /** - * DB read connection - * - * @var Zend_Db_Adapter_Abstract - */ - protected $_read; - - /** - * DB write connection - * - * @var Zend_Db_Adapter_Abstract - */ - protected $_write; - - public function __construct() - { - $resource = Mage::getSingleton('core/resource'); - $this->_regionTable = $resource->getTableName('directory/country_region'); - $this->_regionNameTable = $resource->getTableName('directory/country_region_name'); - $this->_read = $resource->getConnection('directory_read'); - $this->_write = $resource->getConnection('directory_write'); - } - - public function getIdFieldName() - { - return 'region_id'; - } - - public function load(Mage_Directory_Model_Region $region, $regionId) - { - $locale = Mage::app()->getLocale()->getLocaleCode(); - $systemLocale = Mage::app()->getDistroLocaleCode(); - - $select = $this->_read->select() - ->from(array('region'=>$this->_regionTable)) - ->where('region.region_id=?', $regionId) - ->join(array('rname'=>$this->_regionNameTable), - 'rname.region_id=region.region_id AND (rname.locale=\''.$locale.'\' OR rname.locale=\''.$systemLocale.'\')', - array('name', new Zend_Db_Expr('CASE rname.locale WHEN \''.$systemLocale.'\' THEN 1 ELSE 0 END sort_locale'))) - ->order('sort_locale') - ->limit(1); - - $region->setData($this->_read->fetchRow($select)); - return $this; - } - - public function loadByCode(Mage_Directory_Model_Region $region, $regionCode, $countryId) - { - $locale = Mage::app()->getLocale()->getLocaleCode(); - - $select = $this->_read->select() - ->from(array('region'=>$this->_regionTable)) - ->where('region.country_id=?', $countryId) - ->where('region.code=?', $regionCode) - ->join(array('rname'=>$this->_regionNameTable), - 'rname.region_id=region.region_id AND rname.locale=\''.$locale.'\'', - array('name')); - - $region->setData($this->_read->fetchRow($select)); - return $this; - } - - public function loadByName(Mage_Directory_Model_Region $region, $regionName, $countryId) - { - $locale = Mage::app()->getLocale()->getLocaleCode(); - - $select = $this->_read->select() - ->from(array('region'=>$this->_regionTable)) - ->where('region.country_id=?', $countryId) - ->where('region.default_name=?', $regionName) - ->join(array('rname'=>$this->_regionNameTable), - 'rname.region_id=region.region_id AND rname.locale=\''.$locale.'\'', - array('name')); - - $region->setData($this->_read->fetchRow($select)); - return $this; - } } diff --git a/app/code/core/Mage/Directory/Model/Mysql4/Region/Collection.php b/app/code/core/Mage/Directory/Model/Mysql4/Region/Collection.php index 74f32dd4..86d3b9ad 100644 --- a/app/code/core/Mage/Directory/Model/Mysql4/Region/Collection.php +++ b/app/code/core/Mage/Directory/Model/Mysql4/Region/Collection.php @@ -20,97 +20,18 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Country collection * - * @category Mage - * @package Mage_Directory + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ -class Mage_Directory_Model_Mysql4_Region_Collection extends Varien_Data_Collection_Db +class Mage_Directory_Model_Mysql4_Region_Collection extends Mage_Directory_Model_Resource_Region_Collection { - protected $_regionTable; - protected $_regionNameTable; - protected $_countryTable; - - public function __construct() - { - parent::__construct(Mage::getSingleton('core/resource')->getConnection('directory_read')); - - $this->_countryTable = Mage::getSingleton('core/resource')->getTableName('directory/country'); - $this->_regionTable = Mage::getSingleton('core/resource')->getTableName('directory/country_region'); - $this->_regionNameTable = Mage::getSingleton('core/resource')->getTableName('directory/country_region_name'); - - $locale = Mage::app()->getLocale()->getLocaleCode(); - - $this->_select->from(array('region'=>$this->_regionTable), - array('region_id'=>'region_id', 'country_id'=>'country_id', 'code'=>'code', 'default_name'=>'default_name') - ); - $this->_select->joinLeft(array('rname'=>$this->_regionNameTable), - "region.region_id=rname.region_id AND rname.locale='$locale'", array('name')); - - $this->setItemObjectClass(Mage::getConfig()->getModelClassName('directory/region')); - } - - public function addCountryFilter($countryId) - { - if (!empty($countryId)) { - if (is_array($countryId)) { - $this->addFieldToFilter('region.country_id', array('in'=>$countryId)); - } else { - $this->addFieldToFilter('region.country_id', $countryId); - } - } - return $this; - } - - public function addCountryCodeFilter($countryCode) - { - $this->_select->joinLeft(array('country'=>$this->_countryTable), 'region.country_id=country.country_id'); - $this->_select->where("country.iso3_code = '{$countryCode}'"); - return $this; - } - - public function addRegionCodeFilter($regionCode) - { - if (!empty($regionCode)) { - if (is_array($regionCode)) { - $this->_select->where("region.code IN ('".implode("','", $regionCode)."')"); - } else { - $this->_select->where("region.code = '{$regionCode}'"); - } - } - return $this; - } - - public function addRegionNameFilter($regionName) - { - if (!empty($regionName)) { - if (is_array($regionName)) { - $this->_select->where("region.default_name in ('".implode("','", $regionName)."')"); - } else { - $this->_select->where("region.default_name = '{$regionName}'"); - } - } - return $this; - } - - public function toOptionArray() - { - $options = array(); - foreach ($this as $item) { - $options[] = array( - 'value' => $item->getId(), - 'label' => $item->getName() - ); - } - if (count($options)>0) { - array_unshift($options, array('title'=>null, 'value'=>'0', 'label'=>Mage::helper('directory')->__('-- Please select --'))); - } - return $options; - } } diff --git a/app/code/core/Mage/Directory/Model/Observer.php b/app/code/core/Mage/Directory/Model/Observer.php index d698b06e..057987e9 100644 --- a/app/code/core/Mage/Directory/Model/Observer.php +++ b/app/code/core/Mage/Directory/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Region.php b/app/code/core/Mage/Directory/Model/Region.php index bae41166..3966fb1a 100644 --- a/app/code/core/Mage/Directory/Model/Region.php +++ b/app/code/core/Mage/Directory/Model/Region.php @@ -20,15 +20,24 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Region * - * @category Mage - * @package Mage_Directory + * @method Mage_Directory_Model_Resource_Region _getResource() + * @method Mage_Directory_Model_Resource_Region getResource() + * @method string getCountryId() + * @method Mage_Directory_Model_Region setCountryId(string $value) + * @method string getCode() + * @method Mage_Directory_Model_Region setCode(string $value) + * @method string getDefaultName() + * @method Mage_Directory_Model_Region setDefaultName(string $value) + * + * @category Mage + * @package Mage_Directory * @author Magento Core Team */ class Mage_Directory_Model_Region extends Mage_Core_Model_Abstract diff --git a/app/code/core/Mage/Directory/Model/Region/Api.php b/app/code/core/Mage/Directory/Model/Region/Api.php index 519ca547..14540038 100644 --- a/app/code/core/Mage/Directory/Model/Region/Api.php +++ b/app/code/core/Mage/Directory/Model/Region/Api.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Region/Api/V2.php b/app/code/core/Mage/Directory/Model/Region/Api/V2.php index 43abc39b..2a879445 100644 --- a/app/code/core/Mage/Directory/Model/Region/Api/V2.php +++ b/app/code/core/Mage/Directory/Model/Region/Api/V2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/Model/Resource/Country.php b/app/code/core/Mage/Directory/Model/Resource/Country.php new file mode 100755 index 00000000..f9d981a3 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Country.php @@ -0,0 +1,73 @@ + + */ +class Mage_Directory_Model_Resource_Country extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('directory/country', 'country_id'); + } + + /** + * Load country by ISO code + * + * @param Mage_Directory_Model_Country $country + * @param string $code + * + * @throws Mage_Core_Exception + * + * @return Mage_Directory_Model_Resource_Country + */ + public function loadByCode(Mage_Directory_Model_Country $country, $code) + { + switch (strlen($code)) { + case 2: + $field = 'iso2_code'; + break; + + case 3: + $field = 'iso3_code'; + break; + + default: + Mage::throwException(Mage::helper('directory')->__('Invalid country code: %s', $code)); + } + + return $this->load($country, $code, $field); + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Country/Collection.php b/app/code/core/Mage/Directory/Model/Resource/Country/Collection.php new file mode 100755 index 00000000..4a4e9971 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Country/Collection.php @@ -0,0 +1,166 @@ + + */ +class Mage_Directory_Model_Resource_Country_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('directory/country'); + } + + /** + * Load allowed countries for current store + * + * @param mixed $store + * @return Mage_Directory_Model_Resource_Country_Collection + */ + public function loadByStore($store = null) + { + $allowCountries = explode(',', (string)Mage::getStoreConfig('general/country/allow', $store)); + if (!empty($allowCountries)) { + $this->addFieldToFilter("country_id", array('in' => $allowCountries)); + } + return $this; + } + + /** + * Loads Item By Id + * + * @param string $countryId + * @return Mage_Directory_Model_Resource_Country + */ + public function getItemById($countryId) + { + foreach ($this->_items as $country) { + if ($country->getCountryId() == $countryId) { + return $country; + } + } + return Mage::getResourceModel('directory/country'); + } + + /** + * Add filter by country code to collection. + * $countryCode can be either array of country codes or string representing one country code. + * $iso can be either array containing 'iso2', 'iso3' values or string with containing one of that values directly. + * The collection will contain countries where at least one of contry $iso fields matches $countryCode. + * + * @param string|array $countryCode + * @param string|array $iso + * @return Mage_Directory_Model_Resource_Country_Collection + */ + public function addCountryCodeFilter($countryCode, $iso = array('iso3', 'iso2')) + { + if (!empty($countryCode)) { + if (is_array($countryCode)) { + if (is_array($iso)) { + $whereOr = array(); + foreach ($iso as $iso_curr) { + $whereOr[] .= $this->_getConditionSql("{$iso_curr}_code", array('in' => $countryCode)); + } + $this->_select->where('(' . implode(') OR (', $whereOr) . ')'); + } else { + $this->addFieldToFilter("{$iso}_code", array('in'=>$countryCode)); + } + } else { + if (is_array($iso)) { + $whereOr = array(); + foreach ($iso as $iso_curr) { + $whereOr[] .= $this->_getConditionSql("{$iso_curr}_code", $countryCode); + } + $this->_select->where('(' . implode(') OR (', $whereOr) . ')'); + } else { + $this->addFieldToFilter("{$iso}_code", $countryCode); + } + } + } + return $this; + } + + /** + * Add filter by country code(s) to collection + * + * @param string|array $countryId + * @return Mage_Directory_Model_Resource_Country_Collection + */ + public function addCountryIdFilter($countryId) + { + if (!empty($countryId)) { + if (is_array($countryId)) { + $this->addFieldToFilter("country_id", array('in' => $countryId)); + } else { + $this->addFieldToFilter("country_id", $countryId); + } + } + return $this; + } + + /** + * Convert collection items to select options array + * + * @param string $emptyLabel + * @return array + */ + public function toOptionArray($emptyLabel = ' ') + { + $options = $this->_toOptionArray('country_id', 'name', array('title'=>'iso2_code')); + + $sort = array(); + foreach ($options as $data) { + $name = Mage::app()->getLocale()->getCountryTranslation($data['value']); + if (!empty($name)) { + $sort[$name] = $data['value']; + } + } + + Mage::helper('core/string')->ksortMultibyte($sort); + $options = array(); + foreach ($sort as $label=>$value) { + $options[] = array( + 'value' => $value, + 'label' => $label + ); + } + + if (count($options) > 0 && $emptyLabel !== false) { + array_unshift($options, array('value' => '', 'label' => $emptyLabel)); + } + + return $options; + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Country/Format.php b/app/code/core/Mage/Directory/Model/Resource/Country/Format.php new file mode 100755 index 00000000..fd8d9551 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Country/Format.php @@ -0,0 +1,59 @@ + + */ +class Mage_Directory_Model_Resource_Country_Format extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('directory/country_format', 'country_format_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Directory_Model_Resource_Country_Format + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array(array( + 'field' => array('country_id', 'type'), + 'title' => Mage::helper('directory')->__('Country and Format Type combination should be unique') + )); + return $this; + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Country/Format/Collection.php b/app/code/core/Mage/Directory/Model/Resource/Country/Format/Collection.php new file mode 100755 index 00000000..bc8f2d1a --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Country/Format/Collection.php @@ -0,0 +1,62 @@ + + */ +class Mage_Directory_Model_Resource_Country_Format_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('directory/country_format'); + } + + /** + * Set country filter + * + * @param string|Mage_Directory_Model_Country $country + * @return Mage_Directory_Model_Resource_Country_Format_Collection + */ + public function setCountryFilter($country) + { + if ($country instanceof Mage_Directory_Model_Country) { + $countryId = $country->getId(); + } else { + $countryId = $country; + } + + return $this->addFieldToFilter('country_id', $countryId); + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Currency.php b/app/code/core/Mage/Directory/Model/Resource/Currency.php new file mode 100755 index 00000000..1af1f1d8 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Currency.php @@ -0,0 +1,249 @@ + + */ +class Mage_Directory_Model_Resource_Currency extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Currency rate table + * + * @var string + */ + protected $_currencyRateTable; + + /** + * Currency rate cache array + * + * @var array + */ + protected static $_rateCache; + + /** + * Define main and currency rate tables + * + */ + protected function _construct() + { + $this->_init('directory/currency', 'currency_code'); + $this->_currencyRateTable = $this->getTable('directory/currency_rate'); + } + + /** + * Retrieve currency rate (only base=>allowed) + * + * @param Mage_Directory_Model_Currency|string $currencyFrom + * @param Mage_Directory_Model_Currency|string $currencyTo + * @return float + */ + public function getRate($currencyFrom, $currencyTo) + { + if ($currencyFrom instanceof Mage_Directory_Model_Currency) { + $currencyFrom = $currencyFrom->getCode(); + } + + if ($currencyTo instanceof Mage_Directory_Model_Currency) { + $currencyTo = $currencyTo->getCode(); + } + + if ($currencyFrom == $currencyTo) { + return 1; + } + + if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { + $read = $this->_getReadAdapter(); + $bind = array( + ':currency_from' => strtoupper($currencyFrom), + ':currency_to' => strtoupper($currencyTo) + ); + $select = $read->select() + ->from($this->_currencyRateTable, 'rate') + ->where('currency_from = :currency_from') + ->where('currency_to = :currency_to'); + + self::$_rateCache[$currencyFrom][$currencyTo] = $read->fetchOne($select, $bind); + } + + return self::$_rateCache[$currencyFrom][$currencyTo]; + } + + /** + * Retrieve currency rate (base=>allowed or allowed=>base) + * + * @param Mage_Directory_Model_Currency|string $currencyFrom + * @param Mage_Directory_Model_Currency|string $currencyTo + * @return float + */ + public function getAnyRate($currencyFrom, $currencyTo) + { + if ($currencyFrom instanceof Mage_Directory_Model_Currency) { + $currencyFrom = $currencyFrom->getCode(); + } + + if ($currencyTo instanceof Mage_Directory_Model_Currency) { + $currencyTo = $currencyTo->getCode(); + } + + if ($currencyFrom == $currencyTo) { + return 1; + } + + if (!isset(self::$_rateCache[$currencyFrom][$currencyTo])) { + $adapter = $this->_getReadAdapter(); + $bind = array( + ':currency_from' => strtoupper($currencyFrom), + ':currency_to' => strtoupper($currencyTo) + ); + $select = $adapter->select() + ->from($this->_currencyRateTable, 'rate') + ->where('currency_from = :currency_from') + ->where('currency_to = :currency_to'); + + $rate = $adapter->fetchOne($select, $bind); + if ($rate === false) { + $select = $adapter->select() + ->from($this->_currencyRateTable, new Zend_Db_Expr('1/rate')) + ->where('currency_to = :currency_from') + ->where('currency_from = :currency_to'); + $rate = $adapter->fetchOne($select, $bind); + } + self::$_rateCache[$currencyFrom][$currencyTo] = $rate; + } + + return self::$_rateCache[$currencyFrom][$currencyTo]; + } + + /** + * Saving currency rates + * + * @param array $rates + */ + public function saveRates($rates) + { + if (is_array($rates) && sizeof($rates) > 0) { + $adapter = $this->_getWriteAdapter(); + $data = array(); + foreach ($rates as $currencyCode => $rate) { + foreach ($rate as $currencyTo => $value) { + $value = abs($value); + if ($value == 0) { + continue; + } + $data[] = array( + 'currency_from' => $currencyCode, + 'currency_to' => $currencyTo, + 'rate' => $value, + ); + } + } + if ($data) { + $adapter->insertOnDuplicate($this->_currencyRateTable, $data, array('rate')); + } + } else { + Mage::throwException(Mage::helper('directory')->__('Invalid rates received')); + } + } + + /** + * Retrieve config currency data by config path + * + * @param Mage_Directory_Model_Currency $model + * @param string $path + * + * @return array + */ + public function getConfigCurrencies($model, $path) + { + $adapter = $this->_getReadAdapter(); + $bind = array(':config_path' => $path); + $select = $adapter->select() + ->from($this->getTable('core/config_data')) + ->where('path = :config_path'); + $result = array(); + $rowSet = $adapter->fetchAll($select, $bind); + foreach ($rowSet as $row) { + $result = array_merge($result, explode(',', $row['value'])); + } + sort($result); + + return array_unique($result); + } + + /** + * Return currency rates + * + * @param string|array $currency + * @param array $toCurrencies + * + * @return array + */ + public function getCurrencyRates($currency, $toCurrencies = null) + { + $rates = array(); + if (is_array($currency)) { + foreach ($currency as $code) { + $rates[$code] = $this->_getRatesByCode($code, $toCurrencies); + } + } else { + $rates = $this->_getRatesByCode($currency, $toCurrencies); + } + + return $rates; + } + + /** + * Protected method used by getCurrencyRates() method + * + * @param string $code + * @param array $toCurrencies + * @return array + */ + protected function _getRatesByCode($code, $toCurrencies = null) + { + $adapter = $this->_getReadAdapter(); + $bind = array( + ':currency_from' => $code + ); + $select = $adapter->select() + ->from($this->getTable('directory/currency_rate'), array('currency_to', 'rate')) + ->where('currency_from = :currency_from') + ->where('currency_to IN(?)', $toCurrencies); + $rowSet = $adapter->fetchAll($select, $bind); + $result = array(); + + foreach ($rowSet as $row) { + $result[$row['currency_to']] = $row['rate']; + } + + return $result; + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Currency/Collection.php b/app/code/core/Mage/Directory/Model/Resource/Currency/Collection.php new file mode 100755 index 00000000..a8af67d0 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Currency/Collection.php @@ -0,0 +1,124 @@ + + */ +class Mage_Directory_Model_Resource_Currency_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Currency name table + * + * @var string + */ + protected $_currencyNameTable; + + /** + * Currency rate table + * + * @var string + */ + protected $_currencyRateTable; + + /** + * Define resource model and tables + * + * @return void + */ + protected function _construct() + { + $this->_init('directory/currency'); + + $this->_currencyNameTable = $this->getTable('directory/currency_name'); + $this->_currencyRateTable = $this->getTable('directory/currency_rate'); + } + + /** + * Join currency rates by currency + * + * @param string $currency + * @return Mage_Directory_Model_Resource_Currency_Collection + */ + public function joinRates($currency) + { + $alias = sprintf('%s_rate', $currency); + $this->addBindParam(':'.$alias, $currency); + $this->_select + ->joinLeft( + array($alias => $this->_currencyRateTable), + "{$alias}.currency_to = main_table.currency_code AND {$alias}.currency_from=:{$alias}", + 'rate'); + + return $this; + } + + /** + * Set language condition by name table + * + * @param string $lang + * @return Mage_Directory_Model_Resource_Currency_Collection + */ + public function addLanguageFilter($lang = null) + { + if (is_null($lang)) { + $lang = Mage::app()->getStore()->getLanguageCode(); + } + return $this->addFieldToFilter('main_table.language_code', $lang); + } + + /** + * Add currency code condition + * + * @param string $code + * @return Mage_Directory_Model_Resource_Currency_Collection + */ + public function addCodeFilter($code) + { + if (is_array($code)) { + $this->addFieldToFilter("main_table.currency_code", array('in' => $code)); + } else { + $this->addFieldToFilter("main_table.currency_code", $code); + } + + return $this; + } + + /** + * Convert collection items to select options array + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('currency_code', 'currency_name'); + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Region.php b/app/code/core/Mage/Directory/Model/Resource/Region.php new file mode 100755 index 00000000..ddbe0b6f --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Region.php @@ -0,0 +1,154 @@ + + */ +class Mage_Directory_Model_Resource_Region extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Table with localized region names + * + * @var string + */ + protected $_regionNameTable; + + /** + * Define main and locale region name tables + * + */ + protected function _construct() + { + $this->_init('directory/country_region', 'region_id'); + $this->_regionNameTable = $this->getTable('directory/country_region_name'); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Abstract $object + * + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $adapter = $this->_getReadAdapter(); + + $locale = Mage::app()->getLocale()->getLocaleCode(); + $systemLocale = Mage::app()->getDistroLocaleCode(); + + $regionField = $adapter->quoteIdentifier($this->getMainTable() . '.' . $this->getIdFieldName()); + + $condition = $adapter->quoteInto('lrn.locale = ?', $locale); + $select->joinLeft( + array('lrn' => $this->_regionNameTable), + "{$regionField} = lrn.region_id AND {$condition}", + array()); + + if ($locale != $systemLocale) { + $nameExpr = $adapter->getCheckSql('lrn.region_id is null', 'srn.name', 'lrn.name'); + $condition = $adapter->quoteInto('srn.locale = ?', $systemLocale); + $select->joinLeft( + array('srn' => $this->_regionNameTable), + "{$regionField} = srn.region_id AND {$condition}", + array('name' => $nameExpr)); + } else { + $select->columns(array('name'), 'lrn'); + } + + return $select; + } + + /** + * Load object by country id and code or default name + * + * @param Mage_Core_Model_Abstract $object + * @param int $countryId + * @param string $value + * @param string $field + * + * @return Mage_Directory_Model_Resource_Region + */ + protected function _loadByCountry($object, $countryId, $value, $field) + { + $adapter = $this->_getReadAdapter(); + $locale = Mage::app()->getLocale()->getLocaleCode(); + $joinCondition = $adapter->quoteInto('rname.region_id = region.region_id AND rname.locale = ?', $locale); + $select = $adapter->select() + ->from(array('region' => $this->getMainTable())) + ->joinLeft( + array('rname' => $this->_regionNameTable), + $joinCondition, + array('name')) + ->where('region.country_id = ?', $countryId) + ->where("region.{$field} = ?", $value); + + $data = $adapter->fetchRow($select); + if ($data) { + $object->setData($data); + } + + $this->_afterLoad($object); + + return $this; + } + + /** + * Loads region by region code and country id + * + * @param Mage_Directory_Model_Region $region + * @param string $regionCode + * @param string $countryId + * + * @return Mage_Directory_Model_Resource_Region + */ + public function loadByCode(Mage_Directory_Model_Region $region, $regionCode, $countryId) + { + return $this->_loadByCountry($region, $countryId, (string)$regionCode, 'code'); + } + + /** + * Load data by country id and default region name + * + * @param Mage_Directory_Model_Region $region + * @param string $regionName + * @param string $countryId + * + * @return Mage_Directory_Model_Resource_Region + */ + public function loadByName(Mage_Directory_Model_Region $region, $regionName, $countryId) + { + return $this->_loadByCountry($region, $countryId, (string)$regionName, 'default_name'); + } +} diff --git a/app/code/core/Mage/Directory/Model/Resource/Region/Collection.php b/app/code/core/Mage/Directory/Model/Resource/Region/Collection.php new file mode 100755 index 00000000..7f0e29e7 --- /dev/null +++ b/app/code/core/Mage/Directory/Model/Resource/Region/Collection.php @@ -0,0 +1,174 @@ + + */ +class Mage_Directory_Model_Resource_Region_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Locale region name table name + * + * @var string + */ + protected $_regionNameTable; + + /** + * Country table name + * + * @var string + */ + protected $_countryTable; + + /** + * Define main, country, locale region name tables + * + */ + protected function _construct() + { + $this->_init('directory/region'); + + $this->_countryTable = $this->getTable('directory/country'); + $this->_regionNameTable = $this->getTable('directory/country_region_name'); + + $this->addOrder('name', Varien_Data_Collection::SORT_ORDER_ASC); + $this->addOrder('default_name', Varien_Data_Collection::SORT_ORDER_ASC); + } + + /** + * Initialize select object + * + * @return Mage_Directory_Model_Resource_Region_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + $locale = Mage::app()->getLocale()->getLocaleCode(); + + $this->addBindParam(':region_locale', $locale); + $this->getSelect()->joinLeft( + array('rname' => $this->_regionNameTable), + 'main_table.region_id = rname.region_id AND rname.locale = :region_locale', + array('name')); + + return $this; + } + + /** + * Filter by country_id + * + * @param string|array $countryId + * @return Mage_Directory_Model_Resource_Region_Collection + */ + public function addCountryFilter($countryId) + { + if (!empty($countryId)) { + if (is_array($countryId)) { + $this->addFieldToFilter('main_table.country_id', array('in' => $countryId)); + } else { + $this->addFieldToFilter('main_table.country_id', $countryId); + } + } + return $this; + } + + /** + * Filter by country code (ISO 3) + * + * @param string $countryCode + * @return Mage_Directory_Model_Resource_Region_Collection + */ + public function addCountryCodeFilter($countryCode) + { + $this->getSelect() + ->joinLeft( + array('country' => $this->_countryTable), + 'main_table.country_id = country.country_id' + ) + ->where('country.iso3_code = ?', $countryCode); + + return $this; + } + + /** + * Filter by Region code + * + * @param string|array $regionCode + * @return Mage_Directory_Model_Resource_Region_Collection + */ + public function addRegionCodeFilter($regionCode) + { + if (!empty($regionCode)) { + if (is_array($regionCode)) { + $this->addFieldToFilter('main_table.code', array('in' => $regionCode)); + } else { + $this->addFieldToFilter('main_table.code', $regionCode); + } + } + return $this; + } + + /** + * Filter by region name + * + * @param string|array $regionName + * @return Mage_Directory_Model_Resource_Region_Collection + */ + public function addRegionNameFilter($regionName) + { + if (!empty($regionName)) { + if (is_array($regionName)) { + $this->addFieldToFilter('main_table.default_name', array('in' => $regionName)); + } else { + $this->addFieldToFilter('main_table.default_name', $regionName); + } + } + return $this; + } + + /** + * Convert collection items to select options array + * + * @return array + */ + public function toOptionArray() + { + $options = $this->_toOptionArray('region_id', 'default_name', array('title' => 'default_name')); + if (count($options) > 0) { + array_unshift($options, array( + 'title '=> null, + 'value' => '0', + 'label' => Mage::helper('directory')->__('-- Please select --') + )); + } + return $options; + } +} diff --git a/app/code/core/Mage/Directory/controllers/CurrencyController.php b/app/code/core/Mage/Directory/controllers/CurrencyController.php index 05ba315a..9b877ed4 100644 --- a/app/code/core/Mage/Directory/controllers/CurrencyController.php +++ b/app/code/core/Mage/Directory/controllers/CurrencyController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/data/directory_setup/data-install-1.6.0.0.php b/app/code/core/Mage/Directory/data/directory_setup/data-install-1.6.0.0.php new file mode 100644 index 00000000..6ca3822b --- /dev/null +++ b/app/code/core/Mage/Directory/data/directory_setup/data-install-1.6.0.0.php @@ -0,0 +1,317 @@ +getConnection()->insertArray($installer->getTable('directory/country'), $columns, $data); + +/** + * Fill table directory/country_region + * Fill table directory/country_region_name for en_US locale + */ +$data = array( + array('US', 'AL', 'Alabama'), array('US', 'AK', 'Alaska'), array('US', 'AS', 'American Samoa'), + array('US', 'AZ', 'Arizona'), array('US', 'AR', 'Arkansas'), array('US', 'AF', 'Armed Forces Africa'), + array('US', 'AA', 'Armed Forces Americas'), array('US', 'AC', 'Armed Forces Canada'), + array('US', 'AE', 'Armed Forces Europe'), array('US', 'AM', 'Armed Forces Middle East'), + array('US', 'AP', 'Armed Forces Pacific'), array('US', 'CA', 'California'), array('US', 'CO', 'Colorado'), + array('US', 'CT', 'Connecticut'), array('US', 'DE', 'Delaware'), array('US', 'DC', 'District of Columbia'), + array('US', 'FM', 'Federated States Of Micronesia'), array('US', 'FL', 'Florida'), array('US', 'GA', 'Georgia'), + array('US', 'GU', 'Guam'), array('US', 'HI', 'Hawaii'), array('US', 'ID', 'Idaho'), array('US', 'IL', 'Illinois'), + array('US', 'IN', 'Indiana'), array('US', 'IA', 'Iowa'), array('US', 'KS', 'Kansas'), array('US', 'KY', 'Kentucky'), + array('US', 'LA', 'Louisiana'), array('US', 'ME', 'Maine'), array('US', 'MH', 'Marshall Islands'), + array('US', 'MD', 'Maryland'), array('US', 'MA', 'Massachusetts'), array('US', 'MI', 'Michigan'), + array('US', 'MN', 'Minnesota'), array('US', 'MS', 'Mississippi'), array('US', 'MO', 'Missouri'), + array('US', 'MT', 'Montana'), array('US', 'NE', 'Nebraska'), array('US', 'NV', 'Nevada'), + array('US', 'NH', 'New Hampshire'), array('US', 'NJ', 'New Jersey'), array('US', 'NM', 'New Mexico'), + array('US', 'NY', 'New York'), array('US', 'NC', 'North Carolina'), array('US', 'ND', 'North Dakota'), + array('US', 'MP', 'Northern Mariana Islands'), array('US', 'OH', 'Ohio'), array('US', 'OK', 'Oklahoma'), + array('US', 'OR', 'Oregon'), array('US', 'PW', 'Palau'), array('US', 'PA', 'Pennsylvania'), + array('US', 'PR', 'Puerto Rico'), array('US', 'RI', 'Rhode Island'), array('US', 'SC', 'South Carolina'), + array('US', 'SD', 'South Dakota'), array('US', 'TN', 'Tennessee'), array('US', 'TX', 'Texas'), + array('US', 'UT', 'Utah'), array('US', 'VT', 'Vermont'), array('US', 'VI', 'Virgin Islands'), + array('US', 'VA', 'Virginia'), array('US', 'WA', 'Washington'), array('US', 'WV', 'West Virginia'), + array('US', 'WI', 'Wisconsin'), array('US', 'WY', 'Wyoming'), array('CA', 'AB', 'Alberta'), + array('CA', 'BC', 'British Columbia'), array('CA', 'MB', 'Manitoba'), + array('CA', 'NL', 'Newfoundland and Labrador'), array('CA', 'NB', 'New Brunswick'), + array('CA', 'NS', 'Nova Scotia'), array('CA', 'NT', 'Northwest Territories'), array('CA', 'NU', 'Nunavut'), + array('CA', 'ON', 'Ontario'), array('CA', 'PE', 'Prince Edward Island'), array('CA', 'QC', 'Quebec'), + array('CA', 'SK', 'Saskatchewan'), array('CA', 'YT', 'Yukon Territory'), array('DE', 'NDS', 'Niedersachsen'), + array('DE', 'BAW', 'Baden-Württemberg'), array('DE', 'BAY', 'Bayern'), array('DE', 'BER', 'Berlin'), + array('DE', 'BRG', 'Brandenburg'), array('DE', 'BRE', 'Bremen'), array('DE', 'HAM', 'Hamburg'), + array('DE', 'HES', 'Hessen'), array('DE', 'MEC', 'Mecklenburg-Vorpommern'), + array('DE', 'NRW', 'Nordrhein-Westfalen'), array('DE', 'RHE', 'Rheinland-Pfalz'), array('DE', 'SAR', 'Saarland'), + array('DE', 'SAS', 'Sachsen'), array('DE', 'SAC', 'Sachsen-Anhalt'), array('DE', 'SCN', 'Schleswig-Holstein'), + array('DE', 'THE', 'Thüringen'), array('AT', 'WI', 'Wien'), array('AT', 'NO', 'Niederösterreich'), + array('AT', 'OO', 'Oberösterreich'), array('AT', 'SB', 'Salzburg'), array('AT', 'KN', 'Kärnten'), + array('AT', 'ST', 'Steiermark'), array('AT', 'TI', 'Tirol'), array('AT', 'BL', 'Burgenland'), + array('AT', 'VB', 'Voralberg'), array('CH', 'AG', 'Aargau'), array('CH', 'AI', 'Appenzell Innerrhoden'), + array('CH', 'AR', 'Appenzell Ausserrhoden'), array('CH', 'BE', 'Bern'), array('CH', 'BL', 'Basel-Landschaft'), + array('CH', 'BS', 'Basel-Stadt'), array('CH', 'FR', 'Freiburg'), array('CH', 'GE', 'Genf'), + array('CH', 'GL', 'Glarus'), array('CH', 'GR', 'Graubünden'), array('CH', 'JU', 'Jura'), + array('CH', 'LU', 'Luzern'), array('CH', 'NE', 'Neuenburg'), array('CH', 'NW', 'Nidwalden'), + array('CH', 'OW', 'Obwalden'), array('CH', 'SG', 'St. Gallen'), array('CH', 'SH', 'Schaffhausen'), + array('CH', 'SO', 'Solothurn'), array('CH', 'SZ', 'Schwyz'), array('CH', 'TG', 'Thurgau'), + array('CH', 'TI', 'Tessin'), array('CH', 'UR', 'Uri'), array('CH', 'VD', 'Waadt'), array('CH', 'VS', 'Wallis'), + array('CH', 'ZG', 'Zug'), array('CH', 'ZH', 'Zürich'), array('ES', 'A CoruÑa', 'A Coruña'), + array('ES', 'Alava', 'Alava'), array('ES', 'Albacete', 'Albacete'), array('ES', 'Alicante', 'Alicante'), + array('ES', 'Almeria', 'Almeria'), array('ES', 'Asturias', 'Asturias'), array('ES', 'Avila', 'Avila'), + array('ES', 'Badajoz', 'Badajoz'), array('ES', 'Baleares', 'Baleares'), array('ES', 'Barcelona', 'Barcelona'), + array('ES', 'Burgos', 'Burgos'), array('ES', 'Caceres', 'Caceres'), array('ES', 'Cadiz', 'Cadiz'), + array('ES', 'Cantabria', 'Cantabria'), array('ES', 'Castellon', 'Castellon'), array('ES', 'Ceuta', 'Ceuta'), + array('ES', 'Ciudad Real', 'Ciudad Real'), array('ES', 'Cordoba', 'Cordoba'), array('ES', 'Cuenca', 'Cuenca'), + array('ES', 'Girona', 'Girona'), array('ES', 'Granada', 'Granada'), array('ES', 'Guadalajara', 'Guadalajara'), + array('ES', 'Guipuzcoa', 'Guipuzcoa'), array('ES', 'Huelva', 'Huelva'), array('ES', 'Huesca', 'Huesca'), + array('ES', 'Jaen', 'Jaen'), array('ES', 'La Rioja', 'La Rioja'), array('ES', 'Las Palmas', 'Las Palmas'), + array('ES', 'Leon', 'Leon'), array('ES', 'Lleida', 'Lleida'), array('ES', 'Lugo', 'Lugo'), + array('ES', 'Madrid', 'Madrid'), array('ES', 'Malaga', 'Malaga'), array('ES', 'Melilla', 'Melilla'), + array('ES', 'Murcia', 'Murcia'), array('ES', 'Navarra', 'Navarra'), array('ES', 'Ourense', 'Ourense'), + array('ES', 'Palencia', 'Palencia'), array('ES', 'Pontevedra', 'Pontevedra'), array('ES', 'Salamanca', 'Salamanca'), + array('ES', 'Santa Cruz de Tenerife', 'Santa Cruz de Tenerife'), array('ES', 'Segovia', 'Segovia'), + array('ES', 'Sevilla', 'Sevilla'), array('ES', 'Soria', 'Soria'), array('ES', 'Tarragona', 'Tarragona'), + array('ES', 'Teruel', 'Teruel'), array('ES', 'Toledo', 'Toledo'), array('ES', 'Valencia', 'Valencia'), + array('ES', 'Valladolid', 'Valladolid'), array('ES', 'Vizcaya', 'Vizcaya'), array('ES', 'Zamora', 'Zamora'), + array('ES', 'Zaragoza', 'Zaragoza'), array('FR', 1, 'Ain'), array('FR', 2, 'Aisne'), array('FR', 3, 'Allier'), + array('FR', 4, 'Alpes-de-Haute-Provence'), array('FR', 5, 'Hautes-Alpes'), array('FR', 6, 'Alpes-Maritimes'), + array('FR', 7, 'Ardèche'), array('FR', 8, 'Ardennes'), array('FR', 9, 'Ariège'), array('FR', 10, 'Aube'), + array('FR', 11, 'Aude'), array('FR', 12, 'Aveyron'), array('FR', 13, 'Bouches-du-Rhône'), + array('FR', 14, 'Calvados'), array('FR', 15, 'Cantal'), array('FR', 16, 'Charente'), + array('FR', 17, 'Charente-Maritime'), array('FR', 18, 'Cher'), array('FR', 19, 'Corrèze'), + array('FR', '2A', 'Corse-du-Sud'), array('FR', '2B', 'Haute-Corse'), array('FR', 21, 'Côte-d\'Or'), + array('FR', 22, 'Côtes-d\'Armor'), array('FR', 23, 'Creuse'), array('FR', 24, 'Dordogne'), array('FR', 25, 'Doubs'), + array('FR', 26, 'Drôme'), array('FR', 27, 'Eure'), array('FR', 28, 'Eure-et-Loir'), array('FR', 29, 'Finistère'), + array('FR', 30, 'Gard'), array('FR', 31, 'Haute-Garonne'), array('FR', 32, 'Gers'), array('FR', 33, 'Gironde'), + array('FR', 34, 'Hérault'), array('FR', 35, 'Ille-et-Vilaine'), array('FR', 36, 'Indre'), + array('FR', 37, 'Indre-et-Loire'), array('FR', 38, 'Isère'), array('FR', 39, 'Jura'), array('FR', 40, 'Landes'), + array('FR', 41, 'Loir-et-Cher'), array('FR', 42, 'Loire'), array('FR', 43, 'Haute-Loire'), + array('FR', 44, 'Loire-Atlantique'), array('FR', 45, 'Loiret'), array('FR', 46, 'Lot'), + array('FR', 47, 'Lot-et-Garonne'), array('FR', 48, 'Lozère'), array('FR', 49, 'Maine-et-Loire'), + array('FR', 50, 'Manche'), array('FR', 51, 'Marne'), array('FR', 52, 'Haute-Marne'), array('FR', 53, 'Mayenne'), + array('FR', 54, 'Meurthe-et-Moselle'), array('FR', 55, 'Meuse'), array('FR', 56, 'Morbihan'), + array('FR', 57, 'Moselle'), array('FR', 58, 'Nièvre'), array('FR', 59, 'Nord'), array('FR', 60, 'Oise'), + array('FR', 61, 'Orne'), array('FR', 62, 'Pas-de-Calais'), array('FR', 63, 'Puy-de-Dôme'), + array('FR', 64, 'Pyrénées-Atlantiques'), array('FR', 65, 'Hautes-Pyrénées'), array('FR', 66, 'Pyrénées-Orientales'), + array('FR', 67, 'Bas-Rhin'), array('FR', 68, 'Haut-Rhin'), array('FR', 69, 'Rhône'), array('FR', 70, 'Haute-Saône'), + array('FR', 71, 'Saône-et-Loire'), array('FR', 72, 'Sarthe'), array('FR', 73, 'Savoie'), + array('FR', 74, 'Haute-Savoie'), array('FR', 75, 'Paris'), array('FR', 76, 'Seine-Maritime'), + array('FR', 77, 'Seine-et-Marne'), array('FR', 78, 'Yvelines'), array('FR', 79, 'Deux-Sèvres'), + array('FR', 80, 'Somme'), array('FR', 81, 'Tarn'), array('FR', 82, 'Tarn-et-Garonne'), array('FR', 83, 'Var'), + array('FR', 84, 'Vaucluse'), array('FR', 85, 'Vendée'), array('FR', 86, 'Vienne'), array('FR', 87, 'Haute-Vienne'), + array('FR', 88, 'Vosges'), array('FR', 89, 'Yonne'), array('FR', 90, 'Territoire-de-Belfort'), + array('FR', 91, 'Essonne'), array('FR', 92, 'Hauts-de-Seine'), array('FR', 93, 'Seine-Saint-Denis'), + array('FR', 94, 'Val-de-Marne'), array('FR', 95, 'Val-d\'Oise'), array('RO', 'AB', 'Alba'), + array('RO', 'AR', 'Arad'), array('RO', 'AG', 'ArgeÅŸ'), array('RO', 'BC', 'Bacău'), array('RO', 'BH', 'Bihor'), + array('RO', 'BN', 'BistriÅ£a-Năsăud'), array('RO', 'BT', 'BotoÅŸani'), array('RO', 'BV', 'BraÅŸov'), + array('RO', 'BR', 'Brăila'), array('RO', 'B', 'BucureÅŸti'), array('RO', 'BZ', 'Buzău'), + array('RO', 'CS', 'CaraÅŸ-Severin'), array('RO', 'CL', 'CălăraÅŸi'), array('RO', 'CJ', 'Cluj'), + array('RO', 'CT', 'ConstanÅ£a'), array('RO', 'CV', 'Covasna'), array('RO', 'DB', 'DâmboviÅ£a'), + array('RO', 'DJ', 'Dolj'), array('RO', 'GL', 'GalaÅ£i'), array('RO', 'GR', 'Giurgiu'), array('RO', 'GJ', 'Gorj'), + array('RO', 'HR', 'Harghita'), array('RO', 'HD', 'Hunedoara'), array('RO', 'IL', 'IalomiÅ£a'), + array('RO', 'IS', 'IaÅŸi'), array('RO', 'IF', 'Ilfov'), array('RO', 'MM', 'MaramureÅŸ'), + array('RO', 'MH', 'MehedinÅ£i'), array('RO', 'MS', 'MureÅŸ'), array('RO', 'NT', 'NeamÅ£'), array('RO', 'OT', 'Olt'), + array('RO', 'PH', 'Prahova'), array('RO', 'SM', 'Satu-Mare'), array('RO', 'SJ', 'Sălaj'), + array('RO', 'SB', 'Sibiu'), array('RO', 'SV', 'Suceava'), array('RO', 'TR', 'Teleorman'), + array('RO', 'TM', 'TimiÅŸ'), array('RO', 'TL', 'Tulcea'), array('RO', 'VS', 'Vaslui'), + array('RO', 'VL', 'Vâlcea'), array('RO', 'VN', 'Vrancea'), array('FI', 'Lappi', 'Lappi'), + array('FI', 'Pohjois-Pohjanmaa', 'Pohjois-Pohjanmaa'), array('FI', 'Kainuu', 'Kainuu'), + array('FI', 'Pohjois-Karjala', 'Pohjois-Karjala'), array('FI', 'Pohjois-Savo', 'Pohjois-Savo'), + array('FI', 'Etelä-Savo', 'Etelä-Savo'), array('FI', 'Etelä-Pohjanmaa', 'Etelä-Pohjanmaa'), + array('FI', 'Pohjanmaa', 'Pohjanmaa'), array('FI', 'Pirkanmaa', 'Pirkanmaa'), array('FI', 'Satakunta', 'Satakunta'), + array('FI', 'Keski-Pohjanmaa', 'Keski-Pohjanmaa'), array('FI', 'Keski-Suomi', 'Keski-Suomi'), + array('FI', 'Varsinais-Suomi', 'Varsinais-Suomi'), array('FI', 'Etelä-Karjala', 'Etelä-Karjala'), + array('FI', 'Päijät-Häme', 'Päijät-Häme'), array('FI', 'Kanta-Häme', 'Kanta-Häme'), + array('FI', 'Uusimaa', 'Uusimaa'), array('FI', 'Itä-Uusimaa', 'Itä-Uusimaa'), + array('FI', 'Kymenlaakso', 'Kymenlaakso'), array('FI', 'Ahvenanmaa', 'Ahvenanmaa'), + array('EE', 'EE-37', 'Harjumaa'), array('EE', 'EE-39', 'Hiiumaa'), array('EE', 'EE-44', 'Ida-Virumaa'), + array('EE', 'EE-49', 'Jõgevamaa'), array('EE', 'EE-51', 'Järvamaa'), array('EE', 'EE-57', 'Läänemaa'), + array('EE', 'EE-59', 'Lääne-Virumaa'), array('EE', 'EE-65', 'Põlvamaa'), array('EE', 'EE-67', 'Pärnumaa'), + array('EE', 'EE-70', 'Raplamaa'), array('EE', 'EE-74', 'Saaremaa'), array('EE', 'EE-78', 'Tartumaa'), + array('EE', 'EE-82', 'Valgamaa'), array('EE', 'EE-84', 'Viljandimaa'), array('EE', 'EE-86', 'Võrumaa'), + array('LV', 'LV-DGV', 'Daugavpils'), array('LV', 'LV-JEL', 'Jelgava'), array('LV', 'JÄ“kabpils', 'JÄ“kabpils'), + array('LV', 'LV-JUR', 'JÅ«rmala'), array('LV', 'LV-LPX', 'LiepÄja'), array('LV', 'LV-LE', 'LiepÄjas novads'), + array('LV', 'LV-REZ', 'RÄ“zekne'), array('LV', 'LV-RIX', 'RÄ«ga'), array('LV', 'LV-RI', 'RÄ«gas novads'), + array('LV', 'Valmiera', 'Valmiera'), array('LV', 'LV-VEN', 'Ventspils'), + array('LV', 'Aglonas novads', 'Aglonas novads'), array('LV', 'LV-AI', 'Aizkraukles novads'), + array('LV', 'Aizputes novads', 'Aizputes novads'), array('LV', 'AknÄ«stes novads', 'AknÄ«stes novads'), + array('LV', 'Alojas novads', 'Alojas novads'), array('LV', 'Alsungas novads', 'Alsungas novads'), + array('LV', 'LV-AL', 'AlÅ«ksnes novads'), array('LV', 'Amatas novads', 'Amatas novads'), + array('LV', 'Apes novads', 'Apes novads'), array('LV', 'Auces novads', 'Auces novads'), + array('LV', 'BabÄ«tes novads', 'BabÄ«tes novads'), array('LV', 'Baldones novads', 'Baldones novads'), + array('LV', 'Baltinavas novads', 'Baltinavas novads'), array('LV', 'LV-BL', 'Balvu novads'), + array('LV', 'LV-BU', 'Bauskas novads'), array('LV', 'BeverÄ«nas novads', 'BeverÄ«nas novads'), + array('LV', 'BrocÄ“nu novads', 'BrocÄ“nu novads'), array('LV', 'Burtnieku novads', 'Burtnieku novads'), + array('LV', 'Carnikavas novads', 'Carnikavas novads'), array('LV', 'Cesvaines novads', 'Cesvaines novads'), + array('LV', 'Ciblas novads', 'Ciblas novads'), array('LV', 'LV-CE', 'CÄ“su novads'), + array('LV', 'Dagdas novads', 'Dagdas novads'), array('LV', 'LV-DA', 'Daugavpils novads'), + array('LV', 'LV-DO', 'Dobeles novads'), array('LV', 'Dundagas novads', 'Dundagas novads'), + array('LV', 'Durbes novads', 'Durbes novads'), array('LV', 'Engures novads', 'Engures novads'), + array('LV', 'Garkalnes novads', 'Garkalnes novads'), array('LV', 'Grobiņas novads', 'Grobiņas novads'), + array('LV', 'LV-GU', 'Gulbenes novads'), array('LV', 'Iecavas novads', 'Iecavas novads'), + array('LV', 'IkÅ¡Ä·iles novads', 'IkÅ¡Ä·iles novads'), array('LV', 'IlÅ«kstes novads', 'IlÅ«kstes novads'), + array('LV', 'InÄukalna novads', 'InÄukalna novads'), array('LV', 'Jaunjelgavas novads', 'Jaunjelgavas novads'), + array('LV', 'Jaunpiebalgas novads', 'Jaunpiebalgas novads'), array('LV', 'Jaunpils novads', 'Jaunpils novads'), + array('LV', 'LV-JL', 'Jelgavas novads'), array('LV', 'LV-JK', 'JÄ“kabpils novads'), + array('LV', 'Kandavas novads', 'Kandavas novads'), array('LV', 'Kokneses novads', 'Kokneses novads'), + array('LV', 'Krimuldas novads', 'Krimuldas novads'), array('LV', 'Krustpils novads', 'Krustpils novads'), + array('LV', 'LV-KR', 'KrÄslavas novads'), array('LV', 'LV-KU', 'KuldÄ«gas novads'), + array('LV', 'KÄrsavas novads', 'KÄrsavas novads'), array('LV', 'LielvÄrdes novads', 'LielvÄrdes novads'), + array('LV', 'LV-LM', 'Limbažu novads'), array('LV', 'LubÄnas novads', 'LubÄnas novads'), + array('LV', 'LV-LU', 'Ludzas novads'), array('LV', 'LÄ«gatnes novads', 'LÄ«gatnes novads'), + array('LV', 'LÄ«vÄnu novads', 'LÄ«vÄnu novads'), array('LV', 'LV-MA', 'Madonas novads'), + array('LV', 'Mazsalacas novads', 'Mazsalacas novads'), array('LV', 'MÄlpils novads', 'MÄlpils novads'), + array('LV', 'MÄrupes novads', 'MÄrupes novads'), array('LV', 'NaukÅ¡Ä“nu novads', 'NaukÅ¡Ä“nu novads'), + array('LV', 'Neretas novads', 'Neretas novads'), array('LV', 'NÄ«cas novads', 'NÄ«cas novads'), + array('LV', 'LV-OG', 'Ogres novads'), array('LV', 'Olaines novads', 'Olaines novads'), + array('LV', 'Ozolnieku novads', 'Ozolnieku novads'), array('LV', 'LV-PR', 'Preiļu novads'), + array('LV', 'Priekules novads', 'Priekules novads'), array('LV', 'Priekuļu novads', 'Priekuļu novads'), + array('LV', 'PÄrgaujas novads', 'PÄrgaujas novads'), array('LV', 'PÄvilostas novads', 'PÄvilostas novads'), + array('LV', 'Pļaviņu novads', 'Pļaviņu novads'), array('LV', 'Raunas novads', 'Raunas novads'), + array('LV', 'Riebiņu novads', 'Riebiņu novads'), array('LV', 'Rojas novads', 'Rojas novads'), + array('LV', 'Ropažu novads', 'Ropažu novads'), array('LV', 'Rucavas novads', 'Rucavas novads'), + array('LV', 'RugÄju novads', 'RugÄju novads'), array('LV', 'RundÄles novads', 'RundÄles novads'), + array('LV', 'LV-RE', 'RÄ“zeknes novads'), array('LV', 'RÅ«jienas novads', 'RÅ«jienas novads'), + array('LV', 'SalacgrÄ«vas novads', 'SalacgrÄ«vas novads'), array('LV', 'Salas novads', 'Salas novads'), + array('LV', 'Salaspils novads', 'Salaspils novads'), array('LV', 'LV-SA', 'Saldus novads'), + array('LV', 'Saulkrastu novads', 'Saulkrastu novads'), array('LV', 'Siguldas novads', 'Siguldas novads'), + array('LV', 'Skrundas novads', 'Skrundas novads'), array('LV', 'SkrÄ«veru novads', 'SkrÄ«veru novads'), + array('LV', 'Smiltenes novads', 'Smiltenes novads'), array('LV', 'Stopiņu novads', 'Stopiņu novads'), + array('LV', 'StrenÄu novads', 'StrenÄu novads'), array('LV', 'SÄ“jas novads', 'SÄ“jas novads'), + array('LV', 'LV-TA', 'Talsu novads'), array('LV', 'LV-TU', 'Tukuma novads'), + array('LV', 'TÄ“rvetes novads', 'TÄ“rvetes novads'), array('LV', 'Vaiņodes novads', 'Vaiņodes novads'), + array('LV', 'LV-VK', 'Valkas novads'), array('LV', 'LV-VM', 'Valmieras novads'), + array('LV', 'VarakļÄnu novads', 'VarakļÄnu novads'), array('LV', 'Vecpiebalgas novads', 'Vecpiebalgas novads'), + array('LV', 'Vecumnieku novads', 'Vecumnieku novads'), array('LV', 'LV-VE', 'Ventspils novads'), + array('LV', 'ViesÄ«tes novads', 'ViesÄ«tes novads'), array('LV', 'Viļakas novads', 'Viļakas novads'), + array('LV', 'ViļÄnu novads', 'ViļÄnu novads'), array('LV', 'VÄrkavas novads', 'VÄrkavas novads'), + array('LV', 'Zilupes novads', 'Zilupes novads'), array('LV', 'Ä€dažu novads', 'Ä€dažu novads'), + array('LV', 'Ä’rgļu novads', 'Ä’rgļu novads'), array('LV', 'Ķeguma novads', 'Ķeguma novads'), + array('LV', 'Ķekavas novads', 'Ķekavas novads'), array('LT', 'LT-AL', 'Alytaus Apskritis'), + array('LT', 'LT-KU', 'Kauno Apskritis'), array('LT', 'LT-KL', 'KlaipÄ—dos Apskritis'), + array('LT', 'LT-MR', 'MarijampolÄ—s Apskritis'), array('LT', 'LT-PN', 'Panevėžio Apskritis'), + array('LT', 'LT-SA', 'Å iaulių Apskritis'), array('LT', 'LT-TA', 'TauragÄ—s Apskritis'), + array('LT', 'LT-TE', 'TelÅ¡ių Apskritis'), array('LT', 'LT-UT', 'Utenos Apskritis'), + array('LT', 'LT-VL', 'Vilniaus Apskritis') +); + +foreach ($data as $row) { + $bind = array( + 'country_id' => $row[0], + 'code' => $row[1], + 'default_name' => $row[2], + ); + $installer->getConnection()->insert($installer->getTable('directory/country_region'), $bind); + $regionId = $installer->getConnection()->lastInsertId($installer->getTable('directory/country_region')); + + $bind = array( + 'locale' => 'en_US', + 'region_id' => $regionId, + 'name' => $row[2] + ); + $installer->getConnection()->insert($installer->getTable('directory/country_region_name'), $bind); +} + +/** + * Fill table directory/currency_rate + */ +$data = array( + array('EUR', 'EUR', 1), + array('EUR', 'USD', 1.415000000000), + array('USD', 'EUR', 0.706700000000), + array('USD', 'USD', 1), +); + +$columns = array('currency_from', 'currency_to', 'rate'); +$installer->getConnection()->insertArray($installer->getTable('directory/currency_rate'), $columns, $data); diff --git a/app/code/core/Mage/Directory/data/directory_setup/data-upgrade-1.6.0.0-1.6.0.1.php b/app/code/core/Mage/Directory/data/directory_setup/data-upgrade-1.6.0.0-1.6.0.1.php new file mode 100644 index 00000000..1f8ff2da --- /dev/null +++ b/app/code/core/Mage/Directory/data/directory_setup/data-upgrade-1.6.0.0-1.6.0.1.php @@ -0,0 +1,58 @@ +getConnection()->insert( + $installer->getTable('core/config_data'), array( + 'scope' => 'default', + 'scope_id' => 0, + 'path' => Mage_Directory_Helper_Data::XML_PATH_DISPLAY_ALL_STATES, + 'value' => 1 + ) +); + +/** + * @var $countries array + */ +$countries = array(); +foreach(Mage::helper('directory')->getCountryCollection() as $country) { + if($country->getRegionCollection()->getSize() > 0) { + $countries[] = $country->getId(); + } +} + +$installer->getConnection()->insert( + $installer->getTable('core/config_data'), array( + 'scope' => 'default', + 'scope_id' => 0, + 'path' => Mage_Directory_Helper_Data::XML_PATH_STATES_REQUIRED, + 'value' => implode(',', $countries) + ) +); + diff --git a/app/code/core/Mage/Directory/etc/api.xml b/app/code/core/Mage/Directory/etc/api.xml index 344ead07..bd88cb26 100644 --- a/app/code/core/Mage/Directory/etc/api.xml +++ b/app/code/core/Mage/Directory/etc/api.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -30,7 +30,7 @@ directory/country_api - Country Api + Country API directory/country @@ -41,7 +41,7 @@ directory/region_api - Region Api + Region API directory/region diff --git a/app/code/core/Mage/Directory/etc/config.xml b/app/code/core/Mage/Directory/etc/config.xml index 13a30425..496dca51 100644 --- a/app/code/core/Mage/Directory/etc/config.xml +++ b/app/code/core/Mage/Directory/etc/config.xml @@ -21,14 +21,14 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.8.10 + 1.6.0.1 @@ -43,15 +43,18 @@ - Mage_Directory_Block + + Mage_Directory_Block + Mage_Directory_Model - directory_mysql4 + directory_resource - - Mage_Directory_Model_Mysql4 + + Mage_Directory_Model_Resource + directory_mysql4 directory_country
    @@ -69,7 +72,7 @@ directory_currency_rate
    -
    +
    @@ -88,7 +91,6 @@
    - @@ -116,7 +118,6 @@ - @@ -128,14 +129,12 @@ - AZN,AZM,AFN,ALL,DZD,AOA,ARS,AMD,AWG,AUD,BSD,BHD,BDT,BBD,BYR,BZD,BMD,BTN,BOB,BAM,BWP,BRL,GBP,BND,BGN,BUK,BIF,KHR,CAD,CVE,CZK,KYD,CLP,CNY,COP,KMF,CDF,CRC,HRK,CUP,DKK,DJF,DOP,XCD,EGP,SVC,GQE,ERN,EEK,ETB,EUR,FKP,FJD,GMD,GEK,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,KGS,LAK,LVL,LBP,LSL,LRD,LYD,LTL,MOP,MKD,MGA,MWK,MYR,MVR,LSM,MRO,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,ANG,TRL,TRY,NZD,NIC,NGN,KPW,NOK,OMR,PKR,PAB,PGK,PYG,PEN,PHP,PLN,QAR,RHD,RON,ROL,RUB,RWF,SHP,STD,SAR,RSD,SCR,SLL,SGD,SKK,SBD,SOS,ZAR,KRW,LKR,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TMM,USD,UGX,UAH,AED,UYU,UZS,VUV,VEB,VEF,VND,CHE,CHW,XOF,XPF,WST,YER,ZMK,ZWD - USD,EUR @@ -147,18 +146,17 @@ 0 - + general currency_import_error_email_template - + HK,IE,MO,PA AF,AL,DZ,AS,AD,AO,AI,AQ,AG,AR,AM,AW,AU,AT,AX,AZ,BS,BH,BD,BB,BY,BE,BZ,BJ,BM,BL,BT,BO,BA,BW,BV,BR,IO,VG,BN,BG,BF,BI,KH,CM,CA,CD,CV,KY,CF,TD,CL,CN,CX,CC,CO,KM,CG,CK,CR,HR,CU,CY,CZ,DK,DJ,DM,DO,EC,EG,SV,GQ,ER,EE,ET,FK,FO,FJ,FI,FR,GF,PF,TF,GA,GM,GE,DE,GG,GH,GI,GR,GL,GD,GP,GU,GT,GN,GW,GY,HT,HM,HN,HK,HU,IS,IM,IN,ID,IR,IQ,IE,IL,IT,CI,JE,JM,JP,JO,KZ,KE,KI,KW,KG,LA,LV,LB,LS,LR,LY,LI,LT,LU,ME,MF,MO,MK,MG,MW,MY,MV,ML,MT,MH,MQ,MR,MU,YT,FX,MX,FM,MD,MC,MN,MS,MA,MZ,MM,NA,NR,NP,NL,AN,NC,NZ,NI,NE,NG,NU,NF,KP,MP,NO,OM,PK,PW,PA,PG,PY,PE,PH,PN,PL,PS,PT,PR,QA,RE,RO,RS,RU,RW,SH,KN,LC,PM,VC,WS,SM,ST,SA,SN,SC,SL,SG,SK,SI,SB,SO,ZA,GS,KR,ES,LK,SD,SR,SJ,SZ,SE,CH,SY,TL,TW,TJ,TZ,TH,TG,TK,TO,TT,TN,TR,TM,TC,TV,VI,UG,UA,AE,GB,US,UM,UY,UZ,VU,VA,VE,VN,WF,EH,YE,ZM,ZW US - %A, %B %e %Y [%I:%M %p] %a, %b %e %Y [%I:%M %p] diff --git a/app/code/core/Mage/Directory/etc/system.xml b/app/code/core/Mage/Directory/etc/system.xml index dbc7d415..bc370863 100644 --- a/app/code/core/Mage/Directory/etc/system.xml +++ b/app/code/core/Mage/Directory/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -114,6 +114,7 @@ text + validate-email 5 1 1 @@ -209,6 +210,34 @@ + + + text + 4 + 1 + 0 + 0 + + + + multiselect + adminhtml/system_config_source_country + 1 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 8 + 1 + 0 + 0 + + + diff --git a/app/code/core/Mage/Directory/etc/wsi.xml b/app/code/core/Mage/Directory/etc/wsi.xml new file mode 100644 index 00000000..eca62b5d --- /dev/null +++ b/app/code/core/Mage/Directory/etc/wsi.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + List of countries + + + + + List of regions in specified country + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/core/Mage/Directory/sql/directory_setup/install-1.6.0.0.php b/app/code/core/Mage/Directory/sql/directory_setup/install-1.6.0.0.php new file mode 100644 index 00000000..4ef5d69f --- /dev/null +++ b/app/code/core/Mage/Directory/sql/directory_setup/install-1.6.0.0.php @@ -0,0 +1,164 @@ +startSetup(); + +/** + * Create table 'directory/country' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('directory/country')) + ->addColumn('country_id', Varien_Db_Ddl_Table::TYPE_TEXT, 2, array( + 'nullable' => false, + 'primary' => true, + 'default' => '', + ), 'Country Id in ISO-2') + ->addColumn('iso2_code', Varien_Db_Ddl_Table::TYPE_TEXT, 2, array( + 'nullable' => true, + 'default' => null, + ), 'Country ISO-2 format') + ->addColumn('iso3_code', Varien_Db_Ddl_Table::TYPE_TEXT, 3, array( + 'nullable' => true, + 'default' => null, + ), 'Country ISO-3') + ->setComment('Directory Country'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'directory/country_format' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('directory/country_format')) + ->addColumn('country_format_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Country Format Id') + ->addColumn('country_id', Varien_Db_Ddl_Table::TYPE_TEXT, 2, array( + 'nullable' => true, + 'default' => null, + ), 'Country Id in ISO-2') + ->addColumn('type', Varien_Db_Ddl_Table::TYPE_TEXT, 30, array( + 'nullable' => true, + 'default' => null, + ), 'Country Format Type') + ->addColumn('format', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => false, + ), 'Country Format') + ->addIndex( + $installer->getIdxName( + 'directory/country_format', + array('country_id', 'type'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('country_id', 'type'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Directory Country Format'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'directory/country_region' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('directory/country_region')) + ->addColumn('region_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Region Id') + ->addColumn('country_id', Varien_Db_Ddl_Table::TYPE_TEXT, 4, array( + 'nullable' => false, + 'default' => '0', + ), 'Country Id in ISO-2') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + 'nullable' => true, + 'default' => null, + ), 'Region code') + ->addColumn('default_name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Region Name') + ->addIndex($installer->getIdxName('directory/country_region', array('country_id')), + array('country_id')) + ->setComment('Directory Country Region'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'directory/country_region_name' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('directory/country_region_name')) + ->addColumn('locale', Varien_Db_Ddl_Table::TYPE_TEXT, 8, array( + 'nullable' => false, + 'primary' => true, + 'default' => '', + ), 'Locale') + ->addColumn('region_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + ), 'Region Id') + ->addColumn('name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Region Name') + ->addIndex($installer->getIdxName('directory/country_region_name', array('region_id')), + array('region_id')) + ->addForeignKey( + $installer->getFkName('directory/country_region_name', 'region_id', 'directory/country_region', 'region_id'), + 'region_id', $installer->getTable('directory/country_region'), 'region_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Directory Country Region Name'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'directory/currency_rate' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('directory/currency_rate')) + ->addColumn('currency_from', Varien_Db_Ddl_Table::TYPE_TEXT, 3, array( + 'nullable' => false, + 'primary' => true, + 'default' => '', + ), 'Currency Code Convert From') + ->addColumn('currency_to', Varien_Db_Ddl_Table::TYPE_TEXT, 3, array( + 'nullable' => false, + 'primary' => true, + 'default' => '', + ), 'Currency Code Convert To') + ->addColumn('rate', Varien_Db_Ddl_Table::TYPE_DECIMAL, '24,12', array( + 'nullable' => false, + 'default' => '0.000000000000', + ), 'Currency Conversion Rate') + ->addIndex($installer->getIdxName('directory/currency_rate', array('currency_to')), + array('currency_to')) + ->setComment('Directory Currency Rate'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.7.0.php index 045ecaf3..fce3b525 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.8.0.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.8.0.php index f3f79908..e22e1f99 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.8.0.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-install-0.8.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.0-0.7.1.php index d910b8ce..e35ebdbf 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.1-0.7.2.php index 62091551..5f89498c 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.0-0.8.1.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.0-0.8.1.php index 0a17f1d8..401afe26 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.0-0.8.1.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.0-0.8.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.1-0.8.2.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.1-0.8.2.php index bb22cf4b..83dfa080 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.1-0.8.2.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.1-0.8.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.10-0.8.11.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.10-0.8.11.php new file mode 100644 index 00000000..2aadbdc2 --- /dev/null +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.10-0.8.11.php @@ -0,0 +1,219 @@ +getConnection(); + +$regionTable = $installer->getTable('directory/country_region'); + +$regionsToIns = array( + //After reform of 2010 January + array('FI', 'Lappi', 'Lappi'), + array('FI', 'Pohjois-Pohjanmaa', 'Pohjois-Pohjanmaa'), + array('FI', 'Kainuu', 'Kainuu'), + array('FI', 'Pohjois-Karjala', 'Pohjois-Karjala'), + array('FI', 'Pohjois-Savo', 'Pohjois-Savo'), + array('FI', 'Etelä-Savo', 'Etelä-Savo'), + array('FI', 'Etelä-Pohjanmaa', 'Etelä-Pohjanmaa'), + array('FI', 'Pohjanmaa', 'Pohjanmaa'), + array('FI', 'Pirkanmaa', 'Pirkanmaa'), + array('FI', 'Satakunta', 'Satakunta'), + array('FI', 'Keski-Pohjanmaa', 'Keski-Pohjanmaa'), + array('FI', 'Keski-Suomi', 'Keski-Suomi'), + array('FI', 'Varsinais-Suomi', 'Varsinais-Suomi'), + array('FI', 'Etelä-Karjala', 'Etelä-Karjala'), + array('FI', 'Päijät-Häme', 'Päijät-Häme'), + array('FI', 'Kanta-Häme', 'Kanta-Häme'), + array('FI', 'Uusimaa', 'Uusimaa'), + array('FI', 'Itä-Uusimaa', 'Itä-Uusimaa'), + array('FI', 'Kymenlaakso', 'Kymenlaakso'), + array('FI', 'Ahvenanmaa', 'Ahvenanmaa'), + + //ISO-3166-2:EE + array('EE', 'EE-37', 'Harjumaa'), + array('EE', 'EE-39', 'Hiiumaa'), + array('EE', 'EE-44', 'Ida-Virumaa'), + array('EE', 'EE-49', 'Jõgevamaa'), + array('EE', 'EE-51', 'Järvamaa'), + array('EE', 'EE-57', 'Läänemaa'), + array('EE', 'EE-59', 'Lääne-Virumaa'), + array('EE', 'EE-65', 'Põlvamaa'), + array('EE', 'EE-67', 'Pärnumaa'), + array('EE', 'EE-70', 'Raplamaa'), + array('EE', 'EE-74', 'Saaremaa'), + array('EE', 'EE-78', 'Tartumaa'), + array('EE', 'EE-82', 'Valgamaa'), + array('EE', 'EE-84', 'Viljandimaa'), + array('EE', 'EE-86', 'Võrumaa'), + + //After reform of 2009 July + array('LV', 'LV-DGV', 'Daugavpils'),//now become good + array('LV', 'LV-JEL', 'Jelgava'), + array('LV', 'JÄ“kabpils', 'JÄ“kabpils'), + array('LV', 'LV-JUR', 'JÅ«rmala'), + array('LV', 'LV-LPX', 'LiepÄja'), + array('LV', 'LV-LE', 'LiepÄjas novads'), + array('LV', 'LV-REZ', 'RÄ“zekne'), + array('LV', 'LV-RIX', 'RÄ«ga'), + array('LV', 'LV-RI', 'RÄ«gas novads'), + array('LV', 'Valmiera', 'Valmiera'), + array('LV', 'LV-VEN', 'Ventspils'), + array('LV', 'Aglonas novads', 'Aglonas novads'), + array('LV', 'LV-AI', 'Aizkraukles novads'), + array('LV', 'Aizputes novads', 'Aizputes novads'), + array('LV', 'AknÄ«stes novads', 'AknÄ«stes novads'), + array('LV', 'Alojas novads', 'Alojas novads'), + array('LV', 'Alsungas novads', 'Alsungas novads'), + array('LV', 'LV-AL', 'AlÅ«ksnes novads'), + array('LV', 'Amatas novads', 'Amatas novads'), + array('LV', 'Apes novads', 'Apes novads'), + array('LV', 'Auces novads', 'Auces novads'), + array('LV', 'BabÄ«tes novads', 'BabÄ«tes novads'), + array('LV', 'Baldones novads', 'Baldones novads'), + array('LV', 'Baltinavas novads', 'Baltinavas novads'), + array('LV', 'LV-BL', 'Balvu novads'), + array('LV', 'LV-BU', 'Bauskas novads'), + array('LV', 'BeverÄ«nas novads', 'BeverÄ«nas novads'), + array('LV', 'BrocÄ“nu novads', 'BrocÄ“nu novads'), + array('LV', 'Burtnieku novads', 'Burtnieku novads'), + array('LV', 'Carnikavas novads', 'Carnikavas novads'), + array('LV', 'Cesvaines novads', 'Cesvaines novads'), + array('LV', 'Ciblas novads', 'Ciblas novads'), + array('LV', 'LV-CE', 'CÄ“su novads'), + array('LV', 'Dagdas novads', 'Dagdas novads'), + array('LV', 'LV-DA', 'Daugavpils novads'), + array('LV', 'LV-DO', 'Dobeles novads'), + array('LV', 'Dundagas novads', 'Dundagas novads'), + array('LV', 'Durbes novads', 'Durbes novads'), + array('LV', 'Engures novads', 'Engures novads'), + array('LV', 'Garkalnes novads', 'Garkalnes novads'), + array('LV', 'Grobiņas novads', 'Grobiņas novads'), + array('LV', 'LV-GU', 'Gulbenes novads'), + array('LV', 'Iecavas novads', 'Iecavas novads'), + array('LV', 'IkÅ¡Ä·iles novads', 'IkÅ¡Ä·iles novads'), + array('LV', 'IlÅ«kstes novads', 'IlÅ«kstes novads'), + array('LV', 'InÄukalna novads', 'InÄukalna novads'), + array('LV', 'Jaunjelgavas novads', 'Jaunjelgavas novads'), + array('LV', 'Jaunpiebalgas novads', 'Jaunpiebalgas novads'), + array('LV', 'Jaunpils novads', 'Jaunpils novads'), + array('LV', 'LV-JL', 'Jelgavas novads'), + array('LV', 'LV-JK', 'JÄ“kabpils novads'), + array('LV', 'Kandavas novads', 'Kandavas novads'), + array('LV', 'Kokneses novads', 'Kokneses novads'), + array('LV', 'Krimuldas novads', 'Krimuldas novads'), + array('LV', 'Krustpils novads', 'Krustpils novads'), + array('LV', 'LV-KR', 'KrÄslavas novads'), + array('LV', 'LV-KU', 'KuldÄ«gas novads'), + array('LV', 'KÄrsavas novads', 'KÄrsavas novads'), + array('LV', 'LielvÄrdes novads', 'LielvÄrdes novads'), + array('LV', 'LV-LM', 'Limbažu novads'), + array('LV', 'LubÄnas novads', 'LubÄnas novads'), + array('LV', 'LV-LU', 'Ludzas novads'), + array('LV', 'LÄ«gatnes novads', 'LÄ«gatnes novads'), + array('LV', 'LÄ«vÄnu novads', 'LÄ«vÄnu novads'), + array('LV', 'LV-MA', 'Madonas novads'), + array('LV', 'Mazsalacas novads', 'Mazsalacas novads'), + array('LV', 'MÄlpils novads', 'MÄlpils novads'), + array('LV', 'MÄrupes novads', 'MÄrupes novads'), + array('LV', 'NaukÅ¡Ä“nu novads', 'NaukÅ¡Ä“nu novads'), + array('LV', 'Neretas novads', 'Neretas novads'), + array('LV', 'NÄ«cas novads', 'NÄ«cas novads'), + array('LV', 'LV-OG', 'Ogres novads'), + array('LV', 'Olaines novads', 'Olaines novads'), + array('LV', 'Ozolnieku novads', 'Ozolnieku novads'), + array('LV', 'LV-PR', 'Preiļu novads'), + array('LV', 'Priekules novads', 'Priekules novads'), + array('LV', 'Priekuļu novads', 'Priekuļu novads'), + array('LV', 'PÄrgaujas novads', 'PÄrgaujas novads'), + array('LV', 'PÄvilostas novads', 'PÄvilostas novads'), + array('LV', 'Pļaviņu novads', 'Pļaviņu novads'), + array('LV', 'Raunas novads', 'Raunas novads'), + array('LV', 'Riebiņu novads', 'Riebiņu novads'), + array('LV', 'Rojas novads', 'Rojas novads'), + array('LV', 'Ropažu novads', 'Ropažu novads'), + array('LV', 'Rucavas novads', 'Rucavas novads'), + array('LV', 'RugÄju novads', 'RugÄju novads'), + array('LV', 'RundÄles novads', 'RundÄles novads'), + array('LV', 'LV-RE', 'RÄ“zeknes novads'), + array('LV', 'RÅ«jienas novads', 'RÅ«jienas novads'), + array('LV', 'SalacgrÄ«vas novads', 'SalacgrÄ«vas novads'), + array('LV', 'Salas novads', 'Salas novads'), + array('LV', 'Salaspils novads', 'Salaspils novads'), + array('LV', 'LV-SA', 'Saldus novads'), + array('LV', 'Saulkrastu novads', 'Saulkrastu novads'), + array('LV', 'Siguldas novads', 'Siguldas novads'), + array('LV', 'Skrundas novads', 'Skrundas novads'), + array('LV', 'SkrÄ«veru novads', 'SkrÄ«veru novads'), + array('LV', 'Smiltenes novads', 'Smiltenes novads'), + array('LV', 'Stopiņu novads', 'Stopiņu novads'), + array('LV', 'StrenÄu novads', 'StrenÄu novads'), + array('LV', 'SÄ“jas novads', 'SÄ“jas novads'), + array('LV', 'LV-TA', 'Talsu novads'), + array('LV', 'LV-TU', 'Tukuma novads'), + array('LV', 'TÄ“rvetes novads', 'TÄ“rvetes novads'), + array('LV', 'Vaiņodes novads', 'Vaiņodes novads'), + array('LV', 'LV-VK', 'Valkas novads'), + array('LV', 'LV-VM', 'Valmieras novads'), + array('LV', 'VarakļÄnu novads', 'VarakļÄnu novads'), + array('LV', 'Vecpiebalgas novads', 'Vecpiebalgas novads'), + array('LV', 'Vecumnieku novads', 'Vecumnieku novads'), + array('LV', 'LV-VE', 'Ventspils novads'), + array('LV', 'ViesÄ«tes novads', 'ViesÄ«tes novads'), + array('LV', 'Viļakas novads', 'Viļakas novads'), + array('LV', 'ViļÄnu novads', 'ViļÄnu novads'), + array('LV', 'VÄrkavas novads', 'VÄrkavas novads'), + array('LV', 'Zilupes novads', 'Zilupes novads'), + array('LV', 'Ä€dažu novads', 'Ä€dažu novads'), + array('LV', 'Ä’rgļu novads', 'Ä’rgļu novads'), + array('LV', 'Ķeguma novads', 'Ķeguma novads'), + array('LV', 'Ķekavas novads', 'Ķekavas novads'), + + //ISO-3166-2:LT + array('LT', 'LT-AL', 'Alytaus Apskritis'), + array('LT', 'LT-KU', 'Kauno Apskritis'), + array('LT', 'LT-KL', 'KlaipÄ—dos Apskritis'), + array('LT', 'LT-MR', 'MarijampolÄ—s Apskritis'), + array('LT', 'LT-PN', 'Panevėžio Apskritis'), + array('LT', 'LT-SA', 'Å iaulių Apskritis'), + array('LT', 'LT-TA', 'TauragÄ—s Apskritis'), + array('LT', 'LT-TE', 'TelÅ¡ių Apskritis'), + array('LT', 'LT-UT', 'Utenos Apskritis'), + array('LT', 'LT-VL', 'Vilniaus Apskritis'), +); + +foreach ($regionsToIns as $row) { + if (! ($connection->fetchOne("SELECT 1 FROM `{$regionTable}` WHERE `country_id` = :country_id && `code` = :code", array('country_id' => $row[0], 'code' => $row[1])))) { + $connection->insert($regionTable, array( + 'country_id' => $row[0], + 'code' => $row[1], + 'default_name' => $row[2] + )); + } +} + diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.2-0.8.3.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.2-0.8.3.php index 5581605e..d9c733e2 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.2-0.8.3.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.2-0.8.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.3-0.8.4.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.3-0.8.4.php index 93364612..855116fe 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.3-0.8.4.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.3-0.8.4.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.4-0.8.5.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.4-0.8.5.php index b2b14d4c..1a38c857 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.4-0.8.5.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.4-0.8.5.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.5-0.8.6.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.5-0.8.6.php index 0fe67ecb..63a94a1f 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.5-0.8.6.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.5-0.8.6.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.6-0.8.7.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.6-0.8.7.php index bd1f2bc1..4e3a82b4 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.6-0.8.7.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.6-0.8.7.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.7-0.8.8.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.7-0.8.8.php index e20b2138..6a0023ea 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.7-0.8.8.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.7-0.8.8.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.8-0.8.9.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.8-0.8.9.php index 8c9fa0f9..32c8a8d4 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.8-0.8.9.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.8-0.8.9.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.9-0.8.10.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.9-0.8.10.php index 755bb3a1..92ab3025 100644 --- a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.9-0.8.10.php +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-0.8.9-0.8.10.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Directory - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..cb54211e --- /dev/null +++ b/app/code/core/Mage/Directory/sql/directory_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,256 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('directory/country_region_name'), + 'FK_DIRECTORY_REGION_NAME_REGION' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('directory/country_format'), + 'COUNTRY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('directory/country_region'), + 'FK_REGION_COUNTRY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('directory/country_region_name'), + 'FK_DIRECTORY_REGION_NAME_REGION' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('directory/currency_rate'), + 'FK_CURRENCY_RATE_TO' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('directory/country') => array( + 'columns' => array( + 'country_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 2, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Country Id in ISO-2' + ), + 'iso2_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 2, + 'nullable' => false, + 'comment' => 'Country ISO-2 format' + ), + 'iso3_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 3, + 'nullable' => false, + 'comment' => 'Country ISO-3' + ) + ), + 'comment' => 'Directory Country' + ), + $installer->getTable('directory/country_format') => array( + 'columns' => array( + 'country_format_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Country Format Id' + ), + 'country_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 2, + 'nullable' => false, + 'comment' => 'Country Id in ISO-2' + ), + 'type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 30, + 'nullable' => false, + 'comment' => 'Country Format Type' + ), + 'format' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'nullable' => false, + 'comment' => 'Country Format' + ) + ), + 'comment' => 'Directory Country Format' + ), + $installer->getTable('directory/country_region') => array( + 'columns' => array( + 'region_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Region Id' + ), + 'country_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 4, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Country Id in ISO-2' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'nullable' => false, + 'comment' => 'Region code' + ), + 'default_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Region Name' + ) + ), + 'comment' => 'Directory Country Region' + ), + $installer->getTable('directory/country_region_name') => array( + 'columns' => array( + 'locale' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 8, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Locale' + ), + 'region_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Region Id' + ), + 'name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Region Name' + ) + ), + 'comment' => 'Directory Country Region Name' + ), + $installer->getTable('directory/currency_rate') => array( + 'columns' => array( + 'currency_from' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 3, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Currency Code Convert From' + ), + 'currency_to' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 3, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Currency Code Convert To' + ), + 'rate' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DECIMAL, + 'scale' => 12, + 'precision' => 24, + 'nullable' => false, + 'default' => '0.000000000000', + 'comment' => 'Currency Conversion Rate' + ) + ), + 'comment' => 'Directory Currency Rate' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('directory/country_format'), + $installer->getIdxName( + 'directory/country_format', + array('country_id', 'type'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('country_id', 'type'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('directory/country_region'), + $installer->getIdxName('directory/country_region', array('country_id')), + array('country_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('directory/country_region_name'), + $installer->getIdxName('directory/country_region_name', array('region_id')), + array('region_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('directory/currency_rate'), + $installer->getIdxName('directory/currency_rate', array('currency_to')), + array('currency_to') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('directory/country_region_name', 'region_id', 'directory/country_region', 'region_id'), + $installer->getTable('directory/country_region_name'), + 'region_id', + $installer->getTable('directory/country_region'), + 'region_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php index a4360e61..07e3d9cc 100644 --- a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Js.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php index 82274a24..c4d3d3d1 100644 --- a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Main/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -78,12 +78,14 @@ protected function _prepareForm() $yesno = Mage::getModel('adminhtml/system_config_source_yesno')->toOptionArray(); + $validateClass = sprintf('validate-code validate-length maximum-length-%d', + Mage_Eav_Model_Entity_Attribute::ATTRIBUTE_CODE_MAX_LENGTH); $fieldset->addField('attribute_code', 'text', array( 'name' => 'attribute_code', 'label' => Mage::helper('eav')->__('Attribute Code'), 'title' => Mage::helper('eav')->__('Attribute Code'), - 'note' => Mage::helper('eav')->__('For internal use. Must be unique with no spaces'), - 'class' => 'validate-code', + 'note' => Mage::helper('eav')->__('For internal use. Must be unique with no spaces. Maximum length of attribute code must be less then %s symbols', Mage_Eav_Model_Entity_Attribute::ATTRIBUTE_CODE_MAX_LENGTH), + 'class' => $validateClass, 'required' => true, )); diff --git a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php index 0df6153f..913ce127 100644 --- a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Edit/Options/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php index 1c0432c1..df3644a8 100644 --- a/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php +++ b/app/code/core/Mage/Eav/Block/Adminhtml/Attribute/Grid/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Exception.php b/app/code/core/Mage/Eav/Exception.php index 04092a3b..7784d2ac 100644 --- a/app/code/core/Mage/Eav/Exception.php +++ b/app/code/core/Mage/Eav/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Helper/Data.php b/app/code/core/Mage/Eav/Helper/Data.php index 4d777a32..ccf4e0e7 100644 --- a/app/code/core/Mage/Eav/Helper/Data.php +++ b/app/code/core/Mage/Eav/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,6 +29,11 @@ */ class Mage_Eav_Helper_Data extends Mage_Core_Helper_Abstract { + /** + * XML path to input types validator data in config + */ + const XML_PATH_VALIDATOR_DATA_INPUT_TYPES = 'general/validator_data/input_types'; + protected $_attributesLockedFields = array(); protected $_entityTypeFrontendClasses = array(); @@ -118,14 +123,24 @@ public function getAttributeLockedFields($entityTypeCode) if (isset($this->_attributesLockedFields[$entityTypeCode])) { return $this->_attributesLockedFields[$entityTypeCode]; } - $lockedAttributeFields = array(); $_data = Mage::app()->getConfig()->getNode('global/eav_attributes/' . $entityTypeCode); if ($_data) { foreach ($_data->children() as $attribute) { - $this->_attributesLockedFields[$entityTypeCode][(string)$attribute->code] = array_keys($attribute->locked_fields->asArray()); + $this->_attributesLockedFields[$entityTypeCode][(string)$attribute->code] = + array_keys($attribute->locked_fields->asArray()); } return $this->_attributesLockedFields[$entityTypeCode]; } return array(); } + + /** + * Get input types validator data + * + * @return array + */ + public function getInputTypesValidatorData() + { + return Mage::getStoreConfig(self::XML_PATH_VALIDATOR_DATA_INPUT_TYPES); + } } diff --git a/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php b/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php index a134bee8..51c832ba 100644 --- a/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php +++ b/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype.php @@ -20,12 +20,11 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Eav_Model_Adminhtml_System_Config_Source_Inputtype { - public function toOptionArray() { return array( @@ -37,5 +36,4 @@ public function toOptionArray() array('value' => 'select', 'label' => Mage::helper('eav')->__('Dropdown')) ); } - } diff --git a/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php b/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php new file mode 100644 index 00000000..23f24863 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Adminhtml/System/Config/Source/Inputtype/Validator.php @@ -0,0 +1,87 @@ + + */ +class Mage_Eav_Model_Adminhtml_System_Config_Source_Inputtype_Validator extends Zend_Validate_InArray +{ + + /** + * Construct + */ + public function __construct() + { + //set data haystack + /** @var $helper Mage_Eav_Helper_Data */ + $helper = Mage::helper('eav'); + $haystack = $helper->getInputTypesValidatorData(); + + //reset message template and set custom + $this->_messageTemplates = null; + $this->_initMessageTemplates(); + + //parent construct with options + parent::__construct(array( + 'haystack' => $haystack, + 'strict' => true, + )); + } + + /** + * Initialize message templates with translating + * + * @return Mage_Adminhtml_Model_Core_File_Validator_SavePath_Available + */ + protected function _initMessageTemplates() + { + if (!$this->_messageTemplates) { + $this->_messageTemplates = array( + self::NOT_IN_ARRAY => + Mage::helper('core')->__('Input type "%value%" not found in the input types list.'), + ); + } + return $this; + } + + /** + * Add input type to haystack + * + * @param string $type + * @return Mage_Eav_Model_Adminhtml_System_Config_Source_Inputtype_Validator + */ + public function addInputType($type) + { + if (!in_array((string) $type, $this->_haystack, true)) { + $this->_haystack[] = (string) $type; + } + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute.php b/app/code/core/Mage/Eav/Model/Attribute.php new file mode 100644 index 00000000..74c6693e --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute.php @@ -0,0 +1,196 @@ + + */ +abstract class Mage_Eav_Model_Attribute extends Mage_Eav_Model_Entity_Attribute +{ + /** + * Name of the module + * Override it + */ + //const MODULE_NAME = 'Mage_Eav'; + + /** + * Prefix of model events object + * + * @var string + */ + protected $_eventObject = 'attribute'; + + /** + * Active Website instance + * + * @var Mage_Core_Model_Website + */ + protected $_website; + + /** + * Set active website instance + * + * @param Mage_Core_Model_Website|int $website + * @return Mage_Eav_Model_Attribute + */ + public function setWebsite($website) + { + $this->_website = Mage::app()->getWebsite($website); + return $this; + } + + /** + * Return active website instance + * + * @return Mage_Core_Model_Website + */ + public function getWebsite() + { + if (is_null($this->_website)) { + $this->_website = Mage::app()->getWebsite(); + } + + return $this->_website; + } + + /** + * Processing object after save data + * + * @return Mage_Eav_Model_Attribute + */ + protected function _afterSave() + { + Mage::getSingleton('eav/config')->clear(); + return parent::_afterSave(); + } + + /** + * Return forms in which the attribute + * + * @return array + */ + public function getUsedInForms() + { + $forms = $this->getData('used_in_forms'); + if (is_null($forms)) { + $forms = $this->_getResource()->getUsedInForms($this); + $this->setData('used_in_forms', $forms); + } + return $forms; + } + + /** + * Return validate rules + * + * @return array + */ + public function getValidateRules() + { + $rules = $this->getData('validate_rules'); + if (is_array($rules)) { + return $rules; + } else if (!empty($rules)) { + return unserialize($rules); + } + return array(); + } + + /** + * Set validate rules + * + * @param array|string $rules + * @return Mage_Eav_Model_Attribute + */ + public function setValidateRules($rules) + { + if (empty($rules)) { + $rules = null; + } else if (is_array($rules)) { + $rules = serialize($rules); + } + $this->setData('validate_rules', $rules); + + return $this; + } + + /** + * Return scope value by key + * + * @param string $key + * @return mixed + */ + protected function _getScopeValue($key) + { + $scopeKey = sprintf('scope_%s', $key); + if ($this->getData($scopeKey) !== null) { + return $this->getData($scopeKey); + } + return $this->getData($key); + } + + /** + * Return is attribute value required + * + * @return mixed + */ + public function getIsRequired() + { + return $this->_getScopeValue('is_required'); + } + + /** + * Return is visible attribute flag + * + * @return mixed + */ + public function getIsVisible() + { + return $this->_getScopeValue('is_visible'); + } + + /** + * Return default value for attribute + * + * @return mixed + */ + public function getDefaultValue() + { + return $this->_getScopeValue('default_value'); + } + + /** + * Return count of lines for multiply line attribute + * + * @return mixed + */ + public function getMultilineCount() + { + return $this->_getScopeValue('multiline_count'); + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data.php b/app/code/core/Mage/Eav/Model/Attribute/Data.php new file mode 100644 index 00000000..8ea55e6c --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data.php @@ -0,0 +1,85 @@ + + */ +class Mage_Eav_Model_Attribute_Data +{ + const OUTPUT_FORMAT_JSON = 'json'; + const OUTPUT_FORMAT_TEXT = 'text'; + const OUTPUT_FORMAT_HTML = 'html'; + const OUTPUT_FORMAT_PDF = 'pdf'; + const OUTPUT_FORMAT_ONELINE = 'oneline'; + const OUTPUT_FORMAT_ARRAY = 'array'; // available only for multiply attributes + + /** + * Array of attribute data models by input type + * + * @var array + */ + protected static $_dataModels = array(); + + /** + * Return attribute data model by attribute + * Set entity to data model (need for work) + * + * @param Mage_Eav_Model_Attribute $attribute + * @param Mage_Core_Model_Abstract $entity + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public static function factory(Mage_Eav_Model_Attribute $attribute, Mage_Core_Model_Abstract $entity) + { + /* @var $dataModel Mage_Eav_Model_Attribute_Data_Abstract */ + $dataModelClass = $attribute->getDataModel(); + if (!empty($dataModelClass)) { + if (empty(self::$_dataModels[$dataModelClass])) { + $dataModel = Mage::getModel($dataModelClass); + self::$_dataModels[$dataModelClass] = $dataModel; + } else { + $dataModel = self::$_dataModels[$dataModelClass]; + } + } else { + if (empty(self::$_dataModels[$attribute->getFrontendInput()])) { + $dataModelClass = sprintf('eav/attribute_data_%s', $attribute->getFrontendInput()); + $dataModel = Mage::getModel($dataModelClass); + self::$_dataModels[$attribute->getFrontendInput()] = $dataModel; + } else { + $dataModel = self::$_dataModels[$attribute->getFrontendInput()]; + } + } + + $dataModel->setAttribute($attribute); + $dataModel->setEntity($entity); + + return $dataModel; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Abstract.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Abstract.php new file mode 100644 index 00000000..658e27e9 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Abstract.php @@ -0,0 +1,566 @@ + + */ +abstract class Mage_Eav_Model_Attribute_Data_Abstract +{ + /** + * Attribute instance + * + * @var Mage_Eav_Model_Attribute + */ + protected $_attribite; + + /** + * Entity instance + * + * @var Mage_Core_Model_Abstract + */ + protected $_entity; + + /** + * Request Scope name + * + * @var string + */ + protected $_requestScope; + + /** + * Scope visibility flag + * + * @var boolean + */ + protected $_requestScopeOnly = true; + + /** + * Is AJAX request flag + * + * @var boolean + */ + protected $_isAjax = false; + + /** + * Array of full extracted data + * Needed for depends attributes + * + * @var array + */ + protected $_extractedData = array(); + + /** + * Mage_Core_Model_Locale FORMAT + * + * @var string + */ + protected $_dateFilterFormat; + + /** + * Set attribute instance + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public function setAttribute(Mage_Eav_Model_Entity_Attribute_Abstract $attribute) + { + $this->_attribite = $attribute; + return $this; + } + + /** + * Return Attribute instance + * + * @throws Mage_Core_Exception + * @return Mage_Eav_Model_Attribute + */ + public function getAttribute() + { + if (!$this->_attribite) { + Mage::throwException(Mage::helper('eav')->__('Attribute object is undefined')); + } + return $this->_attribite; + } + + /** + * Set Request scope + * + * @param string $scope + * @return string + */ + public function setRequestScope($scope) + { + $this->_requestScope = $scope; + return $this; + } + + /** + * Set scope visibility + * Search value only in scope or search value in scope and global + * + * @param boolean $flag + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public function setRequestScopeOnly($flag) + { + $this->_requestScopeOnly = (bool)$flag; + return $this; + } + + /** + * Set entity instance + * + * @param Mage_Core_Model_Abstract $entity + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public function setEntity(Mage_Core_Model_Abstract $entity) + { + $this->_entity = $entity; + return $this; + } + + /** + * Returns entity instance + * + * @return Mage_Core_Model_Abstract + */ + public function getEntity() + { + if (!$this->_entity) { + Mage::throwException(Mage::helper('eav')->__('Entity object is undefined')); + } + return $this->_entity; + } + + /** + * Set array of full extracted data + * + * @param array $data + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public function setExtractedData(array $data) + { + $this->_extractedData = $data; + return $this; + } + + /** + * Return extracted data + * + * @param string $index + * @return mixed + */ + public function getExtractedData($index = null) + { + if (!is_null($index)) { + if (isset($this->_extractedData[$index])) { + return $this->_extractedData[$index]; + } + return null; + } + return $this->_extractedData; + } + + /** + * Apply attribute input filter to value + * + * @param string $value + * @return string + */ + protected function _applyInputFilter($value) + { + if ($value === false) { + return false; + } + + $filter = $this->_getFormFilter(); + if ($filter) { + $value = $filter->inputFilter($value); + } + + return $value; + } + + /** + * Return Data Form Input/Output Filter + * + * @return Varien_Data_Form_Filter_Interface|false + */ + protected function _getFormFilter() + { + $filterCode = $this->getAttribute()->getInputFilter(); + if ($filterCode) { + $filterClass = 'Varien_Data_Form_Filter_' . ucfirst($filterCode); + if ($filterCode == 'date') { + $filter = new $filterClass($this->_dateFilterFormat(), Mage::app()->getLocale()->getLocale()); + } else { + $filter = new $filterClass(); + } + return $filter; + } + return false; + } + + /** + * Get/Set/Reset date filter format + * + * @param string|null|false $format + * @return Mage_Eav_Model_Attribute_Data_Abstract|string + */ + protected function _dateFilterFormat($format = null) + { + if (is_null($format)) { + // get format + if (is_null($this->_dateFilterFormat)) { + $this->_dateFilterFormat = Mage_Core_Model_Locale::FORMAT_TYPE_SHORT; + } + return Mage::app()->getLocale()->getDateFormat($this->_dateFilterFormat); + } else if ($format === false) { + // reset value + $this->_dateFilterFormat = null; + return $this; + } + + $this->_dateFilterFormat = $format; + return $this; + } + + /** + * Apply attribute output filter to value + * + * @param string $value + * @return string + */ + protected function _applyOutputFilter($value) + { + $filter = $this->_getFormFilter(); + if ($filter) { + $value = $filter->outputFilter($value); + } + + return $value; + } + + /** + * Validate value by attribute input validation rule + * + * @param string $value + * @return string + */ + protected function _validateInputRule($value) + { + // skip validate empty value + if (empty($value)) { + return true; + } + + $label = $this->getAttribute()->getStoreLabel(); + $validateRules = $this->getAttribute()->getValidateRules(); + + if (!empty($validateRules['input_validation'])) { + switch ($validateRules['input_validation']) { + case 'alphanumeric': + $validator = new Zend_Validate_Alnum(true); + $validator->setMessage( + Mage::helper('eav')->__('"%s" invalid type entered.', $label), + Zend_Validate_Alnum::INVALID + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" has not only alphabetic and digit characters.', $label), + Zend_Validate_Alnum::NOT_ALNUM + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is an empty string.', $label), + Zend_Validate_Alnum::STRING_EMPTY + ); + if (!$validator->isValid($value)) { + return $validator->getMessages(); + } + break; + case 'numeric': + $validator = new Zend_Validate_Digits(); + $validator->setMessage( + Mage::helper('eav')->__('"%s" invalid type entered.', $label), + Zend_Validate_Digits::INVALID + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" contains not only digit characters.', $label), + Zend_Validate_Digits::NOT_DIGITS + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is an empty string.', $label), + Zend_Validate_Digits::STRING_EMPTY + ); + if (!$validator->isValid($value)) { + return $validator->getMessages(); + } + break; + case 'alpha': + $validator = new Zend_Validate_Alpha(true); + $validator->setMessage( + Mage::helper('eav')->__('"%s" invalid type entered.', $label), + Zend_Validate_Alpha::INVALID + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" has not only alphabetic characters.', $label), + Zend_Validate_Alpha::NOT_ALPHA + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is an empty string.', $label), + Zend_Validate_Alpha::STRING_EMPTY + ); + if (!$validator->isValid($value)) { + return $validator->getMessages(); + } + break; + case 'email': + /** + $this->__("'%value%' appears to be a DNS hostname but the given punycode notation cannot be decoded") + $this->__("Invalid type given. String expected") + $this->__("'%value%' appears to be a DNS hostname but contains a dash in an invalid position") + $this->__("'%value%' does not match the expected structure for a DNS hostname") + $this->__("'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'") + $this->__("'%value%' does not appear to be a valid local network name") + $this->__("'%value%' does not appear to be a valid URI hostname") + $this->__("'%value%' appears to be an IP address, but IP addresses are not allowed") + $this->__("'%value%' appears to be a local network name but local network names are not allowed") + $this->__("'%value%' appears to be a DNS hostname but cannot extract TLD part") + $this->__("'%value%' appears to be a DNS hostname but cannot match TLD against known list") + */ + $validator = new Zend_Validate_EmailAddress(); + $validator->setMessage( + Mage::helper('eav')->__('"%s" invalid type entered.', $label), + Zend_Validate_EmailAddress::INVALID + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid email address.', $label), + Zend_Validate_EmailAddress::INVALID_FORMAT + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid hostname.', $label), + Zend_Validate_EmailAddress::INVALID_HOSTNAME + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid hostname.', $label), + Zend_Validate_EmailAddress::INVALID_MX_RECORD + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid hostname.', $label), + Zend_Validate_EmailAddress::INVALID_MX_RECORD + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid email address.', $label), + Zend_Validate_EmailAddress::DOT_ATOM + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid email address.', $label), + Zend_Validate_EmailAddress::QUOTED_STRING + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid email address.', $label), + Zend_Validate_EmailAddress::INVALID_LOCAL_PART + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" exceeds the allowed length.', $label), + Zend_Validate_EmailAddress::LENGTH_EXCEEDED + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be an IP address, but IP addresses are not allowed"), + Zend_Validate_Hostname::IP_ADDRESS_NOT_ALLOWED + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a DNS hostname but cannot match TLD against known list"), + Zend_Validate_Hostname::UNKNOWN_TLD + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a DNS hostname but contains a dash in an invalid position"), + Zend_Validate_Hostname::INVALID_DASH + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'"), + Zend_Validate_Hostname::INVALID_HOSTNAME_SCHEMA + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a DNS hostname but cannot extract TLD part"), + Zend_Validate_Hostname::UNDECIPHERABLE_TLD + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' does not appear to be a valid local network name"), + Zend_Validate_Hostname::INVALID_LOCAL_NAME + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a local network name but local network names are not allowed"), + Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED + ); + $validator->setMessage( + Mage::helper('customer')->__("'%value%' appears to be a DNS hostname but the given punycode notation cannot be decoded"), + Zend_Validate_Hostname::CANNOT_DECODE_PUNYCODE + ); + if (!$validator->isValid($value)) { + return array_unique($validator->getMessages()); + } + break; + case 'url': + $parsedUrl = parse_url($value); + if ($parsedUrl === false || empty($parsedUrl['scheme']) || empty($parsedUrl['host'])) { + return array(Mage::helper('eav')->__('"%s" is not a valid URL.', $label)); + } + $validator = new Zend_Validate_Hostname(); + if (!$validator->isValid($parsedUrl['host'])) { + return array(Mage::helper('eav')->__('"%s" is not a valid URL.', $label)); + } + break; + case 'date': + $validator = new Zend_Validate_Date(Varien_Date::DATE_INTERNAL_FORMAT); + $validator->setMessage( + Mage::helper('eav')->__('"%s" invalid type entered.', $label), + Zend_Validate_Date::INVALID + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" is not a valid date.', $label), + Zend_Validate_Date::INVALID_DATE + ); + $validator->setMessage( + Mage::helper('eav')->__('"%s" does not fit the entered date format.', $label), + Zend_Validate_Date::FALSEFORMAT + ); + if (!$validator->isValid($value)) { + return array_unique($validator->getMessages()); + } + + break; + } + } + return true; + } + + /** + * Set is AJAX Request flag + * + * @param boolean $flag + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + public function setIsAjaxRequest($flag = true) + { + $this->_isAjax = (bool)$flag; + return $this; + } + + /** + * Return is AJAX Request + * + * @return boolean + */ + public function getIsAjaxRequest() + { + return $this->_isAjax; + } + + /** + * Return Original Attribute value from Request + * + * @param Zend_Controller_Request_Http $request + * @return mixed + */ + protected function _getRequestValue(Zend_Controller_Request_Http $request) + { + $attrCode = $this->getAttribute()->getAttributeCode(); + if ($this->_requestScope) { + if (strpos($this->_requestScope, '/') !== false) { + $params = $request->getParams(); + $parts = explode('/', $this->_requestScope); + foreach ($parts as $part) { + if (isset($params[$part])) { + $params = $params[$part]; + } else { + $params = array(); + } + } + } else { + $params = $request->getParam($this->_requestScope); + } + + if (isset($params[$attrCode])) { + $value = $params[$attrCode]; + } else { + $value = false; + } + + if (!$this->_requestScopeOnly && $value === false) { + $value = $request->getParam($attrCode, false); + } + } else { + $value = $request->getParam($attrCode, false); + } + return $value; + } + + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + abstract public function extractValue(Zend_Controller_Request_Http $request); + + /** + * Validate data + * + * @param array|string $value + * @throws Mage_Core_Exception + * @return boolean + */ + abstract public function validateValue($value); + + /** + * Export attribute value to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + abstract public function compactValue($value); + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + abstract public function restoreValue($value); + + /** + * Return formated attribute value from entity model + * + * @param string $format + * @return string|array + */ + abstract public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT); +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Boolean.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Boolean.php new file mode 100644 index 00000000..bca2c004 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Boolean.php @@ -0,0 +1,58 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Boolean extends Mage_Eav_Model_Attribute_Data_Select +{ + /** + * Return a text for option value + * + * @param int $value + * @return string + */ + protected function _getOptionText($value) + { + switch ($value) { + case '0': + $text = Mage::helper('eav')->__('No'); + break; + case '1': + $text = Mage::helper('eav')->__('Yes'); + break; + default: + $text = ''; + break; + } + return $text; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Date.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Date.php new file mode 100644 index 00000000..3c25f955 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Date.php @@ -0,0 +1,153 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Date extends Mage_Eav_Model_Attribute_Data_Abstract +{ + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + $value = $this->_getRequestValue($request); + return $this->_applyInputFilter($value); + } + + /** + * Validate data + * Return true or array of errors + * + * @param array|string $value + * @return boolean|array + */ + public function validateValue($value) + { + $errors = array(); + $attribute = $this->getAttribute(); + $label = $attribute->getStoreLabel(); + + if ($value === false) { + // try to load original value and validate it + $value = $this->getEntity()->getDataUsingMethod($attribute->getAttributeCode()); + } + + if ($attribute->getIsRequired() && empty($value)) { + $errors[] = Mage::helper('eav')->__('"%s" is a required value.', $label); + } + + if (!$errors && !$attribute->getIsRequired() && empty($value)) { + return true; + } + + $result = $this->_validateInputRule($value); + if ($result !== true) { + $errors = array_merge($errors, $result); + } + + //range validation + $validateRules = $attribute->getValidateRules(); + if ((!empty($validateRules['date_range_min']) && (strtotime($value) < $validateRules['date_range_min'])) + || (!empty($validateRules['date_range_max']) && (strtotime($value) > $validateRules['date_range_max'])) + ) { + if (!empty($validateRules['date_range_min']) && !empty($validateRules['date_range_max'])) { + $errors[] = Mage::helper('customer')->__('Please enter a valid date between %s and %s at %s.', date('d/m/Y', $validateRules['date_range_min']), date('d/m/Y', $validateRules['date_range_max']), $label); + } elseif (!empty($validateRules['date_range_min'])) { + $errors[] = Mage::helper('customer')->__('Please enter a valid date equal to or greater than %s at %s.', date('d/m/Y', $validateRules['date_range_min']), $label); + } elseif (!empty($validateRules['date_range_max'])) { + $errors[] = Mage::helper('customer')->__('Please enter a valid date less than or equal to %s at %s.', date('d/m/Y', $validateRules['date_range_max']), $label); + } + } + + if (count($errors) == 0) { + return true; + } + + return $errors; + } + + /** + * Export attribute value to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Date + */ + public function compactValue($value) + { + if ($value !== false) { + if (empty($value)) { + $value = null; + } + $this->getEntity()->setDataUsingMethod($this->getAttribute()->getAttributeCode(), $value); + } + return $this; + } + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Date + */ + public function restoreValue($value) + { + return $this->compactValue($value); + } + + /** + * Return formated attribute value from entity model + * + * @param string $format + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $value = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + if ($value) { + switch ($format) { + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT: + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_HTML: + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_PDF: + $this->_dateFilterFormat(Mage_Core_Model_Locale::FORMAT_TYPE_MEDIUM); + break; + } + $value = $this->_applyOutputFilter($value); + } + + $this->_dateFilterFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); + + return $value; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/File.php b/app/code/core/Mage/Eav/Model/Attribute/Data/File.php new file mode 100644 index 00000000..12d19e3b --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/File.php @@ -0,0 +1,280 @@ + + */ +class Mage_Eav_Model_Attribute_Data_File extends Mage_Eav_Model_Attribute_Data_Abstract +{ + /** + * Validator for check not protected extensions + * + * @var Mage_Core_Model_File_Validator_NotProtectedExtension + */ + protected $_validatorNotProtectedExtensions; + + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + if ($this->getIsAjaxRequest()) { + return false; + } + + $extend = $this->_getRequestValue($request); + + $attrCode = $this->getAttribute()->getAttributeCode(); + if ($this->_requestScope) { + $value = array(); + if (strpos($this->_requestScope, '/') !== false) { + $scopes = explode('/', $this->_requestScope); + $mainScope = array_shift($scopes); + } else { + $mainScope = $this->_requestScope; + $scopes = array(); + } + + if (!empty($_FILES[$mainScope])) { + foreach ($_FILES[$mainScope] as $fileKey => $scopeData) { + foreach ($scopes as $scopeName) { + if (isset($scopeData[$scopeName])) { + $scopeData = $scopeData[$scopeName]; + } else { + $scopeData[$scopeName] = array(); + } + } + + if (isset($scopeData[$attrCode])) { + $value[$fileKey] = $scopeData[$attrCode]; + } + } + } else { + $value = array(); + } + } else { + if (isset($_FILES[$attrCode])) { + $value = $_FILES[$attrCode]; + } else { + $value = array(); + } + } + + if (!empty($extend['delete'])) { + $value['delete'] = true; + } + + return $value; + } + + /** + * Validate file by attribute validate rules + * Return array of errors + * + * @param array $value + * @return array + */ + protected function _validateByRules($value) + { + $label = $this->getAttribute()->getStoreLabel(); + $rules = $this->getAttribute()->getValidateRules(); + $extension = pathinfo($value['name'], PATHINFO_EXTENSION); + + if (!empty($rules['file_extensions'])) { + $extensions = explode(',', $rules['file_extensions']); + $extensions = array_map('trim', $extensions); + if (!in_array($extension, $extensions)) { + return array( + Mage::helper('eav')->__('"%s" is not a valid file extension.', $label) + ); + } + } + + /** + * Check protected file extension + */ + /** @var $validator Mage_Core_Model_File_Validator_NotProtectedExtension */ + $validator = Mage::getSingleton('core/file_validator_notProtectedExtension'); + if (!$validator->isValid($extension)) { + return $validator->getMessages(); + } + + if (!is_uploaded_file($value['tmp_name'])) { + return array( + Mage::helper('eav')->__('"%s" is not a valid file.', $label) + ); + } + + if (!empty($rules['max_file_size'])) { + $size = $value['size']; + if ($rules['max_file_size'] < $size) { + return array( + Mage::helper('eav')->__('"%s" exceeds the allowed file size.', $label) + ); + }; + } + + return array(); + } + + /** + * Validate data + * + * @param array|string $value + * @throws Mage_Core_Exception + * @return boolean + */ + public function validateValue($value) + { + if ($this->getIsAjaxRequest()) { + return true; + } + + $errors = array(); + $attribute = $this->getAttribute(); + $label = $attribute->getStoreLabel(); + + $toDelete = !empty($value['delete']) ? true : false; + $toUpload = !empty($value['tmp_name']) ? true : false; + + if (!$toUpload && !$toDelete && $this->getEntity()->getData($attribute->getAttributeCode())) { + return true; + } + + if (!$attribute->getIsRequired() && !$toUpload) { + return true; + } + + if ($attribute->getIsRequired() && !$toUpload) { + $errors[] = Mage::helper('eav')->__('"%s" is a required value.', $label); + } + + if ($toUpload) { + $errors = array_merge($errors, $this->_validateByRules($value)); + } + + if (count($errors) == 0) { + return true; + } + + return $errors; + } + + /** + * Export attribute value to entity model + * + * @param Mage_Core_Model_Abstract $entity + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_File + */ + public function compactValue($value) + { + if ($this->getIsAjaxRequest()) { + return $this; + } + + $attribute = $this->getAttribute(); + $original = $this->getEntity()->getData($attribute->getAttributeCode()); + $toDelete = false; + if ($original) { + if (!$attribute->getIsRequired() && !empty($value['delete'])) { + $toDelete = true; + } + if (!empty($value['tmp_name'])) { + $toDelete = true; + } + } + + $path = Mage::getBaseDir('media') . DS . $attribute->getEntity()->getEntityTypeCode(); + + // unlink entity file + if ($toDelete) { + $this->getEntity()->setData($attribute->getAttributeCode(), ''); + $file = $path . $original; + $ioFile = new Varien_Io_File(); + if ($ioFile->fileExists($file)) { + $ioFile->rm($file); + } + } + + if (!empty($value['tmp_name'])) { + try { + $uploader = new Varien_File_Uploader($value); + $uploader->setFilesDispersion(true); + $uploader->setFilenamesCaseSensitivity(false); + $uploader->setAllowRenameFiles(true); + $uploader->save($path, $value['name']); + $fileName = $uploader->getUploadedFileName(); + $this->getEntity()->setData($attribute->getAttributeCode(), $fileName); + } catch (Exception $e) { + Mage::logException($e); + } + } + + return $this; + } + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_File + */ + public function restoreValue($value) + { + return $this; + } + + /** + * Return formated attribute value from entity model + * + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $output = ''; + $value = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + if ($value) { + switch ($format) { + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_JSON: + $output = array( + 'value' => $value, + 'url_key' => Mage::helper('core')->urlEncode($value) + ); + break; + } + } + + return $output; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Hidden.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Hidden.php new file mode 100644 index 00000000..50735437 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Hidden.php @@ -0,0 +1,37 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Hidden extends Mage_Eav_Model_Attribute_Data_Text +{ +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Image.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Image.php new file mode 100644 index 00000000..33e76690 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Image.php @@ -0,0 +1,98 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Image extends Mage_Eav_Model_Attribute_Data_File +{ + /** + * Validate file by attribute validate rules + * Return array of errors + * + * @param array $value + * @return array + */ + protected function _validateByRules($value) + { + $label = Mage::helper('eav')->__($this->getAttribute()->getStoreLabel()); + $rules = $this->getAttribute()->getValidateRules(); + + $imageProp = @getimagesize($value['tmp_name']); + + if (!is_uploaded_file($value['tmp_name']) || !$imageProp) { + return array( + Mage::helper('eav')->__('"%s" is not a valid file', $label) + ); + } + + $allowImageTypes = array( + 1 => 'gif', + 2 => 'jpg', + 3 => 'png', + ); + + if (!isset($allowImageTypes[$imageProp[2]])) { + return array( + Mage::helper('eav')->__('"%s" is not a valid image format', $label) + ); + } + + // modify image name + $extension = pathinfo($value['name'], PATHINFO_EXTENSION); + if ($extension != $allowImageTypes[$imageProp[2]]) { + $value['name'] = pathinfo($value['name'], PATHINFO_FILENAME) . '.' . $allowImageTypes[$imageProp[2]]; + } + + $errors = array(); + if (!empty($rules['max_file_size'])) { + $size = $value['size']; + if ($rules['max_file_size'] < $size) { + $errors[] = Mage::helper('eav')->__('"%s" exceeds the allowed file size.', $label); + }; + } + + if (!empty($rules['max_image_width'])) { + if ($rules['max_image_width'] < $imageProp[0]) { + $r = $rules['max_image_width']; + $errors[] = Mage::helper('eav')->__('"%s" width exceeds allowed value of %s px.', $label, $r); + }; + } + if (!empty($rules['max_image_heght'])) { + if ($rules['max_image_heght'] < $imageProp[1]) { + $r = $rules['max_image_heght']; + $errors[] = Mage::helper('eav')->__('"%s" height exceeds allowed value of %s px.', $label, $r); + }; + } + + return $errors; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Multiline.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Multiline.php new file mode 100644 index 00000000..b54f532c --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Multiline.php @@ -0,0 +1,157 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Multiline extends Mage_Eav_Model_Attribute_Data_Text +{ + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + $value = $this->_getRequestValue($request); + if (!is_array($value)) { + $value = false; + } else { + $value = array_map(array($this, '_applyInputFilter'), $value); + } + return $value; + } + + /** + * Validate data + * Return true or array of errors + * + * @param array|string $value + * @return boolean|array + */ + public function validateValue($value) + { + $errors = array(); + $attribute = $this->getAttribute(); + + if ($value === false) { + // try to load original value and validate it + $value = $this->getEntity()->getDataUsingMethod($attribute->getAttributeCode()); + if (!is_array($value)) { + $value = explode("\n", $value); + } + } + + if (!is_array($value)) { + $value = array($value); + } + for ($i = 0; $i < $attribute->getMultilineCount(); $i ++) { + if (!isset($value[$i])) { + $value[$i] = null; + } + // validate first line + if ($i == 0) { + $result = parent::validateValue($value[$i]); + if ($result !== true) { + $errors = $result; + } + } else { + if (!empty($value[$i])) { + $result = parent::validateValue($value[$i]); + if ($result !== true) { + $errors = array_merge($errors, $result); + } + } + } + } + + if (count($errors) == 0) { + return true; + } + return $errors; + } + + /** + * Export attribute value to entity model + * + * @param Mage_Core_Model_Abstract $entity + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Multiline + */ + public function compactValue($value) + { + if (is_array($value)) { + $value = trim(implode("\n", $value)); + } + return parent::compactValue($value); + } + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Multiline + */ + public function restoreValue($value) + { + return $this->compactValue($value); + } + + /** + * Return formated attribute value from entity model + * + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $values = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + if (!is_array($values)) { + $values = explode("\n", $values); + } + $values = array_map(array($this, '_applyOutputFilter'), $values); + switch ($format) { + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_ARRAY: + $output = $values; + break; + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_HTML: + $output = implode("
    ", $values); + break; + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_ONELINE: + $output = implode(" ", $values); + break; + default: + $output = implode("\n", $values); + break; + } + return $output; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Multiselect.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Multiselect.php new file mode 100644 index 00000000..25976497 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Multiselect.php @@ -0,0 +1,96 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Multiselect extends Mage_Eav_Model_Attribute_Data_Select +{ + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + $values = $this->_getRequestValue($request); + if ($values !== false && !is_array($values)) { + $values = array($values); + } + return $values; + } + + /** + * Export attribute value to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Multiselect + */ + public function compactValue($value) + { + if (is_array($value)) { + $value = implode(',', $value); + } + return parent::compactValue($value); + } + + /** + * Return formated attribute value from entity model + * + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $values = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + if (!is_array($values)) { + $values = explode(',', $values); + } + + switch ($format) { + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_JSON: + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_ARRAY: + $output = $values; + default: + $output = array(); + foreach ($values as $value) { + if (!$value) { + continue; + } + $output[] = $this->getAttribute()->getSource()->getOptionText($value); + } + $output = implode(', ', $output); + break; + } + + return $output; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Select.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Select.php new file mode 100644 index 00000000..7ad12b89 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Select.php @@ -0,0 +1,140 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Select extends Mage_Eav_Model_Attribute_Data_Abstract +{ + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + return $this->_getRequestValue($request); + } + + /** + * Validate data + * Return true or array of errors + * + * @param array|string $value + * @return boolean|array + */ + public function validateValue($value) + { + $errors = array(); + $attribute = $this->getAttribute(); + $label = Mage::helper('eav')->__($attribute->getStoreLabel()); + + if ($value === false) { + // try to load original value and validate it + $value = $this->getEntity()->getData($attribute->getAttributeCode()); + } + + if ($attribute->getIsRequired() && empty($value) && $value != '0') { + $errors[] = Mage::helper('eav')->__('"%s" is a required value.', $label); + } + + if (!$errors && !$attribute->getIsRequired() && empty($value)) { + return true; + } + + if (count($errors) == 0) { + return true; + } + + return $errors; + } + + /** + * Export attribute value to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Select + */ + public function compactValue($value) + { + if ($value !== false) { + $this->getEntity()->setData($this->getAttribute()->getAttributeCode(), $value); + } + return $this; + } + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Select + */ + public function restoreValue($value) + { + return $this->compactValue($value); + } + + /** + * Return a text for option value + * + * @param int $value + * @return string + */ + protected function _getOptionText($value) + { + return $this->getAttribute()->getSource()->getOptionText($value); + } + + /** + * Return formated attribute value from entity model + * + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $value = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + switch ($format) { + case Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_JSON: + $output = $value; + break; + default: + if ($value != '') { + $output = $this->_getOptionText($value); + } else { + $output = ''; + } + break; + } + + return $output; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Text.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Text.php new file mode 100644 index 00000000..60d631a5 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Text.php @@ -0,0 +1,137 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Text extends Mage_Eav_Model_Attribute_Data_Abstract +{ + /** + * Extract data from request and return value + * + * @param Zend_Controller_Request_Http $request + * @return array|string + */ + public function extractValue(Zend_Controller_Request_Http $request) + { + $value = $this->_getRequestValue($request); + return $this->_applyInputFilter($value); + } + + /** + * Validate data + * Return true or array of errors + * + * @param array|string $value + * @return boolean|array + */ + public function validateValue($value) + { + $errors = array(); + $attribute = $this->getAttribute(); + $label = Mage::helper('eav')->__($attribute->getStoreLabel()); + + if ($value === false) { + // try to load original value and validate it + $value = $this->getEntity()->getDataUsingMethod($attribute->getAttributeCode()); + } + + if ($attribute->getIsRequired() && empty($value)) { + $errors[] = Mage::helper('eav')->__('"%s" is a required value.', $label); + } + + if (!$errors && !$attribute->getIsRequired() && empty($value)) { + return true; + } + + // validate length + $length = Mage::helper('core/string')->strlen(trim($value)); + + $validateRules = $attribute->getValidateRules(); + if (!empty($validateRules['min_text_length']) && $length < $validateRules['min_text_length']) { + $v = $validateRules['min_text_length']; + $errors[] = Mage::helper('eav')->__('"%s" length must be equal or greater than %s characters.', $label, $v); + } + if (!empty($validateRules['max_text_length']) && $length > $validateRules['max_text_length']) { + $v = $validateRules['max_text_length']; + $errors[] = Mage::helper('eav')->__('"%s" length must be equal or less than %s characters.', $label, $v); + } + + $result = $this->_validateInputRule($value); + if ($result !== true) { + $errors = array_merge($errors, $result); + } + if (count($errors) == 0) { + return true; + } + + return $errors; + } + + /** + * Export attribute value to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Text + */ + public function compactValue($value) + { + if ($value !== false) { + $this->getEntity()->setDataUsingMethod($this->getAttribute()->getAttributeCode(), $value); + } + return $this; + } + + /** + * Restore attribute value from SESSION to entity model + * + * @param array|string $value + * @return Mage_Eav_Model_Attribute_Data_Text + */ + public function restoreValue($value) + { + return $this->compactValue($value); + } + + /** + * Return formated attribute value from entity model + * + * @param string $format + * @return string|array + */ + public function outputValue($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $value = $this->getEntity()->getData($this->getAttribute()->getAttributeCode()); + $value = $this->_applyOutputFilter($value); + + return $value; + } +} diff --git a/app/code/core/Mage/Eav/Model/Attribute/Data/Textarea.php b/app/code/core/Mage/Eav/Model/Attribute/Data/Textarea.php new file mode 100644 index 00000000..bcc02692 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Attribute/Data/Textarea.php @@ -0,0 +1,37 @@ + + */ +class Mage_Eav_Model_Attribute_Data_Textarea extends Mage_Eav_Model_Attribute_Data_Text +{ +} diff --git a/app/code/core/Mage/Eav/Model/Config.php b/app/code/core/Mage/Eav/Model/Config.php index ee224c99..80c1278c 100644 --- a/app/code/core/Mage/Eav/Model/Config.php +++ b/app/code/core/Mage/Eav/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -49,21 +49,21 @@ class Mage_Eav_Model_Config * * @var array */ - protected $_preloadedAttributes = array(); + protected $_preloadedAttributes = array(); /** * Information about entity types with initialized attributes * * @var array */ - protected $_initializedAttributes = array(); + protected $_initializedAttributes = array(); /** * Attribute codes cache array * * @var array */ - protected $_attributeCodes = array(); + protected $_attributeCodes = array(); /** * Initialized objects @@ -91,14 +91,14 @@ class Mage_Eav_Model_Config * * @var unknown_type */ - protected $_isCacheEnabled = null; + protected $_isCacheEnabled = null; /** * Array of attributes objects used in collections * * @var array */ - protected $_collectionAttributes = array(); + protected $_collectionAttributes = array(); /** * Reset object state @@ -108,17 +108,17 @@ class Mage_Eav_Model_Config */ public function clear() { - $this->_entityData = null; - $this->_attributeData = null; - $this->_objects = null; - $this->_references = null; - $this->_preloadedAttributes = array(); + $this->_entityData = null; + $this->_attributeData = null; + $this->_objects = null; + $this->_references = null; + $this->_preloadedAttributes = array(); $this->_initializedAttributes = array(); return $this; } /** - * Get object by idetifier + * Get object by identifier * * @param mixed $id * @return mixed @@ -202,7 +202,7 @@ protected function _getAttributeReference($id, $entityTypeCode) */ protected function _getEntityKey($code) { - return 'ENTITY/'.$code; + return 'ENTITY/' . $code; } /** @@ -214,7 +214,7 @@ protected function _getEntityKey($code) */ protected function _getAttributeKey($entityTypeCode, $attributeCode) { - return 'ATTRIBUTE/'. $entityTypeCode .'/' . $attributeCode; + return 'ATTRIBUTE/' . $entityTypeCode . '/' . $attributeCode; } /** @@ -258,7 +258,7 @@ protected function _initEntityTypes() } $entityTypesData = Mage::getModel('eav/entity_type')->getCollection()->getData(); - $types = array(); + $types = array(); /** * prepare entity type data @@ -298,18 +298,17 @@ public function getEntityType($code) return $code; } Varien_Profiler::start('EAV: '.__METHOD__); - //$this->_initEntityTypes(); if (is_numeric($code)) { $entityCode = $this->_getEntityTypeReference($code); if ($entityCode !== null) { $code = $entityCode; - //Mage::throwException(Mage::helper('eav')->__('Invalid entity_type specified: %s', $code)); } } $entityKey = $this->_getEntityKey($code); - if ($entityType = $this->_load($entityKey)) { + $entityType = $this->_load($entityKey); + if ($entityType) { Varien_Profiler::stop('EAV: '.__METHOD__); return $entityType; } @@ -318,8 +317,7 @@ public function getEntityType($code) $entityType = Mage::getModel('eav/entity_type'); if (isset($this->_entityData[$code])) { $entityType->setData($this->_entityData[$code]); - } - else { + } else { if (is_numeric($code)) { $entityType->load($code); } else { @@ -353,9 +351,8 @@ protected function _initAttributes($entityType) } Varien_Profiler::start('EAV: '.__METHOD__); - $attributesInfo = Mage::getResourceModel('eav/entity_attribute_collection') + $attributesInfo = Mage::getResourceModel($entityType->getEntityAttributeCollection()) ->setEntityTypeFilter($entityType) -// ->addSetInfo() ->getData(); $codes = array(); @@ -376,7 +373,7 @@ protected function _initAttributes($entityType) * * @param mixed $entityType * @param mixed $code - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Eav_Model_Entity_Attribute_Abstract|false */ public function getAttribute($entityType, $code) { @@ -403,23 +400,17 @@ public function getAttribute($entityType, $code) /** * Try use loaded attribute */ - if ($attribute = $this->_load($attributeKey)) { + $attribute = $this->_load($attributeKey); + if ($attribute) { Varien_Profiler::stop('EAV: '.__METHOD__); return $attribute; } -// if (!isset($this->_preloadedAttributes[$entityTypeCode]) -// || !in_array($code, $this->_preloadedAttributes[$entityTypeCode])) { -// $this->_initAttributes($entityType); -// } - - $attribute = false; if (isset($this->_attributeData[$entityTypeCode][$code])) { $data = $this->_attributeData[$entityTypeCode][$code]; unset($this->_attributeData[$entityTypeCode][$code]); $attribute = Mage::getModel($data['attribute_model'], $data); - } - else { + } else { if (is_numeric($code)) { $attribute = Mage::getModel($entityType->getAttributeModel())->load($code); if ($attribute->getEntityTypeId() != $entityType->getId()) { @@ -445,6 +436,7 @@ public function getAttribute($entityType, $code) $this->_save($attribute, $attributeKey); } Varien_Profiler::stop('EAV: '.__METHOD__); + return $attribute; } @@ -455,7 +447,7 @@ public function getAttribute($entityType, $code) * @param Varien_Object $object * @return array */ - public function getEntityAttributeCodes($entityType, $object=null) + public function getEntityAttributeCodes($entityType, $object = null) { $entityType = $this->getEntityType($entityType); $attributeSetId = 0; @@ -472,11 +464,9 @@ public function getEntityAttributeCodes($entityType, $object=null) } if ($attributeSetId) { - - $attributesInfo = Mage::getResourceModel('eav/entity_attribute_collection') + $attributesInfo = Mage::getResourceModel($entityType->getEntityAttributeCollection()) ->setEntityTypeFilter($entityType) ->setAttributeSetFilter($attributeSetId) -// ->addSetInfo() ->addStoreLabel($storeId) ->getData(); $attributes = array(); @@ -484,13 +474,13 @@ public function getEntityAttributeCodes($entityType, $object=null) $attributes[] = $attributeData['attribute_code']; $this->_createAttribute($entityType, $attributeData); } - } - else { + } else { $this->_initAttributes($entityType); $attributes = $this->getEntityType($entityType)->getAttributeCodes(); } $this->_attributeCodes[$cacheKey] = $attributes; + return $attributes; } @@ -512,10 +502,11 @@ public function preloadAttributes($entityType, $attributes) if (!isset($this->_preloadedAttributes[$entityTypeCode])) { $this->_preloadedAttributes[$entityTypeCode] = $attributes; - } - else { + } else { $attributes = array_diff($attributes, $this->_preloadedAttributes[$entityTypeCode]); - $this->_preloadedAttributes[$entityTypeCode] = array_merge($this->_preloadedAttributes[$entityTypeCode], $attributes); + $this->_preloadedAttributes[$entityTypeCode] = array_merge($this->_preloadedAttributes[$entityTypeCode], + $attributes + ); } if (empty($attributes)) { @@ -523,10 +514,9 @@ public function preloadAttributes($entityType, $attributes) } Varien_Profiler::start('EAV: '.__METHOD__ . ':'.$entityTypeCode); - $attributesInfo = Mage::getResourceModel('eav/entity_attribute_collection') + $attributesInfo = Mage::getResourceModel($entityType->getEntityAttributeCollection()) ->setEntityTypeFilter($entityType) ->setCodeFilter($attributes) -// ->addSetInfo() ->getData(); if (!$attributesInfo) { @@ -534,8 +524,7 @@ public function preloadAttributes($entityType, $attributes) return $this; } - $attributesData = array(); - $codes = array(); + $attributesData = $codes = array(); foreach ($attributesInfo as $attribute) { if (empty($attribute['attribute_model'])) { @@ -547,12 +536,13 @@ public function preloadAttributes($entityType, $attributes) $this->_addAttributeReference($attributeId, $attributeCode, $entityTypeCode); $attributesData[$attributeCode] = $attribute; - $codes[] = $attributeCode; + $codes[] = $attributeCode; } $this->_attributeData[$entityTypeCode] = $attributesData; Varien_Profiler::stop('EAV: '.__METHOD__ . ':'.$entityTypeCode); + return $this; } @@ -561,7 +551,7 @@ public function preloadAttributes($entityType, $attributes) * * @param mixed $entityType * @param string $attribute - * @return Mage_Eav_Model_Entity_Attribute_Abstract + * @return Mage_Eav_Model_Entity_Attribute_Abstract|null */ public function getCollectionAttribute($entityType, $attribute) { @@ -575,8 +565,9 @@ public function getCollectionAttribute($entityType, $attribute) } } - $attributeKey = $this->_getAttributeKey($entityTypeCode, $attribute); - if ($attributeObject = $this->_load($attributeKey)) { + $attributeKey = $this->_getAttributeKey($entityTypeCode, $attribute); + $attributeObject = $this->_load($attributeKey); + if ($attributeObject) { return $attributeObject; } @@ -592,7 +583,7 @@ public function getCollectionAttribute($entityType, $attribute) */ public function loadCollectionAttributes($entityType, $attributes) { - $entityType = $this->getEntityType($entityType); + $entityType = $this->getEntityType($entityType); $entityTypeCode = $entityType->getEntityTypeCode(); if (!isset($this->_collectionAttributes[$entityTypeCode])) { @@ -642,7 +633,8 @@ protected function _createAttribute($entityType, $attributeData) $entityTypeCode = $entityType->getEntityTypeCode(); $attributeKey = $this->_getAttributeKey($entityTypeCode, $attributeData['attribute_code']); - if (($attribute = $this->_load($attributeKey))) { + $attribute = $this->_load($attributeKey); + if ($attribute) { $existsFullAttribute = $attribute->hasIsRequired(); $fullAttributeData = array_key_exists('is_required', $attributeData); @@ -653,8 +645,7 @@ protected function _createAttribute($entityType, $attributeData) if (!empty($attributeData['attribute_model'])) { $model = $attributeData['attribute_model']; - } - else { + } else { $model = $entityType->getAttributeModel(); } $attribute = Mage::getModel($model)->setData($attributeData); @@ -665,6 +656,7 @@ protected function _createAttribute($entityType, $attributeData) ); $attributeKey = $this->_getAttributeKey($entityTypeCode, $attributeData['attribute_code']); $this->_save($attribute, $attributeKey); + return $attribute; } diff --git a/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php b/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php index 638b146a..15b2ea27 100644 --- a/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php +++ b/app/code/core/Mage/Eav/Model/Convert/Adapter/Entity.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -146,6 +146,12 @@ public function setFilter($attrFilterArray, $attrToDb = null, $bind = null, $joi 'like' => '%'.$val.'%' ); break; + case 'startsWith': + $attr = array( + 'attribute' => $keyDB, + 'like' => $val.'%' + ); + break; case 'fromTo': $attr = array( 'attribute' => $keyDB, @@ -212,12 +218,33 @@ public function setJoinAttr($joinAttr) } + /** + * Add join field + * + * @param array $joinField Variable should be have view: + * Example: + * array( + * 'alias' => 'alias_table', + * 'attribute' => 'table_name', //table name, must be used path of table like 'module/table_name' + * 'field' => 'field_name', //selected field name (optional) + * //bind main condition + * //left field use for joined table + * //and right field use for main table of collection + * //NOTE: around '=' cannot be used ' ' (space) because on the exploding not use space trimming + * 'bind' => 'self_item_id=other_id', + * 'cond' => 'alias_table.entity_id = e.entity_id', //additional condition (optional) + * 'joinType' => 'LEFT' + * ) + * NOTE: Optional key must be have NULL at least + * @return void + */ public function setJoinField($joinField) { if (is_array($joinField)) { $this->_joinField[] = $joinField; } } + public function load() { if (!($entityType = $this->getVar('entity_type')) diff --git a/app/code/core/Mage/Eav/Model/Convert/Adapter/Grid.php b/app/code/core/Mage/Eav/Model/Convert/Adapter/Grid.php index 81a4bd0b..01ec67c3 100644 --- a/app/code/core/Mage/Eav/Model/Convert/Adapter/Grid.php +++ b/app/code/core/Mage/Eav/Model/Convert/Adapter/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Convert/Parser/Abstract.php b/app/code/core/Mage/Eav/Model/Convert/Parser/Abstract.php index 2b9969b6..d9223182 100644 --- a/app/code/core/Mage/Eav/Model/Convert/Parser/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Convert/Parser/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity.php b/app/code/core/Mage/Eav/Model/Entity.php index e233d63f..093af2d7 100644 --- a/app/code/core/Mage/Eav/Model/Entity.php +++ b/app/code/core/Mage/Eav/Model/Entity.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,7 +33,6 @@ */ class Mage_Eav_Model_Entity extends Mage_Eav_Model_Entity_Abstract { - const DEFAULT_ENTITY_MODEL = 'eav/entity'; const DEFAULT_ATTRIBUTE_MODEL = 'eav/entity_attribute'; const DEFAULT_BACKEND_MODEL = 'eav/entity_attribute_backend_default'; @@ -45,12 +44,12 @@ class Mage_Eav_Model_Entity extends Mage_Eav_Model_Entity_Abstract const DEFAULT_VALUE_TABLE_PREFIX= 'eav/entity_attribute'; /** - * Enter description here... - * + * Resource initialization */ public function __construct() { - $this->setConnection(Mage::getSingleton('core/resource')->getConnection('core_read')); + $resource = Mage::getSingleton('core/resource'); + $this->setConnection($resource->getConnection('eav_read')); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Abstract.php index 7c5a6b30..5898217b 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,8 +32,7 @@ * @package Mage_Eav * @author Magento Core Team */ -abstract class Mage_Eav_Model_Entity_Abstract - extends Mage_Core_Model_Resource_Abstract +abstract class Mage_Eav_Model_Entity_Abstract extends Mage_Core_Model_Resource_Abstract implements Mage_Eav_Model_Entity_Interface { /** @@ -62,21 +61,21 @@ abstract class Mage_Eav_Model_Entity_Abstract * * @var array */ - protected $_attributesById = array(); + protected $_attributesById = array(); /** * Attributes array by attribute name * * @var unknown_type */ - protected $_attributesByCode = array(); + protected $_attributesByCode = array(); /** * 2-dimentional array by table name and attribute name * * @var array */ - protected $_attributesByTable = array(); + protected $_attributesByTable = array(); /** * Attributes that are static fields in entity table @@ -86,7 +85,14 @@ abstract class Mage_Eav_Model_Entity_Abstract protected $_staticAttributes = array(); /** - * Enter description here... + * Default Attributes that are static + * + * @var array + */ + protected static $_defaultAttributes = array(); + + /** + * Entity table * * @var string */ @@ -97,38 +103,44 @@ abstract class Mage_Eav_Model_Entity_Abstract * * @var array */ - protected $_describeTable = array(); + protected $_describeTable = array(); /** - * Enter description here... + * Entity table identification field name * * @var string */ protected $_entityIdField; /** - * Enter description here... + * Entity values table identification field name * * @var string */ protected $_valueEntityIdField; /** - * Enter description here... + * Entity value table prefix * * @var string */ protected $_valueTablePrefix; + /* Entity table string + * + * @var string + */ + protected $_entityTablePrefix; + /** - * Enter description here... + * Partial load flag * * @var boolean */ protected $_isPartialLoad = false; /** - * Enter description here... + * Partial save flag * * @var boolean */ @@ -155,6 +167,14 @@ abstract class Mage_Eav_Model_Entity_Abstract */ protected $_attributeValuesToSave = array(); + /** + * Array of describe attribute backend tables + * The table name as key + * + * @var array + */ + protected static $_attributeBackendTables = array(); + /** * Set connections for entity operations * @@ -162,10 +182,11 @@ abstract class Mage_Eav_Model_Entity_Abstract * @param Zend_Db_Adapter_Abstract|string|null $write * @return Mage_Eav_Model_Entity_Abstract */ - public function setConnection($read, $write=null) + public function setConnection($read, $write = null) { - $this->_read = $read; + $this->_read = $read; $this->_write = $write ? $write : $read; + return $this; } @@ -173,14 +194,12 @@ public function setConnection($read, $write=null) * Resource initialization */ protected function _construct() - { - - } + {} /** * Retrieve connection for read data * - * @return Varien_Db_Adapter_Pdo_Mysql + * @return Varien_Db_Adapter_Interface */ protected function _getReadAdapter() { @@ -193,7 +212,7 @@ protected function _getReadAdapter() /** * Retrieve connection for write data * - * @return Varien_Db_Adapter_Pdo_Mysql + * @return Varien_Db_Adapter_Interface */ protected function _getWriteAdapter() { @@ -206,7 +225,7 @@ protected function _getWriteAdapter() /** * Retrieve read DB connection * - * @return Varien_Db_Adapter_Pdo_Mysql + * @return Varien_Db_Adapter_Interface */ public function getReadConnection() { @@ -216,7 +235,7 @@ public function getReadConnection() /** * Retrieve write DB connection * - * @return Varien_Db_Adapter_Pdo_Mysql + * @return Varien_Db_Adapter_Interface */ public function getWriteConnection() { @@ -234,7 +253,7 @@ public function getIdFieldName() } /** - * Enter description here... + * Retreive table name * * @param string $alias * @return string @@ -267,7 +286,7 @@ public function setType($type) public function getEntityType() { if (empty($this->_type)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized')); } return $this->_type; } @@ -285,7 +304,7 @@ public function getType() /** * Get entity type id * - * @return integer + * @return int */ public function getTypeId() { @@ -301,12 +320,12 @@ public function getTypeId() * @param array|string|null $attributes * @return Mage_Eav_Model_Entity_Abstract */ - public function unsetAttributes($attributes=null) + public function unsetAttributes($attributes = null) { - if (is_null($attributes)) { - $this->_attributesByCode = array(); - $this->_attributesById = array(); - $this->_attributesByTable = array(); + if ($attributes === null) { + $this->_attributesByCode = array(); + $this->_attributesById = array(); + $this->_attributesByTable = array(); return $this; } @@ -315,7 +334,7 @@ public function unsetAttributes($attributes=null) } if (!is_array($attributes)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Unknown parameter.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Unknown parameter')); } foreach ($attributes as $attrCode) { @@ -355,7 +374,7 @@ public function getAttribute($attribute) $attributeCode = $attributeInstance->getAttributeCode(); } - } elseif (is_string($attribute)) { + } else if (is_string($attribute)) { $attributeCode = $attribute; if (isset($this->_attributesByCode[$attributeCode])) { @@ -372,7 +391,7 @@ public function getAttribute($attribute) ->setEntityType($this->getEntityType()) ->setEntityTypeId($this->getEntityType()->getId()); } - } elseif ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { + } else if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { $attributeInstance = $attribute; $attributeCode = $attributeInstance->getAttributeCode(); @@ -383,7 +402,8 @@ public function getAttribute($attribute) if (empty($attributeInstance) || !($attributeInstance instanceof Mage_Eav_Model_Entity_Attribute_Abstract) - || (!$attributeInstance->getId() && !in_array($attributeInstance->getAttributeCode(), $this->getDefaultAttributes())) + || (!$attributeInstance->getId() + && !in_array($attributeInstance->getAttributeCode(), $this->getDefaultAttributes())) ) { return false; } @@ -406,6 +426,28 @@ public function getAttribute($attribute) return $attribute; } + /** + * Return default static virtual attribute that doesn't exists in EAV attributes + * + * @param string $attributeCode + * @return Mage_Eav_Model_Entity_Attribute + */ + protected function _getDefaultAttribute($attributeCode) + { + $entityTypeId = $this->getEntityType()->getId(); + if (!isset(self::$_defaultAttributes[$entityTypeId][$attributeCode])) { + $attribute = Mage::getModel($this->getEntityType()->getAttributeModel()) + ->setAttributeCode($attributeCode) + ->setBackendType(Mage_Eav_Model_Entity_Attribute_Abstract::TYPE_STATIC) + ->setIsGlobal(1) + ->setEntityType($this->getEntityType()) + ->setEntityTypeId($this->getEntityType()->getId()); + self::$_defaultAttributes[$entityTypeId][$attributeCode] = $attribute; + } + + return self::$_defaultAttributes[$entityTypeId][$attributeCode]; + } + /** * Adding attribute to entity * @@ -425,35 +467,36 @@ public function addAttribute(Mage_Eav_Model_Entity_Attribute_Abstract $attribute $this->_attributesById[$attribute->getId()] = $attribute; $this->_attributesByTable[$attribute->getBackendTable()][$attributeCode] = $attribute; } + return $this; } /** - * Enter description here... + * Retreive partial load flag * * @param boolean $flag * @return boolean */ - public function isPartialLoad($flag=null) + public function isPartialLoad($flag = null) { $result = $this->_isPartialLoad; - if (!is_null($flag)) { - $this->_isPartialLoad = $flag; + if ($flag !== null) { + $this->_isPartialLoad = (bool)$flag; } return $result; } /** - * Enter description here... + * Retreive partial save flag * * @param boolean $flag * @return boolean */ - public function isPartialSave($flag=null) + public function isPartialSave($flag = null) { $result = $this->_isPartialSave; - if (!is_null($flag)) { - $this->_isPartialSave = $flag; + if ($flag !== null) { + $this->_isPartialSave = (bool)$flag; } return $result; } @@ -478,13 +521,7 @@ public function loadAllAttributes($object=null) $this->getAttribute($attributeCodes[$attributeIndex]); unset($attributeCodes[$attributeIndex]); } else { - $attribute = Mage::getModel($this->getEntityType()->getAttributeModel()); - $attribute->setAttributeCode($attributeCode) - ->setBackendType(Mage_Eav_Model_Entity_Attribute_Abstract::TYPE_STATIC) - ->setIsGlobal(1) - ->setEntityType($this->getEntityType()) - ->setEntityTypeId($this->getEntityType()->getId()); - $this->addAttribute($attribute); + $this->addAttribute($this->_getDefaultAttribute($attributeCode)); } } @@ -504,7 +541,7 @@ public function loadAllAttributes($object=null) public function getSortedAttributes($setId = null) { $attributes = $this->getAttributesByCode(); - if (is_null($setId)) { + if ($setId === null) { $setId = $this->getEntityType()->getDefaultAttributeSetId(); } @@ -524,10 +561,17 @@ public function getSortedAttributes($setId = null) return $attributes; } + /** + * Compare attributes + * + * @param Mage_Eav_Model_Entity_Attribute $attribute1 + * @param Mage_Eav_Model_Entity_Attribute $attribute2 + * @return int + */ public function attributesCompare($attribute1, $attribute2) { - $sortPath = 'attribute_set_info/' . $this->_sortingSetId . '/sort'; - $groupSortPath = 'attribute_set_info/' . $this->_sortingSetId . '/group_sort'; + $sortPath = sprintf('attribute_set_info/%s/sort', $this->_sortingSetId); + $groupSortPath = sprintf('attribute_set_info/%s/group_sort', $this->_sortingSetId); $sort1 = ($attribute1->getData($groupSortPath) * 1000) + ($attribute1->getData($sortPath) * 0.0001); $sort2 = ($attribute2->getData($groupSortPath) * 1000) + ($attribute2->getData($sortPath) * 0.0001); @@ -566,22 +610,22 @@ protected function _isApplicableAttribute($object, $attribute) * @param array $part attribute, backend, frontend, source * @return array */ - public function walkAttributes($partMethod, array $args=array()) + public function walkAttributes($partMethod, array $args = array()) { $methodArr = explode('/', $partMethod); switch (sizeof($methodArr)) { case 1: - $part = 'attribute'; + $part = 'attribute'; $method = $methodArr[0]; break; case 2: - $part = $methodArr[0]; + $part = $methodArr[0]; $method = $methodArr[1]; break; } $results = array(); - foreach ($this->getAttributesByCode() as $attrCode=>$attribute) { + foreach ($this->getAttributesByCode() as $attrCode => $attribute) { if (isset($args[0]) && is_object($args[0]) && !$this->_isApplicableAttribute($args[0], $attribute)) { continue; @@ -605,21 +649,41 @@ public function walkAttributes($partMethod, array $args=array()) break; } + if (!$this->_isCallableAttributeInstance($instance, $method, $args)) { + continue; + } + try { $results[$attrCode] = call_user_func_array(array($instance, $method), $args); - } - catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { + } catch (Mage_Eav_Model_Entity_Attribute_Exception $e) { + throw $e; + } catch (Exception $e) { + $e = Mage::getModel('eav/entity_attribute_exception', $e->getMessage()); + $e->setAttributeCode($attrCode)->setPart($part); throw $e; - } - catch (Exception $e) { - $exception = new Mage_Eav_Model_Entity_Attribute_Exception($e->getMessage()); - $exception->setAttributeCode($attrCode)->setPart($part); - throw $exception; } } + return $results; } + /** + * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract|Mage_Eav_Model_Entity_Attribute_Backend_Abstract|Mage_Eav_Model_Entity_Attribute_Frontend_Abstract|Mage_Eav_Model_Entity_Attribute_Source_Abstract $instance + * @param string $method + * @param array $args array of arguments + * @return boolean + */ + protected function _isCallableAttributeInstance($instance, $method, $args) + { + if (!is_object($instance) || !method_exists($instance, $method)) { + return false; + } + + return true; + } + /** * Get attributes by name array * @@ -657,13 +721,14 @@ public function getAttributesByTable() */ public function getEntityTable() { - if (empty($this->_entityTable)) { + if (!$this->_entityTable) { $table = $this->getEntityType()->getEntityTable(); - if (empty($table)) { + if (!$table) { $table = Mage_Eav_Model_Entity::DEFAULT_ENTITY_TABLE; } $this->_entityTable = Mage::getSingleton('core/resource')->getTableName($table); } + return $this->_entityTable; } @@ -674,12 +739,13 @@ public function getEntityTable() */ public function getEntityIdField() { - if (empty($this->_entityIdField)) { + if (!$this->_entityIdField) { $this->_entityIdField = $this->getEntityType()->getEntityIdField(); - if (empty($this->_entityIdField)) { + if (!$this->_entityIdField) { $this->_entityIdField = Mage_Eav_Model_Entity::DEFAULT_ENTITY_ID_FIELD; } } + return $this->_entityIdField; } @@ -700,7 +766,7 @@ public function getValueEntityIdField() */ public function getValueTablePrefix() { - if (empty($this->_valueTablePrefix)) { + if (!$this->_valueTablePrefix) { $prefix = (string)$this->getEntityType()->getValueTablePrefix(); if (!empty($prefix)) { $this->_valueTablePrefix = $prefix; @@ -712,20 +778,43 @@ public function getValueTablePrefix() $this->_valueTablePrefix = $this->getEntityTable(); } } + return $this->_valueTablePrefix; } + /** + * Get entity table prefix for value + * + * @return string + */ + public function getEntityTablePrefix() + { + if (empty($this->_entityTablePrefix)) { + $prefix = $this->getEntityType()->getEntityTablePrefix(); + if (empty($prefix)) { + $prefix = $this->getEntityType()->getEntityTable(); + if (empty($prefix)) { + $prefix = Mage_Eav_Model_Entity::DEFAULT_ENTITY_TABLE; + } + } + $this->_entityTablePrefix = $prefix; + } + + return $this->_entityTablePrefix; + } + /** * Check whether the attribute is a real field in entity table * * @see Mage_Eav_Model_Entity_Abstract::getAttribute for $attribute format * @param integer|string|Mage_Eav_Model_Entity_Attribute_Abstract $attribute - * @return unknown + * @return boolean */ public function isAttributeStatic($attribute) { - $attrInstance = $this->getAttribute($attribute); - return $attrInstance && $attrInstance->getBackend()->isStatic(); + $attrInstance = $this->getAttribute($attribute); + $attrBackendStatic = $attrInstance->getBackend()->isStatic(); + return $attrInstance && $attrBackendStatic; } /** @@ -743,8 +832,7 @@ public function validate($object) foreach ($result as $attributeCode => $error) { if ($error === false) { $errors[$attributeCode] = true; - } - elseif (is_string($error)) { + } elseif (is_string($error)) { $errors[$attributeCode] = $error; } } @@ -756,7 +844,7 @@ public function validate($object) } /** - * Enter description here... + * Set new increment id to object * * @param Varien_Object $object * @return Mage_Eav_Model_Entity_Abstract @@ -769,7 +857,7 @@ public function setNewIncrementId(Varien_Object $object) $incrementId = $this->getEntityType()->fetchNewIncrementId($object->getStoreId()); - if (false!==$incrementId) { + if ($incrementId !== false) { $object->setIncrementId($incrementId); } @@ -777,7 +865,7 @@ public function setNewIncrementId(Varien_Object $object) } /** - * Enter description here... + * Check attribute unique value * * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param Varien_Object $object @@ -785,25 +873,37 @@ public function setNewIncrementId(Varien_Object $object) */ public function checkAttributeUniqueValue(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $object) { - if ($attribute->getBackend()->getType()==='static') { - $select = $this->_getWriteAdapter()->select() + $adapter = $this->_getReadAdapter(); + $select = $adapter->select(); + if ($attribute->getBackend()->getType() === 'static') { + $value = $object->getData($attribute->getAttributeCode()); + $bind = array( + 'entity_type_id' => $this->getTypeId(), + 'attribute_code' => trim($value) + ); + + $select ->from($this->getEntityTable(), $this->getEntityIdField()) - ->where('entity_type_id=?', $this->getTypeId()) - ->where($attribute->getAttributeCode().'=?', $object->getData($attribute->getAttributeCode())); + ->where('entity_type_id = :entity_type_id') + ->where($attribute->getAttributeCode() . ' = :attribute_code'); } else { $value = $object->getData($attribute->getAttributeCode()); - if ($attribute->getBackend()->getType() == 'datetime'){ - $date = new Zend_Date($value); + if ($attribute->getBackend()->getType() == 'datetime') { + $date = new Zend_Date($value, Varien_Date::DATE_INTERNAL_FORMAT); $value = $date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); } - - $select = $this->_getWriteAdapter()->select() + $bind = array( + 'entity_type_id' => $this->getTypeId(), + 'attribute_id' => $attribute->getId(), + 'value' => trim($value) + ); + $select ->from($attribute->getBackend()->getTable(), $attribute->getBackend()->getEntityIdField()) - ->where('entity_type_id=?', $this->getTypeId()) - ->where('attribute_id=?', $attribute->getId()) - ->where('value=?', $value); + ->where('entity_type_id = :entity_type_id') + ->where('attribute_id = :attribute_id') + ->where('value = :value'); } - $data = $this->_getWriteAdapter()->fetchCol($select); + $data = $adapter->fetchCol($select, $bind); if ($object->getId()) { if (isset($data[0])) { @@ -811,13 +911,12 @@ public function checkAttributeUniqueValue(Mage_Eav_Model_Entity_Attribute_Abstra } return true; } - else { - return !count($data); - } + + return !count($data); } /** - * Enter description here... + * Retreive default source model * * @return string */ @@ -829,22 +928,24 @@ public function getDefaultAttributeSourceModel() /** * Load entity's attributes into the object * - * @param Varien_Object $object + * @param Mage_Core_Model_Abstract $object * @param integer $entityId * @param array|null $attributes * @return Mage_Eav_Model_Entity_Abstract */ - public function load($object, $entityId, $attributes=array()) + public function load($object, $entityId, $attributes = array()) { Varien_Profiler::start('__EAV_LOAD_MODEL__'); /** * Load object base row data */ - $select = $this->_getLoadRowSelect($object, $entityId); - $row = $this->_getReadAdapter()->fetchRow($select); - //$object->setData($row); + $select = $this->_getLoadRowSelect($object, $entityId); + $row = $this->_getReadAdapter()->fetchRow($select); + if (is_array($row)) { $object->addData($row); + } else { + $object->isObjectNew(true); } if (empty($attributes)) { @@ -855,32 +956,66 @@ public function load($object, $entityId, $attributes=array()) } } - /** - * Load data for entity attributes - */ + $this->_loadModelAttributes($object); + + $object->setOrigData(); + Varien_Profiler::start('__EAV_LOAD_MODEL_AFTER_LOAD__'); + + $this->_afterLoad($object); + Varien_Profiler::stop('__EAV_LOAD_MODEL_AFTER_LOAD__'); + + Varien_Profiler::stop('__EAV_LOAD_MODEL__'); + return $this; + } + + /** + * Load model attributes data + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Entity_Abstract + */ + protected function _loadModelAttributes($object) + { + if (!$object->getId()) { + return $this; + } + Varien_Profiler::start('__EAV_LOAD_MODEL_ATTRIBUTES__'); + $selects = array(); - foreach ($this->getAttributesByTable() as $table=>$attributes) { - $selects[] = $this->_getLoadAttributesSelect($object, $table); + foreach (array_keys($this->getAttributesByTable()) as $table) { + $attribute = current($this->_attributesByTable[$table]); + $eavType = $attribute->getBackendType(); + $select = $this->_getLoadAttributesSelect($object, $table); + $selects[$eavType][] = $this->_addLoadAttributesSelectFields($select, $table, $eavType); } - if (!empty($selects)) { - $values = $this->_getReadAdapter()->fetchAll(implode(' UNION ', $selects)); - foreach ($values as $valueRow) { - $this->_setAttribteValue($object, $valueRow); + $selectGroups = Mage::getResourceHelper('eav')->getLoadAttributesSelectGroups($selects); + foreach ($selectGroups as $selects) { + if (!empty($selects)) { + $select = $this->_prepareLoadSelect($selects); + $values = $this->_getReadAdapter()->fetchAll($select); + foreach ($values as $valueRow) { + $this->_setAttributeValue($object, $valueRow); + } } } Varien_Profiler::stop('__EAV_LOAD_MODEL_ATTRIBUTES__'); - $object->setOrigData(); - Varien_Profiler::start('__EAV_LOAD_MODEL_AFTER_LOAD__'); - $this->_afterLoad($object); - Varien_Profiler::stop('__EAV_LOAD_MODEL_AFTER_LOAD__'); - - Varien_Profiler::stop('__EAV_LOAD_MODEL__'); return $this; } + /** + * Prepare select object for loading entity attributes values + * + * @param array $selects + * @return Zend_Db_Select + */ + protected function _prepareLoadSelect(array $selects) + { + return $this->_getReadAdapter()->select()->union($selects, Zend_Db_Select::SQL_UNION_ALL); + } + /** * Retrieve select object for loading base entity row * @@ -892,7 +1027,7 @@ protected function _getLoadRowSelect($object, $rowId) { $select = $this->_getReadAdapter()->select() ->from($this->getEntityTable()) - ->where($this->getEntityIdField()."=?", $rowId); + ->where($this->getEntityIdField() . ' =?', $rowId); return $select; } @@ -907,25 +1042,58 @@ protected function _getLoadRowSelect($object, $rowId) protected function _getLoadAttributesSelect($object, $table) { $select = $this->_getReadAdapter()->select() - ->from($table) - ->where($this->getEntityIdField() . '=?', $object->getId()); + ->from($table, array()) + ->where($this->getEntityIdField() . ' =?', $object->getId()); + + return $select; + } + + /** + * Adds Columns prepared for union + * + * @param Varien_Db_Select $select + * @param string $table + * @param string $type + * @return Varien_Db_Select + */ + protected function _addLoadAttributesSelectFields($select, $table, $type) + { + $select->columns( + Mage::getResourceHelper('eav')->attributeSelectFields($table, $type) + ); return $select; } /** * Initialize attribute value for object * + * @deprecated after 1.5.1.0 - mistake in method name + * * @param Varien_Object $object * @param array $valueRow * @return Mage_Eav_Model_Entity_Abstract */ protected function _setAttribteValue($object, $valueRow) { - if ($attribute = $this->getAttribute($valueRow['attribute_id'])) { + return _setAttributeValue($object, $valueRow); + } + + /** + * Initialize attribute value for object + * + * @param Varien_Object $object + * @param array $valueRow + * @return Mage_Eav_Model_Entity_Abstract + */ + protected function _setAttributeValue($object, $valueRow) + { + $attribute = $this->getAttribute($valueRow['attribute_id']); + if ($attribute) { $attributeCode = $attribute->getAttributeCode(); $object->setData($attributeCode, $valueRow['value']); - $attribute->getBackend()->setValueId($valueRow['value_id']); + $attribute->getBackend()->setEntityValueId($object, $valueRow['value_id']); } + return $this; } @@ -970,6 +1138,7 @@ protected function _getOrigObject($object) $origObject = new $className(); $origObject->setData(array()); $this->load($origObject, $object->getData($this->getEntityIdField())); + return $origObject; } @@ -1053,15 +1222,15 @@ protected function _collectSaveData($newObject) /** * Check comparability for attribute value */ - if (array_key_exists($k, $origData)) { + if ($this->_canUpdateAttribute($attribute, $v, $origData)) { if ($this->_isAttributeValueEmpty($attribute, $v)) { $delete[$attribute->getBackend()->getTable()][] = array( 'attribute_id' => $attrId, - 'value_id' => $attribute->getBackend()->getValueId() + 'value_id' => $attribute->getBackend()->getEntityValueId($newObject) ); - } else if ($v !== $origData[$k]) { + } elseif ($v !== $origData[$k]) { $update[$attrId] = array( - 'value_id' => $attribute->getBackend()->getValueId(), + 'value_id' => $attribute->getBackend()->getEntityValueId($newObject), 'value' => $v, ); } @@ -1074,6 +1243,19 @@ protected function _collectSaveData($newObject) return $result; } + /** + * Return if attribute exists in original data array. + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param mixed $value New value of the attribute. Can be used in subclasses. + * @param array $origData + * @return bool + */ + protected function _canUpdateAttribute(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $v, array &$origData) + { + return array_key_exists($attribute->getAttributeCode(), $origData); + } + /** * Retrieve static field properties * @@ -1083,7 +1265,8 @@ protected function _collectSaveData($newObject) protected function _getStaticFieldProperties($field) { if (empty($this->_describeTable[$this->getEntityTable()])) { - $this->_describeTable[$this->getEntityTable()] = $this->_getWriteAdapter()->describeTable($this->getEntityTable()); + $this->_describeTable[$this->getEntityTable()] = $this->_getWriteAdapter() + ->describeTable($this->getEntityTable()); } if (isset($this->_describeTable[$this->getEntityTable()][$field])) { @@ -1124,37 +1307,62 @@ protected function _prepareStaticValue($key, $value) protected function _processSaveData($saveData) { extract($saveData); + /** + * Import variables into the current symbol table from save data array + * + * @see Mage_Eav_Model_Entity_Attribute_Abstract::_collectSaveData() + * + * @var array $entityRow + * @var Mage_Core_Model_Abstract $newObject + * @var array $insert + * @var array $update + * @var array $delete + */ + $adapter = $this->_getWriteAdapter(); $insertEntity = true; + $entityTable = $this->getEntityTable(); $entityIdField = $this->getEntityIdField(); $entityId = $newObject->getId(); - $condition = $this->_getWriteAdapter()->quoteInto("$entityIdField=?", $entityId); - if (!empty($entityId)) { - $select = $this->_getWriteAdapter()->select() - ->from($this->getEntityTable(), $entityIdField) - ->where($condition); - if ($this->_getWriteAdapter()->fetchOne($select)) { + unset($entityRow[$entityIdField]); + if (!empty($entityId) && is_numeric($entityId)) { + $bind = array('entity_id' => $entityId); + $select = $adapter->select() + ->from($entityTable, $entityIdField) + ->where("{$entityIdField} = :entity_id"); + $result = $adapter->fetchOne($select, $bind); + if ($result) { $insertEntity = false; } + } else { + $entityId = null; } /** * Process base row */ + $entityObject = new Varien_Object($entityRow); + $entityRow = $this->_prepareDataForTable($entityObject, $entityTable); if ($insertEntity) { - $this->_getWriteAdapter()->insert($this->getEntityTable(), $entityRow); - $entityId = $this->_getWriteAdapter()->lastInsertId(); + if (!empty($entityId)) { + $entityRow[$entityIdField] = $entityId; + $adapter->insertForce($entityTable, $entityRow); + } else { + $adapter->insert($entityTable, $entityRow); + $entityId = $adapter->lastInsertId($entityTable); + } $newObject->setId($entityId); } else { - $this->_getWriteAdapter()->update($this->getEntityTable(), $entityRow, $condition); + $where = sprintf('%s=%d', $adapter->quoteIdentifier($entityIdField), $entityId); + $adapter->update($entityTable, $entityRow, $where); } /** * insert attribute values */ if (!empty($insert)) { - foreach ($insert as $attrId => $value) { - $attribute = $this->getAttribute($attrId); + foreach ($insert as $attributeId => $value) { + $attribute = $this->getAttribute($attributeId); $this->_insertAttribute($newObject, $attribute, $value); } } @@ -1163,8 +1371,8 @@ protected function _processSaveData($saveData) * update attribute values */ if (!empty($update)) { - foreach ($update as $attrId => $v) { - $attribute = $this->getAttribute($attrId); + foreach ($update as $attributeId => $v) { + $attribute = $this->getAttribute($attributeId); $this->_updateAttribute($newObject, $attribute, $v['value_id'], $v['value']); } } @@ -1180,6 +1388,8 @@ protected function _processSaveData($saveData) $this->_processAttributeValues(); + $newObject->isObjectNew(false); + return $this; } @@ -1194,15 +1404,6 @@ protected function _processSaveData($saveData) protected function _insertAttribute($object, $attribute, $value) { return $this->_saveAttribute($object, $attribute, $value); - -// $row = array( -// $entityIdField => $object->getId(), -// 'entity_type_id'=> $object->getEntityTypeId(), -// 'attribute_id' => $attribute->getId(), -// 'value' => $this->_prepareValueForSave($value, $attribute), -// ); -// $this->_getWriteAdapter()->insert($attribute->getBackend()->getTable(), $row); -// return $this; } /** @@ -1217,11 +1418,6 @@ protected function _insertAttribute($object, $attribute, $value) protected function _updateAttribute($object, $attribute, $valueId, $value) { return $this->_saveAttribute($object, $attribute, $value); -// $this->_getWriteAdapter()->update($attribute->getBackend()->getTable(), -// array('value' => $this->_prepareValueForSave($value, $attribute)), -// 'value_id='.(int)$valueId -// ); -// return $this; } /** @@ -1268,7 +1464,7 @@ protected function _processAttributeValues() } foreach ($this->_attributeValuesToDelete as $table => $valueIds) { - $adapter->delete($table, array('value_id IN(?)' => $valueIds)); + $adapter->delete($table, array('value_id IN (?)' => $valueIds)); } // reset data arrays @@ -1290,7 +1486,13 @@ protected function _prepareValueForSave($value, Mage_Eav_Model_Entity_Attribute_ if ($attribute->getBackendType() == 'decimal') { return Mage::app()->getLocale()->getNumber($value); } - return $value; + + $backendTable = $attribute->getBackendTable(); + if (!isset(self::$_attributeBackendTables[$backendTable])) { + self::$_attributeBackendTables[$backendTable] = $this->_getReadAdapter()->describeTable($backendTable); + } + $describe = self::$_attributeBackendTables[$backendTable]; + return $this->_getReadAdapter()->prepareColumnValue($describe['value'], $value); } /** @@ -1319,12 +1521,6 @@ protected function _deleteAttributes($object, $table, $info) } return $this; - -// if (!empty($valueIds)) { -// $condition = $this->_getWriteAdapter()->quoteInto('value_id IN (?)', $valueIds); -// $this->_getWriteAdapter()->delete($table, $condition); -// } -// return $this; } /** @@ -1336,16 +1532,17 @@ protected function _deleteAttributes($object, $table, $info) */ public function saveAttribute(Varien_Object $object, $attributeCode) { - $attribute = $this->getAttribute($attributeCode); - $backend = $attribute->getBackend(); - $table = $backend->getTable(); - $entity = $attribute->getEntity(); - $entityIdField = $entity->getEntityIdField(); + $attribute = $this->getAttribute($attributeCode); + $backend = $attribute->getBackend(); + $table = $backend->getTable(); + $entity = $attribute->getEntity(); + $entityIdField = $entity->getEntityIdField(); + $adapter = $this->_getWriteAdapter(); $row = array( 'entity_type_id' => $entity->getTypeId(), - 'attribute_id' => $attribute->getId(), - $entityIdField=> $object->getData($entityIdField), + 'attribute_id' => $attribute->getId(), + $entityIdField => $object->getData($entityIdField), ); $newValue = $object->getData($attributeCode); @@ -1355,34 +1552,32 @@ public function saveAttribute(Varien_Object $object, $attributeCode) $whereArr = array(); foreach ($row as $field => $value) { - $whereArr[] = $this->_getReadAdapter()->quoteInto("$field=?", $value); + $whereArr[] = $adapter->quoteInto($field . '=?', $value); } - $where = '('.join(') AND (', $whereArr).')'; + $where = implode(' AND ', $whereArr); - $this->_getWriteAdapter()->beginTransaction(); + $adapter->beginTransaction(); try { - $select = $this->_getWriteAdapter()->select() + $select = $adapter->select() ->from($table, 'value_id') ->where($where); - $origValueId = $this->_getWriteAdapter()->fetchOne($select); + $origValueId = $adapter->fetchOne($select); - if ($origValueId === false && !is_null($newValue)) { + if ($origValueId === false && ($newValue !== null)) { $this->_insertAttribute($object, $attribute, $newValue); - $backend->setValueId($this->_getWriteAdapter()->lastInsertId()); - } elseif ($origValueId !== false && !is_null($newValue)) { + } elseif ($origValueId !== false && ($newValue !== null)) { $this->_updateAttribute($object, $attribute, $origValueId, $newValue); - } elseif ($origValueId !== false && is_null($newValue)) { - $this->_getWriteAdapter()->delete($table, $where); + } elseif ($origValueId !== false && ($newValue === null)) { + $adapter->delete($table, $where); } - $this->_getWriteAdapter()->commit(); + $this->_processAttributeValues(); + $adapter->commit(); } catch (Exception $e) { - $this->_getWriteAdapter()->rollback(); + $adapter->rollback(); throw $e; } - $this->_processAttributeValues(); - return $this; } @@ -1402,10 +1597,13 @@ public function delete($object) $this->_beforeDelete($object); try { - $this->_getWriteAdapter()->delete($this->getEntityTable(), $this->getEntityIdField()."=".$id); + $where = array( + $this->getEntityIdField() . '=?' => $id + ); + $this->_getWriteAdapter()->delete($this->getEntityTable(), $where); $this->loadAllAttributes($object); - foreach ($this->getAttributesByTable() as $table=>$attributes) { - $this->_getWriteAdapter()->delete($table, $this->getEntityIdField()."=".$id); + foreach ($this->getAttributesByTable() as $table => $attributes) { + $this->_getWriteAdapter()->delete($table, $where); } } catch (Exception $e) { throw $e; @@ -1419,50 +1617,60 @@ public function delete($object) * After Load Entity process * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Abstract */ protected function _afterLoad(Varien_Object $object) { $this->walkAttributes('backend/afterLoad', array($object)); + return $this; } /** * Before delete Entity process * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Abstract */ protected function _beforeSave(Varien_Object $object) { $this->walkAttributes('backend/beforeSave', array($object)); + return $this; } /** * After Save Entity process * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Abstract */ protected function _afterSave(Varien_Object $object) { $this->walkAttributes('backend/afterSave', array($object)); + return $this; } /** * Before Delete Entity process * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Abstract */ protected function _beforeDelete(Varien_Object $object) { $this->walkAttributes('backend/beforeDelete', array($object)); + return $this; } /** * After delete entity process * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Abstract */ protected function _afterDelete(Varien_Object $object) { $this->walkAttributes('backend/afterDelete', array($object)); + return $this; } /** @@ -1503,24 +1711,6 @@ public function getDefaultAttributes() { protected function _afterSetConfig() { return $this; -// Varien_Profiler::start(__METHOD__); -// -// $defaultAttributes = $this->_getDefaultAttributes(); -// $defaultAttributes[] = $this->getEntityIdField(); -// -// $attributes = $this->getAttributesByCode(); -// foreach ($defaultAttributes as $attr) { -// if (empty($attributes[$attr]) && !$this->getAttribute($attr)) { -// $attribute = Mage::getModel($this->getEntityType()->getAttributeModel()); -// $attribute->setAttributeCode($attr) -// ->setBackendType('static') -// ->setEntityType($this->getEntityType()) -// ->setEntityTypeId($this->getEntityType()->getId()); -// $this->addAttribute($attribute); -// } -// } -// Varien_Profiler::stop(__METHOD__); -// return $this; } /** diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Entity/Attribute.php index cbea42e9..32d2c827 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -39,7 +39,9 @@ class Mage_Eav_Model_Entity_Attribute extends Mage_Eav_Model_Entity_Attribute_Ab * * @var string */ - protected $_eventPrefix = 'eav_entity_attribute'; + protected $_eventPrefix = 'eav_entity_attribute'; + + CONST ATTRIBUTE_CODE_MAX_LENGTH = 30; /** * Parameter name in event @@ -53,6 +55,11 @@ class Mage_Eav_Model_Entity_Attribute extends Mage_Eav_Model_Entity_Attribute_Ab const CACHE_TAG = 'EAV_ATTRIBUTE'; protected $_cacheTag = 'EAV_ATTRIBUTE'; + /** + * Retreive default attribute backend model by attribute code + * + * @return string + */ protected function _getDefaultBackendModel() { switch ($this->getAttributeCode()) { @@ -69,36 +76,105 @@ protected function _getDefaultBackendModel() return 'eav/entity_attribute_backend_increment'; } - - return parent::_getDefaultBackendModel(); } + /** + * Retreive default attribute frontend model + * + * @return string + */ protected function _getDefaultFrontendModel() { return parent::_getDefaultFrontendModel(); } + /** + * Retreive default attribute source model + * + * @return string + */ protected function _getDefaultSourceModel() { - switch ($this->getAttributeCode()) { - case 'store_id': - return 'eav/entity_attribute_source_store'; + if ($this->getAttributeCode() == 'store_id') { + return 'eav/entity_attribute_source_store'; } return parent::_getDefaultSourceModel(); } + /** + * Delete entity + * + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ public function deleteEntity() { return $this->_getResource()->deleteEntity($this); } + /** + * Load entity_attribute_id into $this by $this->attribute_set_id + * + * @return Mage_Core_Model_Abstract + */ + public function loadEntityAttributeIdBySet() + { + // load attributes collection filtered by attribute_id and attribute_set_id + $filteredAttributes = $this->getResourceCollection() + ->setAttributeSetFilter($this->getAttributeSetId()) + ->addFieldToFilter('entity_attribute.attribute_id', $this->getId()) + ->load(); + if (count($filteredAttributes) > 0) { + // getFirstItem() can be used as we can have one or zero records in the collection + $this->setEntityAttributeId($filteredAttributes->getFirstItem()->getEntityAttributeId()); + } + return $this; + } + + /** + * Prepare data for save + * + * @return Mage_Eav_Model_Entity_Attribute + */ protected function _beforeSave() { // prevent overriding product data if (isset($this->_data['attribute_code']) - && Mage::getModel('catalog/product')->isReservedAttribute($this)) { - Mage::throwException(Mage::helper('eav')->__('The attribute code \'%s\' is reserved by system. Please try another attribute code.', $this->_data['attribute_code'])); + && Mage::getModel('catalog/product')->isReservedAttribute($this)) + { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('The attribute code \'%s\' is reserved by system. Please try another attribute code', $this->_data['attribute_code'])); + } + + /** + * Check for maximum attribute_code length + */ + if (isset($this->_data['attribute_code']) && + !Zend_Validate::is( + $this->_data['attribute_code'], + 'StringLength', + array('max' => self::ATTRIBUTE_CODE_MAX_LENGTH) + ) + ) { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Maximum length of attribute code must be less then %s symbols', self::ATTRIBUTE_CODE_MAX_LENGTH)); + } + + $defaultValue = $this->getDefaultValue(); + $hasDefaultValue = ((string)$defaultValue != ''); + + if ($this->getBackendType() == 'decimal' && $hasDefaultValue) { + $locale = Mage::app()->getLocale()->getLocaleCode(); + if (!Zend_Locale_Format::isNumber($defaultValue, array('locale' => $locale))) { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid default decimal value')); + } + + try { + $filter = new Zend_Filter_LocalizedToNormalized( + array('locale' => Mage::app()->getLocale()->getLocaleCode()) + ); + $this->setDefaultValue($filter->filter($defaultValue)); + } catch (Exception $e) { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid default decimal value')); + } } if ($this->getBackendType() == 'datetime') { @@ -111,13 +187,13 @@ protected function _beforeSave() } // save default date value as timestamp - if ($defaultValue = $this->getDefaultValue()) { + if ($hasDefaultValue) { $format = Mage::app()->getLocale()->getDateFormat(Mage_Core_Model_Locale::FORMAT_TYPE_SHORT); try { $defaultValue = Mage::app()->getLocale()->date($defaultValue, $format, null, false)->toValue(); $this->setDefaultValue($defaultValue); } catch (Exception $e) { - throw new Exception('Invalid default date.'); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid default date')); } } } @@ -131,21 +207,17 @@ protected function _beforeSave() return parent::_beforeSave(); } + /** + * Save additional data + * + * @return Mage_Eav_Model_Entity_Attribute + */ protected function _afterSave() { $this->_getResource()->saveInSetIncluding($this); - return parent::_afterSave(); } - protected function _beforeDelete() - { - if ($this->_getResource()->isUsedBySuperProducts($this)) { - Mage::throwException(Mage::helper('eav')->__('This attribute is used in configurable products.')); - } - return parent::_beforeDelete(); - } - /** * Detect backend storage type using frontend input type * @@ -154,36 +226,35 @@ protected function _beforeDelete() */ public function getBackendTypeByInput($type) { + $field = null; switch ($type) { case 'text': case 'gallery': case 'media_image': case 'multiselect': - return 'varchar'; + $field = 'varchar'; + break; case 'image': case 'textarea': - return 'text'; + $field = 'text'; + break; case 'date': - return 'datetime'; + $field = 'datetime'; + break; case 'select': case 'boolean': - return 'int'; - + $field = 'int'; + break; case 'price': - return 'decimal'; -/* - default: - Mage::dispatchEvent('eav_attribute_get_backend_type_by_input', array('model'=>$this, 'type'=>$type)); - if ($this->hasBackendTypeByInput()) { - return $this->getData('backend_type_by_input'); - } - Mage::throwException('Unknown frontend input type'); -*/ + $field = 'decimal'; + break; } + + return $field; } /** @@ -199,8 +270,10 @@ public function getDefaultValueByInput($type) case 'select': case 'gallery': case 'media_image': + break; case 'multiselect': - return ''; + $field = null; + break; case 'text': case 'price': @@ -219,18 +292,17 @@ public function getDefaultValueByInput($type) case 'boolean': $field = 'default_value_yesno'; break; -/* - default: - Mage::dispatchEvent('eav_attribute_get_default_value_by_input', array('model'=>$this, 'type'=>$type)); - if ($this->hasBackendTypeByInput()) { - return $this->getData('backend_type_by_input'); - } - Mage::throwException('Unknown frontend input type'); -*/ } return $field; } + + /** + * Retreive attribute codes by frontend type + * + * @param string $type + * @return array + */ public function getAttributeCodesByFrontendType($type) { return $this->getResource()->getAttributeCodesByFrontendType($type); @@ -244,7 +316,8 @@ public function getAttributeCodesByFrontendType($type) public function getStoreLabels() { if (!$this->getData('store_labels')) { - $this->setData('store_labels', $this->getResource()->getStoreLabelsByAttributeId($this->getId())); + $storeLabel = $this->getResource()->getStoreLabelsByAttributeId($this->getId()); + $this->setData('store_labels', $storeLabel); } return $this->getData('store_labels'); } @@ -254,8 +327,19 @@ public function getStoreLabels() * * @return string */ - public function getStoreLabel() + public function getStoreLabel($storeId = null) { - return $this->getData('store_label'); + if ($this->hasData('store_label')) { + return $this->getData('store_label'); + } + $store = Mage::app()->getStore($storeId); + $label = false; + if (!$store->isAdmin()) { + $labels = $this->getStoreLabels(); + if (isset($labels[$store->getId()])) { + return $labels[$store->getId()]; + } + } + return $this->getFrontendLabel(); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php index 7bf1188d..15bb5d00 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -77,14 +77,14 @@ abstract class Mage_Eav_Model_Entity_Attribute_Abstract extends Mage_Core_Model_ * * @var array */ - protected $_attributeIdCache = array(); + protected $_attributeIdCache = array(); /** * Attribute data table name * * @var string */ - protected $_dataTable = null; + protected $_dataTable = null; /** * Initialize resource model @@ -263,13 +263,14 @@ public function getIsGlobal() * @param Mage_Eav_Model_Entity_Abstract $entity exclude this entity * @return string */ - public function getAlias($entity=null) + public function getAlias($entity = null) { $alias = ''; - if (is_null($entity) || ($entity->getType() !== $this->getEntity()->getType())) { + if (($entity === null) || ($entity->getType() !== $this->getEntity()->getType())) { $alias .= $this->getEntity()->getType() . '/'; } $alias .= $this->getAttributeCode(); + return $alias; } @@ -284,11 +285,13 @@ public function setName($name) return $this->setData('attribute_code', $name); } + /** + * Retreive entity type + * + * @return string + */ public function getEntityType() { - /*if ($this->hasData('entity_type')) { - return $this->_getData('entity_type'); - }*/ return Mage::getSingleton('eav/config')->getEntityType($this->getEntityTypeId()); } @@ -317,6 +320,11 @@ public function getEntity() return $this->_entity; } + /** + * Retreive entity type + * + * @return string + */ public function getEntityIdField() { return $this->getEntity()->getValueEntityIdField(); @@ -335,10 +343,11 @@ public function getBackend() } $backend = Mage::getModel($this->getBackendModel()); if (!$backend) { - throw Mage::exception('Mage_Eav', 'Invalid backend model specified: '.$this->getBackendModel()); + throw Mage::exception('Mage_Eav', 'Invalid backend model specified: ' . $this->getBackendModel()); } $this->_backend = $backend->setAttribute($this); } + return $this->_backend; } @@ -356,6 +365,7 @@ public function getFrontend() $this->_frontend = Mage::getModel($this->getFrontendModel()) ->setAttribute($this); } + return $this->_frontend; } @@ -370,15 +380,21 @@ public function getSource() if (!$this->getSourceModel()) { $this->setSourceModel($this->_getDefaultSourceModel()); } - $this->_source = Mage::getModel($this->getSourceModel()) - ->setAttribute($this); + $source = Mage::getModel($this->getSourceModel()); + if (!$source) { + throw Mage::exception('Mage_Eav', + Mage::helper('eav')->__('Source model "%s" not found for attribute "%s"',$this->getSourceModel(), $this->getAttributeCode()) + ); + } + $this->_source = $source->setAttribute($this); } return $this->_source; } public function usesSource() { - return $this->getFrontendInput()==='select' || $this->getFrontendInput()==='multiselect'; + return $this->getFrontendInput() === 'select' || $this->getFrontendInput() === 'multiselect' + || $this->getData('source_model') != ''; } protected function _getDefaultBackendModel() @@ -400,9 +416,10 @@ public function isValueEmpty($value) { $attrType = $this->getBackend()->getType(); $isEmpty = is_array($value) - || is_null($value) - || $value===false && $attrType!='int' - || $value==='' && ($attrType=='int' || $attrType=='decimal' || $attrType=='datetime'); + || ($value === null) + || $value === false && $attrType != 'int' + || $value === '' && ($attrType == 'int' || $attrType == 'decimal' || $attrType == 'datetime'); + return $isEmpty; } @@ -440,7 +457,8 @@ public function isInSet($setId) */ public function isInGroup($setId, $groupId) { - if ($this->isInSet($setId) && $this->getData('attribute_set_info/' . $setId . '/group_id') == $groupId) { + $dataPath = sprintf('attribute_set_info/%s/group_id', $setId); + if ($this->isInSet($setId) && $this->getData($dataPath) == $groupId) { return true; } @@ -483,30 +501,129 @@ public function getBackendTable() if ($this->_dataTable === null) { if ($this->isStatic()) { $this->_dataTable = $this->getEntityType()->getValueTablePrefix(); - } elseif ($backendTable = trim($this->_getData('backend_table'))) { - $this->_dataTable = $backendTable; } else { - $this->_dataTable = $this->getEntity()->getValueTablePrefix().'_'.$this->getBackendType(); + $backendTable = trim($this->_getData('backend_table')); + if (empty($backendTable)) { + $entityTable = array($this->getEntity()->getEntityTablePrefix(), $this->getBackendType()); + $backendTable = $this->getResource()->getTable($entityTable); + } + $this->_dataTable = $backendTable; } } return $this->_dataTable; } /** - * Retrieve Flat Column(s) + * Retrieve flat columns definition * * @return array */ - public function getFlatColumns() { - if ($this->usesSource() && $this->getBackendType() != 'static') { + public function getFlatColumns() + { + // If source model exists - get definition from it + if ($this->usesSource() && $this->getBackendType() != self::TYPE_STATIC) { return $this->getSource()->getFlatColums(); } + if (Mage::helper('core')->useDbCompatibleMode()) { + return $this->_getFlatColumnsOldDefinition(); + } else { + return $this->_getFlatColumnsDdlDefinition(); + } + } + + /** + * Retrieve flat columns DDL definition + * + * @return array + */ + public function _getFlatColumnsDdlDefinition() + { + $helper = Mage::getResourceHelper('eav'); $columns = array(); switch ($this->getBackendType()) { case 'static': - $describe = $this->_getResource() - ->describeTable($this->getBackend()->getTable()); + $describe = $this->_getResource()->describeTable($this->getBackend()->getTable()); + if (!isset($describe[$this->getAttributeCode()])) { + break; + } + $prop = $describe[$this->getAttributeCode()]; + $type = $prop['DATA_TYPE']; + $size = ($prop['LENGTH'] ? $prop['LENGTH'] : null); + + $columns[$this->getAttributeCode()] = array( + 'type' => $helper->getDdlTypeByColumnType($type), + 'length' => $size, + 'unsigned' => $prop['UNSIGNED'] ? true: false, + 'nullable' => $prop['NULLABLE'], + 'default' => $prop['DEFAULT'], + 'extra' => null + ); + break; + case 'datetime': + $columns[$this->getAttributeCode()] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATETIME, + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null + ); + break; + case 'decimal': + $columns[$this->getAttributeCode()] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_DECIMAL, + 'length' => '12,4', + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null + ); + break; + case 'int': + $columns[$this->getAttributeCode()] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null + ); + break; + case 'text': + $columns[$this->getAttributeCode()] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null, + 'length' => Varien_Db_Ddl_Table::MAX_TEXT_SIZE + ); + break; + case 'varchar': + $columns[$this->getAttributeCode()] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '255', + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null + ); + break; + } + + return $columns; + } + + /** + * Retrieve flat columns definition in old format (before MMDB support) + * Used in database compatible mode + * + * @return array + */ + protected function _getFlatColumnsOldDefinition() { + $columns = array(); + switch ($this->getBackendType()) { + case 'static': + $describe = $this->_getResource()->describeTable($this->getBackend()->getTable()); if (!isset($describe[$this->getAttributeCode()])) { break; } @@ -569,7 +686,7 @@ public function getFlatColumns() { } /** - * Retrieve index data for Flat table + * Retrieve index data for flat table * * @return array */ @@ -581,7 +698,7 @@ public function getFlatIndexes() } if ($condition) { - if ($this->usesSource() && $this->getBackendType() != 'static') { + if ($this->usesSource() && $this->getBackendType() != self::TYPE_STATIC) { return $this->getSource()->getFlatIndexes(); } $indexes = array(); @@ -639,6 +756,7 @@ public function getFlatIndexes() return $indexes; } + return array(); } @@ -649,14 +767,14 @@ public function getFlatIndexes() * @return Varien_Db_Select */ public function getFlatUpdateSelect($store = null) { - if (is_null($store)) { + if ($store === null) { foreach (Mage::app()->getStores() as $store) { $this->getFlatUpdateSelect($store->getId()); } return $this; } - if ($this->getBackendType() == 'static') { + if ($this->getBackendType() == self::TYPE_STATIC) { return null; } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Abstract.php index 972545ac..f9c488b6 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,7 +32,8 @@ * @package Mage_Eav * @author Magento Core Team */ -abstract class Mage_Eav_Model_Entity_Attribute_Backend_Abstract implements Mage_Eav_Model_Entity_Attribute_Backend_Interface +abstract class Mage_Eav_Model_Entity_Attribute_Backend_Abstract + implements Mage_Eav_Model_Entity_Attribute_Backend_Interface { /** * Reference to the attribute instance @@ -48,6 +49,13 @@ abstract class Mage_Eav_Model_Entity_Attribute_Backend_Abstract implements Mage_ */ protected $_valueId; + /** + * PK value_ids for each loaded entity + * + * @var array + */ + protected $_valueIds = array(); + /** * Table name for this attribute * @@ -125,8 +133,8 @@ public function getTable() $this->_table = $this->getAttribute()->getBackendTable(); } else { $entity = $this->getAttribute()->getEntity(); - $this->_table = $entity->getValueTablePrefix() - .'_'.$this->getType(); + $tableName = sprintf('%s_%s', $entity->getValueTablePrefix(), $this->getType()); + $this->_table = $tableName; } } @@ -147,32 +155,89 @@ public function getEntityIdField() $this->_entityIdField = $this->getAttribute()->getEntityType()->getValueEntityIdField(); } } + return $this->_entityIdField; } + /** + * Set value id + * + * @param int $valueId + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function setValueId($valueId) { $this->_valueId = $valueId; return $this; } + /** + * Set entity value id + * + * @param Varien_Object $entity + * @param int $valueId + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ + public function setEntityValueId($entity, $valueId) + { + if (!$entity || !$entity->getId()) { + return $this->setValueId($valueId); + } + + $this->_valueIds[$entity->getId()] = $valueId; + return $this; + } + + /** + * Retrieve value id + * + * @return int + */ public function getValueId() { return $this->_valueId; } + /** + * Get entity value id + * + * @param Varien_Object $entity + * @return int + */ + public function getEntityValueId($entity) + { + if (!$entity || !$entity->getId() || !array_key_exists($entity->getId(), $this->_valueIds)) { + return $this->getValueId(); + } + + return $this->_valueIds[$entity->getId()]; + } + + /** + * Retrieve default value + * + * @return mixed + */ public function getDefaultValue() { - if (is_null($this->_defaultValue)) { + if ($this->_defaultValue === null) { if ($this->getAttribute()->getDefaultValue()) { $this->_defaultValue = $this->getAttribute()->getDefaultValue(); } else { $this->_defaultValue = ""; } } + return $this->_defaultValue; } + /** + * Validate object + * + * @param Varien_Object $object + * @throws Mage_Eav_Exception + * @return boolean + */ public function validate($object) { $attrCode = $this->getAttribute()->getAttributeCode(); @@ -181,44 +246,81 @@ public function validate($object) return false; } - if ($this->getAttribute()->getIsUnique() && !$this->getAttribute()->getIsRequired() && ($value == '' || $this->getAttribute()->isValueEmpty($value))) { + if ($this->getAttribute()->getIsUnique() + && !$this->getAttribute()->getIsRequired() + && ($value == '' || $this->getAttribute()->isValueEmpty($value))) + { return true; } if ($this->getAttribute()->getIsUnique()) { if (!$this->getAttribute()->getEntity()->checkAttributeUniqueValue($this->getAttribute(), $object)) { $label = $this->getAttribute()->getFrontend()->getLabel(); - Mage::throwException(Mage::helper('eav')->__('The value of attribute "%s" must be unique.', $label)); + throw Mage::exception('Mage_Eav', + Mage::helper('eav')->__('The value of attribute "%s" must be unique', $label) + ); } } + return true; } + /** + * After load method + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function afterLoad($object) { - + return $this; } + /** + * Before save method + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function beforeSave($object) { $attrCode = $this->getAttribute()->getAttributeCode(); if (!$object->hasData($attrCode) && $this->getDefaultValue()) { $object->setData($attrCode, $this->getDefaultValue()); } + + return $this; } + /** + * After save method + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function afterSave($object) { - + return $this; } + /** + * Before delete method + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function beforeDelete($object) { - + return $this; } - + /** + * After delete method + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function afterDelete($object) { - + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Array.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Array.php index 25066c88..ea532dea 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Array.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Array.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,12 +33,21 @@ */ class Mage_Eav_Model_Entity_Attribute_Backend_Array extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Prepare data for save + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Abstract + */ public function beforeSave($object) { - $data = $object->getData($this->getAttribute()->getAttributeCode()); + $attributeCode = $this->getAttribute()->getAttributeCode(); + $data = $object->getData($attributeCode); if (is_array($data)) { - $object->setData($this->getAttribute()->getAttributeCode(), implode(',', $data)); + $data = array_filter($data); + $object->setData($attributeCode, implode(',', $data)); } + return parent::beforeSave($object); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Datetime.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Datetime.php index c9a80fd2..ef24eea0 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Datetime.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Datetime.php @@ -20,25 +20,42 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Eav_Model_Entity_Attribute_Backend_Datetime extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Formating date value before save + * + * Should set (bool, string) correct type for empty value from html form, + * neccessary for farther proccess, else date string + * + * @param Varien_Object $object + * @throws Mage_Eav_Exception + * @return Mage_Eav_Model_Entity_Attribute_Backend_Datetime + */ public function beforeSave($object) { $attributeName = $this->getAttribute()->getName(); - $_formated = $object->getData($attributeName . '_is_formated'); + $_formated = $object->getData($attributeName . '_is_formated'); if (!$_formated && $object->hasData($attributeName)) { try { $value = $this->formatDate($object->getData($attributeName)); } catch (Exception $e) { - throw new Exception("Invalid date."); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid date')); + } + + if (is_null($value)) { + $value = $object->getData($attributeName); } + $object->setData($attributeName, $value); $object->setData($attributeName . '_is_formated', true); } + + return $this; } /** @@ -59,6 +76,11 @@ public function formatDate($date) if (preg_match('/^[0-9]+$/', $date)) { $date = new Zend_Date((int)$date); } + // international format + else if (preg_match('#^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$#', $date)) { + $zendDate = new Zend_Date(); + $date = $zendDate->setIso($date); + } // parse this date in current locale, do not apply GMT offset else { $date = Mage::app()->getLocale()->date($date, @@ -68,5 +90,4 @@ public function formatDate($date) } return $date->toString(Varien_Date::DATETIME_INTERNAL_FORMAT); } - } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Default.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Default.php index e0e66616..5678e432 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Default.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Default.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Increment.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Increment.php index 6a5080d2..7078d08a 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Increment.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Increment.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,10 +33,18 @@ */ class Mage_Eav_Model_Entity_Attribute_Backend_Increment extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Set new increment id + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Increment + */ public function beforeSave($object) { if (!$object->getId()) { $this->getAttribute()->getEntity()->setNewIncrementId($object); } + + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Interface.php index 9fd94057..6d8f9066 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -45,4 +45,19 @@ public function beforeSave($object); public function afterSave($object); public function beforeDelete($object); public function afterDelete($object); + + /** + * Get entity value id + * + * @param Varien_Object $entity + */ + public function getEntityValueId($entity); + + /** + * Set entity value id + * + * @param Varien_Object $entity + * @param int $valueId + */ + public function setEntityValueId($entity, $valueId); } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Serialized.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Serialized.php index 352f5d63..e2e689f5 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Serialized.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Serialized.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,7 +31,9 @@ class Mage_Eav_Model_Entity_Attribute_Backend_Serialized extends Mage_Eav_Model_ { /** * Serialize before saving + * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Serialized */ public function beforeSave($object) { @@ -40,31 +42,41 @@ public function beforeSave($object) if ($object->hasData($attrCode)) { $object->setData($attrCode, serialize($object->getData($attrCode))); } + + return $this; } /** * Unserialize after saving + * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Serialized */ public function afterSave($object) { parent::afterSave($object); $this->_unserialize($object); + return $this; } /** * Unserialize after loading + * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Serialized */ public function afterLoad($object) { parent::afterLoad($object); $this->_unserialize($object); + return $this; } /** * Try to unserialize the attribute value + * * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Serialized */ protected function _unserialize(Varien_Object $object) { @@ -77,5 +89,7 @@ protected function _unserialize(Varien_Object $object) $object->unsetData($attrCode); } } + + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Store.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Store.php index 794ab918..f6edd877 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Store.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Store.php @@ -20,17 +20,25 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Eav_Model_Entity_Attribute_Backend_Store extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Prepare data before save + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Store + */ protected function _beforeSave($object) { if (!$object->getData($this->getAttribute()->getAttributeCode())) { $object->setData($this->getAttribute()->getAttributeCode(), Mage::app()->getStore()->getId()); } + + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Created.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Created.php index 1c790cde..29a14133 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Created.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Created.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,10 +33,19 @@ */ class Mage_Eav_Model_Entity_Attribute_Backend_Time_Created extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Set created date + * + * @param Mage_Core_Model_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Time_Created + */ public function beforeSave($object) { - if (!$object->getId() && is_null($object->getData($this->getAttribute()->getAttributeCode()))) { - $object->setData($this->getAttribute()->getAttributeCode(), now()); + $attributeCode = $this->getAttribute()->getAttributeCode(); + if ($object->isObjectNew() && is_null($object->getData($attributeCode))) { + $object->setData($attributeCode, Varien_Date::now()); } + + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Updated.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Updated.php index b6b3121d..0c3b0639 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Updated.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Backend/Time/Updated.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,8 +33,15 @@ */ class Mage_Eav_Model_Entity_Attribute_Backend_Time_Updated extends Mage_Eav_Model_Entity_Attribute_Backend_Abstract { + /** + * Set modified date + * + * @param Varien_Object $object + * @return Mage_Eav_Model_Entity_Attribute_Backend_Time_Updated + */ public function beforeSave($object) { - $object->setData($this->getAttribute()->getAttributeCode(), now()); + $object->setData($this->getAttribute()->getAttributeCode(), Varien_Date::now()); + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Exception.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Exception.php index 320084c5..17298c96 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Exception.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Exception.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php index 5d538cec..77b55ebb 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,7 +32,8 @@ * @package Mage_Eav * @author Magento Core Team */ -abstract class Mage_Eav_Model_Entity_Attribute_Frontend_Abstract implements Mage_Eav_Model_Entity_Attribute_Frontend_Interface +abstract class Mage_Eav_Model_Entity_Attribute_Frontend_Abstract + implements Mage_Eav_Model_Entity_Attribute_Frontend_Interface { /** @@ -75,27 +76,35 @@ public function getInputType() } /** - * Enter description here... + * Retreive lable * * @return string */ public function getLabel() { $label = $this->getAttribute()->getFrontendLabel(); - if (is_null($label) || $label=='') { + if (($label === null) || $label == '') { $label = $this->getAttribute()->getAttributeCode(); } + return $label; } + /** + * Retreive attribute value + * + * @param $object + * @return mixed + */ public function getValue(Varien_Object $object) { $value = $object->getData($this->getAttribute()->getAttributeCode()); if (in_array($this->getConfigField('input'), array('select','boolean'))) { $valueOption = $this->getOption($value); if (!$valueOption) { - $opt = new Mage_Eav_Model_Entity_Attribute_Source_Boolean(); - if ($options = $opt->getAllOptions()) { + $opt = Mage::getModel('eav/entity_attribute_source_boolean'); + $options = $opt->getAllOptions(); + if ($options) { foreach ($options as $option) { if ($option['value'] == $value) { $valueOption = $option['label']; @@ -104,33 +113,94 @@ public function getValue(Varien_Object $object) } } $value = $valueOption; - } - elseif ($this->getConfigField('input')=='multiselect') { + } elseif ($this->getConfigField('input') == 'multiselect') { $value = $this->getOption($value); if (is_array($value)) { $value = implode(', ', $value); } } + return $value; } + /** + * Checks if attribute is visible on frontend + * + * @return boolean + */ public function isVisible() { return $this->getConfigField('frontend_visible'); } + /** + * Retrieve frontend class + * + * @return string + */ public function getClass() { - $out = $this->getAttribute()->getFrontendClass(); + $out = array(); + $out[] = $this->getAttribute()->getFrontendClass(); if ($this->getAttribute()->getIsRequired()) { - $out .= ' required-entry'; + $out[] = 'required-entry'; + } + + $inputRuleClass = $this->_getInputValidateClass(); + if ($inputRuleClass) { + $out[] = $inputRuleClass; + } + if (!empty($out)) { + $out = implode(' ', $out); + } else { + $out = ''; } return $out; } + /** + * Return validate class by attribute input validation rule + * + * @return string|false + */ + protected function _getInputValidateClass() + { + $class = false; + $validateRules = $this->getAttribute()->getValidateRules(); + if (!empty($validateRules['input_validation'])) { + switch ($validateRules['input_validation']) { + case 'alphanumeric': + $class = 'validate-alphanum'; + break; + case 'numeric': + $class = 'validate-digits'; + break; + case 'alpha': + $class = 'validate-alpha'; + break; + case 'email': + $class = 'validate-email'; + break; + case 'url': + $class = 'validate-url'; + break; + default: + $class = false; + break; + } + } + return $class; + } + + /** + * Reireive config field + * + * @param string $fieldName + * @return mixed + */ public function getConfigField($fieldName) { - return $this->getAttribute()->getData('frontend_'.$fieldName); + return $this->getAttribute()->getData('frontend_' . $fieldName); } /** @@ -143,9 +213,16 @@ public function getSelectOptions() return $this->getAttribute()->getSource()->getAllOptions(); } + /** + * Retreive option by option id + * + * @param int $optionId + * @return mixed|boolean + */ public function getOption($optionId) { - if ($source = $this->getAttribute()->getSource()) { + $source = $this->getAttribute()->getSource(); + if ($source) { return $source->getOptionText($optionId); } return false; @@ -157,10 +234,10 @@ public function getOption($optionId) * @return string */ public function getInputRendererClass() { - if ($className = $this->getAttribute()->getData('frontend_input_renderer')) { + $className = $this->getAttribute()->getData('frontend_input_renderer'); + if ($className) { return Mage::getConfig()->getBlockClassName($className); } return null; } - } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Datetime.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Datetime.php index 9088199b..a5fed822 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Datetime.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Datetime.php @@ -20,13 +20,19 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Eav_Model_Entity_Attribute_Frontend_Datetime extends Mage_Eav_Model_Entity_Attribute_Frontend_Abstract { + /** + * Retreive attribute value + * + * @param $object + * @return mixed + */ public function getValue(Varien_Object $object) { $data = ''; diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Default.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Default.php index 6e2e1b37..38a62fa9 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Default.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Default.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Interface.php index c86be297..f9bba510 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Frontend/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Group.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Group.php index 5c7487f1..4a202bbf 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Group.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Group.php @@ -20,13 +20,34 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Eav_Model_Resource_Entity_Attribute_Group _getResource() + * @method Mage_Eav_Model_Resource_Entity_Attribute_Group getResource() + * @method int getAttributeSetId() + * @method Mage_Eav_Model_Entity_Attribute_Group setAttributeSetId(int $value) + * @method string getAttributeGroupName() + * @method Mage_Eav_Model_Entity_Attribute_Group setAttributeGroupName(string $value) + * @method int getSortOrder() + * @method Mage_Eav_Model_Entity_Attribute_Group setSortOrder(int $value) + * @method int getDefaultId() + * @method Mage_Eav_Model_Entity_Attribute_Group setDefaultId(int $value) + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ class Mage_Eav_Model_Entity_Attribute_Group extends Mage_Core_Model_Abstract { + /** + * Resource initialization + */ protected function _construct() { $this->_init('eav/entity_attribute_group'); @@ -42,6 +63,11 @@ public function itemExists() return $this->_getResource()->itemExists($this); } + /** + * Delete groups + * + * @return Mage_Eav_Model_Entity_Attribute_Group + */ public function deleteGroups() { return $this->_getResource()->deleteGroups($this); diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Interface.php index 98be8831..27d0feed 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Interface.php @@ -20,12 +20,11 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ interface Mage_Eav_Model_Entity_Attribute_Interface { - } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Option.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Option.php index c5c2e64a..7f2248a3 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Option.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Option.php @@ -20,19 +20,29 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Emtity attribute option model * - * @category Mage - * @package Mage_Eav + * @method Mage_Eav_Model_Resource_Entity_Attribute_Option _getResource() + * @method Mage_Eav_Model_Resource_Entity_Attribute_Option getResource() + * @method int getAttributeId() + * @method Mage_Eav_Model_Entity_Attribute_Option setAttributeId(int $value) + * @method int getSortOrder() + * @method Mage_Eav_Model_Entity_Attribute_Option setSortOrder(int $value) + * + * @category Mage + * @package Mage_Eav * @author Magento Core Team */ class Mage_Eav_Model_Entity_Attribute_Option extends Mage_Core_Model_Abstract { + /** + * Resource initialization + */ public function _construct() { $this->_init('eav/entity_attribute_option'); diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Set.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Set.php index 6af8c148..1f3cc674 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Set.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Set.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,12 +28,27 @@ /** * Eav attribute set model * - * @category Mage - * @package Mage_Eav - * @author Magento Core Team + * @method Mage_Eav_Model_Resource_Entity_Attribute_Set _getResource() + * @method Mage_Eav_Model_Resource_Entity_Attribute_Set getResource() + * @method int getEntityTypeId() + * @method Mage_Eav_Model_Entity_Attribute_Set setEntityTypeId(int $value) + * @method string getAttributeSetName() + * @method Mage_Eav_Model_Entity_Attribute_Set setAttributeSetName(string $value) + * @method int getSortOrder() + * @method Mage_Eav_Model_Entity_Attribute_Set setSortOrder(int $value) + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team */ class Mage_Eav_Model_Entity_Attribute_Set extends Mage_Core_Model_Abstract { + /** + * Prefix of model events names + * @var string + */ + protected $_eventPrefix = 'eav_entity_attribute_set'; + /** * Initialize resource model * @@ -57,7 +72,7 @@ public function initFromSkeleton($skeletonId) ->load(); $newGroups = array(); - foreach( $groups as $group ) { + foreach ($groups as $group) { $newGroup = clone $group; $newGroup->setId(null) ->setAttributeSetId($this->getId()) @@ -69,7 +84,7 @@ public function initFromSkeleton($skeletonId) ->load(); $newAttributes = array(); - foreach( $groupAttributesCollection as $attribute ) { + foreach ($groupAttributesCollection as $attribute) { $newAttribute = Mage::getModel('eav/entity_attribute') ->setId($attribute->getId()) //->setAttributeGroupId($newGroup->getId()) @@ -82,6 +97,7 @@ public function initFromSkeleton($skeletonId) $newGroups[] = $newGroup; } $this->setGroups($newGroups); + return $this; } @@ -89,6 +105,7 @@ public function initFromSkeleton($skeletonId) * Collect data for save * * @param array $data + * @return Mage_Eav_Model_Entity_Attribute_Set */ public function organizeData($data) { @@ -104,7 +121,7 @@ public function organizeData($data) ->getValidAttributeIds($ids); } if( $data['groups'] ) { - foreach( $data['groups'] as $group ) { + foreach ($data['groups'] as $group) { $modelGroup = Mage::getModel('eav/entity_attribute_group'); $modelGroup->setId(is_numeric($group[0]) && $group[0] > 0 ? $group[0] : null) ->setAttributeGroupName($group[1]) @@ -155,22 +172,25 @@ public function organizeData($data) } $this->setAttributeSetName($data['attribute_set_name']) ->setEntityTypeId($this->getEntityTypeId()); + + return $this; } /** * Validate attribute set name * * @param string $name - * @throws Mage_Core_Exception + * @throws Mage_Eav_Exception * @return bool */ public function validate() { if (!$this->_getResource()->validate($this, $this->getAttributeSetName())) { - Mage::throwException( + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Attribute set with the "%s" name already exists.', $this->getAttributeSetName()) ); } + return true; } @@ -219,8 +239,7 @@ public function addSetInfo($entityType, array $attributes, $setId = null) $attributeSetInfo[$setId] = $setInfo[$attribute->getAttributeId()][$setId]; } $attribute->setAttributeSetInfo($attributeSetInfo); - } - else { + } else { if (isset($setInfo[$attribute->getAttributeId()])) { $attribute->setAttributeSetInfo($setInfo[$attribute->getAttributeId()]); } @@ -233,4 +252,23 @@ public function addSetInfo($entityType, array $attributes, $setId = null) return $this; } + + /** + * Return default Group Id for current or defined Attribute Set + * + * @param int $setId + * @return int|null + */ + public function getDefaultGroupId($setId = null) + { + if ($setId === null) { + $setId = $this->getId(); + } + if ($setId) { + $groupId = $this->_getResource()->getDefaultGroupId($setId); + } else { + $groupId = null; + } + return $groupId; + } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Abstract.php index 814c42e7..51ca14f3 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,9 +30,10 @@ * * @category Mage * @package Mage_Eav - * @author Magento Core Team + * @author Magento Core Team */ -abstract class Mage_Eav_Model_Entity_Attribute_Source_Abstract implements Mage_Eav_Model_Entity_Attribute_Source_Interface +abstract class Mage_Eav_Model_Entity_Attribute_Source_Abstract + implements Mage_Eav_Model_Entity_Attribute_Source_Interface { /** * Reference to the attribute instance @@ -46,7 +47,7 @@ abstract class Mage_Eav_Model_Entity_Attribute_Source_Abstract implements Mage_E * * @var array */ - protected $_options; + protected $_options = null; /** * Set attribute instance @@ -73,8 +74,8 @@ public function getAttribute() /** * Get a text for option value * - * @param string|integer $value - * @return string + * @param string|integer $value + * @return string|bool */ public function getOptionText($value) { @@ -108,12 +109,12 @@ public function getOptionId($value) * @param string $dir direction * @return Mage_Eav_Model_Entity_Attribute_Source_Abstract */ - public function addValueSortToCollection($collection, $dir = 'asc') { + public function addValueSortToCollection($collection, $dir = Varien_Data_Collection::SORT_ORDER_DESC) { return $this; } /** - * Retrieve Column(s) for Flat + * Retrieve flat column definition * * @return array */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php index 22cbdd0a..79894c82 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Boolean.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -81,22 +81,30 @@ public function getOptionText($value) } /** - * Retrieve Column(s) for Flat + * Retrieve flat column definition * * @return array */ public function getFlatColums() { - $columns = array(); - $columns[$this->getAttribute()->getAttributeCode()] = array( - 'type' => 'tinyint(1)', + $attributeCode = $this->getAttribute()->getAttributeCode(); + $column = array( 'unsigned' => false, - 'is_null' => true, 'default' => null, 'extra' => null ); - return $columns; + if (Mage::helper('core')->useDbCompatibleMode()) { + $column['type'] = 'tinyint(1)'; + $column['is_null'] = true; + } else { + $column['type'] = Varien_Db_Ddl_Table::TYPE_SMALLINT; + $column['length'] = 1; + $column['nullable'] = true; + $column['comment'] = $attributeCode . ' column'; + } + + return array($attributeCode => $column); } /** diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Config.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Config.php index 54914e6d..9b3cf0bf 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Config.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -36,23 +36,29 @@ */ class Mage_Eav_Model_Entity_Attribute_Source_Config extends Mage_Eav_Model_Entity_Attribute_Source_Abstract { + /** + * Config Node Path + * + * @var Mage_Core_Model_Config_Element + */ protected $_configNodePath; /** * Retrieve all options for the source from configuration * + * @throws Mage_Eav_Exception * @return array */ public function getAllOptions() { - if (is_null($this->_options)) { + if ($this->_options === null) { $this->_options = array(); $rootNode = null; if ($this->_configNodePath) { $rootNode = Mage::getConfig()->getNode($this->_configNodePath); } if (!$rootNode) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Failed to load node %s from config.', $this->_configNodePath)); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Failed to load node %s from config', $this->_configNodePath)); } $options = $rootNode->children(); if (empty($options)) { diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Interface.php index 94f8ffca..fb17a3c7 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Store.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Store.php index a3f8f89c..5b693094 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Store.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Store.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,9 +33,14 @@ */ class Mage_Eav_Model_Entity_Attribute_Source_Store extends Mage_Eav_Model_Entity_Attribute_Source_Table { + /** + * Retrieve Full Option values array + * + * @return array + */ public function getAllOptions() { - if (is_null($this->_options)) { + if ($this->_options === null) { $this->_options = Mage::getResourceModel('core/store_collection')->load()->toOptionArray(); } return $this->_options; diff --git a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php index 3850cd71..138247ec 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php +++ b/app/code/core/Mage/Eav/Model/Entity/Attribute/Source/Table.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -61,8 +61,9 @@ public function getAllOptions($withEmpty = true, $defaultValues = false) } $options = ($defaultValues ? $this->_optionsDefault[$storeId] : $this->_options[$storeId]); if ($withEmpty) { - array_unshift($options, array('label'=>'', 'value'=>'')); + array_unshift($options, array('label' => '', 'value' => '')); } + return $options; } @@ -91,14 +92,13 @@ public function getOptionText($value) } return $values; } - else { - foreach ($options as $item) { - if ($item['value'] == $value) { - return $item['label']; - } + + foreach ($options as $item) { + if ($item['value'] == $value) { + return $item['label']; } - return false; } + return false; } /** @@ -109,25 +109,26 @@ public function getOptionText($value) * * @return Mage_Eav_Model_Entity_Attribute_Source_Table */ - public function addValueSortToCollection($collection, $dir = 'asc') + public function addValueSortToCollection($collection, $dir = Varien_Db_Select::SQL_ASC) { $valueTable1 = $this->getAttribute()->getAttributeCode() . '_t1'; $valueTable2 = $this->getAttribute()->getAttributeCode() . '_t2'; $collection->getSelect() ->joinLeft( array($valueTable1 => $this->getAttribute()->getBackend()->getTable()), - "`e`.`entity_id`=`{$valueTable1}`.`entity_id`" - . " AND `{$valueTable1}`.`attribute_id`='{$this->getAttribute()->getId()}'" - . " AND `{$valueTable1}`.`store_id`='0'", + "e.entity_id={$valueTable1}.entity_id" + . " AND {$valueTable1}.attribute_id='{$this->getAttribute()->getId()}'" + . " AND {$valueTable1}.store_id=0", array()) ->joinLeft( array($valueTable2 => $this->getAttribute()->getBackend()->getTable()), - "`e`.`entity_id`=`{$valueTable2}`.`entity_id`" - . " AND `{$valueTable2}`.`attribute_id`='{$this->getAttribute()->getId()}'" - . " AND `{$valueTable2}`.`store_id`='{$collection->getStoreId()}'", + "e.entity_id={$valueTable2}.entity_id" + . " AND {$valueTable2}.attribute_id='{$this->getAttribute()->getId()}'" + . " AND {$valueTable2}.store_id='{$collection->getStoreId()}'", array() ); - $valueExpr = new Zend_Db_Expr("IF(`{$valueTable2}`.`value_id`>0, `{$valueTable2}`.`value`, `{$valueTable1}`.`value`)"); + $valueExpr = $collection->getSelect()->getAdapter() + ->getCheckSql("{$valueTable2}.value_id > 0", "{$valueTable2}.value", "{$valueTable1}.value"); Mage::getResourceModel('eav/entity_attribute_option') ->addOptionValueToCollection($collection, $this->getAttribute(), $valueExpr); @@ -146,23 +147,48 @@ public function addValueSortToCollection($collection, $dir = 'asc') public function getFlatColums() { $columns = array(); + $attributeCode = $this->getAttribute()->getAttributeCode(); $isMulti = $this->getAttribute()->getFrontend()->getInputType() == 'multiselect'; - $columns[$this->getAttribute()->getAttributeCode()] = array( - 'type' => $isMulti ? 'varchar(255)' : 'int', - 'unsigned' => false, - 'is_null' => true, - 'default' => null, - 'extra' => null - ); - if (!$isMulti) { - $columns[$this->getAttribute()->getAttributeCode() . '_value'] = array( - 'type' => 'varchar(255)', + if (Mage::helper('core')->useDbCompatibleMode()) { + $columns[$attributeCode] = array( + 'type' => $isMulti ? 'varchar(255)' : 'int', 'unsigned' => false, 'is_null' => true, 'default' => null, 'extra' => null ); + if (!$isMulti) { + $columns[$attributeCode . '_value'] = array( + 'type' => 'varchar(255)', + 'unsigned' => false, + 'is_null' => true, + 'default' => null, + 'extra' => null + ); + } + } else { + $type = ($isMulti) ? Varien_Db_Ddl_Table::TYPE_TEXT : Varien_Db_Ddl_Table::TYPE_INTEGER; + $columns[$attributeCode] = array( + 'type' => $type, + 'length' => $isMulti ? '255' : null, + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null, + 'comment' => $attributeCode . ' column' + ); + if (!$isMulti) { + $columns[$attributeCode . '_value'] = array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'unsigned' => false, + 'nullable' => true, + 'default' => null, + 'extra' => null, + 'comment' => $attributeCode . ' column' + ); + } } return $columns; @@ -177,15 +203,16 @@ public function getFlatIndexes() { $indexes = array(); - $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()); + $index = sprintf('IDX_%s', strtoupper($this->getAttribute()->getAttributeCode())); $indexes[$index] = array( 'type' => 'index', 'fields' => array($this->getAttribute()->getAttributeCode()) ); $sortable = $this->getAttribute()->getUsedForSortBy(); - if ($sortable and $this->getAttribute()->getFrontend()->getInputType() != 'multiselect') { - $index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode()) . '_VALUE'; + if ($sortable && $this->getAttribute()->getFrontend()->getInputType() != 'multiselect') { + $index = sprintf('IDX_%s_VALUE', strtoupper($this->getAttribute()->getAttributeCode())); + $indexes[$index] = array( 'type' => 'index', 'fields' => array($this->getAttribute()->getAttributeCode() . '_value') @@ -198,7 +225,6 @@ public function getFlatIndexes() /** * Retrieve Select For Flat Attribute update * - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute * @param int $store * @return Varien_Db_Select|null */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Collection.php b/app/code/core/Mage/Eav/Model/Entity/Collection.php index 87134fc7..39686d06 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Collection.php +++ b/app/code/core/Mage/Eav/Model/Entity/Collection.php @@ -20,18 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - class Mage_Eav_Model_Entity_Collection extends Mage_Eav_Model_Entity_Collection_Abstract { + /** + * Initialize resource + */ public function __construct() { $resources = Mage::getSingleton('core/resource'); - parent::__construct($resources->getConnection('customeralert_type')); - - #$this->setConnection(Mage::getSingleton('core/resource')->getConnection('core_read')); + parent::__construct($resources->getConnection('eav_setup')); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php index 889005f3..bef9e0d5 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Collection/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -31,21 +31,21 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Entity_Collection_Abstract extends Varien_Data_Collection_Db +abstract class Mage_Eav_Model_Entity_Collection_Abstract extends Varien_Data_Collection_Db { /** * Array of items with item id key * * @var array */ - protected $_itemsById = array(); + protected $_itemsById = array(); /** * Entity static fields * * @var array */ - protected $_staticFields = array(); + protected $_staticFields = array(); /** * Entity object to define collection's attributes @@ -59,49 +59,66 @@ class Mage_Eav_Model_Entity_Collection_Abstract extends Varien_Data_Collection_D * * @var array */ - protected $_selectEntityTypes = array(); + protected $_selectEntityTypes = array(); /** * Attributes to be fetched for objects in collection * * @var array */ - protected $_selectAttributes=array(); + protected $_selectAttributes = array(); /** * Attributes to be filtered order sorted by * * @var array */ - protected $_filterAttributes=array(); + protected $_filterAttributes = array(); /** * Joined entities * * @var array */ - protected $_joinEntities = array(); + protected $_joinEntities = array(); /** * Joined attributes * * @var array */ - protected $_joinAttributes = array(); + protected $_joinAttributes = array(); /** * Joined fields data * * @var array */ - protected $_joinFields = array(); + protected $_joinFields = array(); + + /** + * Use analytic function flag + * If true - allows to prepare final select with analytic functions + * + * @var bool + */ + protected $_useAnalyticFunction = false; + + /** + * Cast map for attribute order + * + * @var array + */ + protected $_castToIntMap = array( + 'validate-digits' + ); /** * Collection constructor * - * @param Mage_Core_Model_Mysql4_Abstract $resource + * @param Mage_Core_Model_Resource_Abstract $resource */ - public function __construct($resource=null) + public function __construct($resource = null) { parent::__construct(); $this->_construct(); @@ -118,6 +135,12 @@ protected function _construct() } + /** + * Retreive table name + * + * @param string $table + * @return string + */ public function getTable($table) { return $this->getResource()->getTable($table); @@ -136,9 +159,14 @@ protected function _prepareStaticFields() return $this; } + /** + * Init select + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ protected function _initSelect() { - $this->getSelect()->from(array('e'=>$this->getEntity()->getEntityTable())); + $this->getSelect()->from(array('e' => $this->getEntity()->getEntityTable())); if ($this->getEntity()->getTypeId()) { $this->addAttributeToFilter('entity_type_id', $this->getEntity()->getTypeId()); } @@ -151,14 +179,15 @@ protected function _initSelect() * @param string $model * @return Mage_Core_Model_Mysql4_Collection_Abstract */ - protected function _init($model, $entityModel=null) + protected function _init($model, $entityModel = null) { $this->setItemObjectClass(Mage::getConfig()->getModelClassName($model)); - if (is_null($entityModel)) { + if ($entityModel === null) { $entityModel = $model; } $entity = Mage::getResourceSingleton($entityModel); $this->setEntity($entity); + return $this; } @@ -166,6 +195,7 @@ protected function _init($model, $entityModel=null) * Set entity to use for attributes * * @param Mage_Eav_Model_Entity_Abstract $entity + * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function setEntity($entity) @@ -175,7 +205,7 @@ public function setEntity($entity) } elseif (is_string($entity) || $entity instanceof Mage_Core_Model_Config_Element) { $this->_entity = Mage::getModel('eav/entity')->setType($entity); } else { - Mage::throwException(Mage::helper('eav')->__('Invalid entity supplied: %s.', print_r($entity,1))); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity supplied: %s', print_r($entity, 1))); } return $this; } @@ -188,7 +218,7 @@ public function setEntity($entity) public function getEntity() { if (empty($this->_entity)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Entity is not initialized')); } return $this->_entity; } @@ -213,8 +243,7 @@ public function setObject($object=null) { if (is_object($object)) { $this->setItemObjectClass(get_class($object)); - } - else { + } else { $this->setItemObjectClass($object); } @@ -230,8 +259,8 @@ public function setObject($object=null) */ public function addItem(Varien_Object $object) { - if (get_class($object)!== $this->_itemObjectClass) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Attempt to add an invalid object.')); + if (get_class($object) !== $this->_itemObjectClass) { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Attempt to add an invalid object')); } return parent::addItem($object); } @@ -246,9 +275,9 @@ public function getAttribute($attributeCode) { if (isset($this->_joinAttributes[$attributeCode])) { return $this->_joinAttributes[$attributeCode]['attribute']; - } else { - return $this->getEntity()->getAttribute($attributeCode); } + + return $this->getEntity()->getAttribute($attributeCode); } /** @@ -266,17 +295,16 @@ public function getAttribute($attributeCode) * @param string $operator * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function addAttributeToFilter($attribute, $condition=null, $joinType='inner') + public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner') { - if($attribute===null) { + if ($attribute === null) { $this->getSelect(); return $this; } if (is_numeric($attribute)) { $attribute = $this->getEntity()->getAttribute($attribute)->getAttributeCode(); - } - elseif ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Interface) { + } else if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Interface) { $attribute = $attribute->getAttributeCode(); } @@ -285,16 +313,16 @@ public function addAttributeToFilter($attribute, $condition=null, $joinType='inn foreach ($attribute as $condition) { $sqlArr[] = $this->_getAttributeConditionSql($condition['attribute'], $condition, $joinType); } - $conditionSql = '('.join(') OR (', $sqlArr).')'; - } elseif (is_string($attribute)) { - if (is_null($condition)) { + $conditionSql = '('.implode(') OR (', $sqlArr).')'; + } else if (is_string($attribute)) { + if ($condition === null) { $condition = ''; } $conditionSql = $this->_getAttributeConditionSql($attribute, $condition, $joinType); } if (!empty($conditionSql)) { - $this->getSelect()->where($conditionSql); + $this->getSelect()->where($conditionSql, null, Varien_Db_Select::TYPE_CONDITION); } else { Mage::throwException('Invalid attribute identifier for filter ('.get_class($attribute).')'); } @@ -308,7 +336,7 @@ public function addAttributeToFilter($attribute, $condition=null, $joinType='inn * @param mixed $attribute * @param mixed $condition */ - public function addFieldToFilter($attribute, $condition=null) + public function addFieldToFilter($attribute, $condition = null) { return $this->addAttributeToFilter($attribute, $condition); } @@ -320,7 +348,7 @@ public function addFieldToFilter($attribute, $condition=null) * @param string $dir * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function addAttributeToSort($attribute, $dir='asc') + public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC) { if (isset($this->_joinFields[$attribute])) { $this->getSelect()->order($this->_getAttributeFieldName($attribute).' '.$dir); @@ -328,29 +356,59 @@ public function addAttributeToSort($attribute, $dir='asc') } if (isset($this->_staticFields[$attribute])) { $this->getSelect()->order("e.{$attribute} {$dir}"); + return $this; } if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; - $entityField = $this->_getAttributeTableAlias($attribute).'.'.$attrInstance->getAttributeCode(); + $entityField = $this->_getAttributeTableAlias($attribute) . '.' . $attrInstance->getAttributeCode(); } else { $attrInstance = $this->getEntity()->getAttribute($attribute); - $entityField = 'e.'.$attribute; + $entityField = 'e.' . $attribute; } + if ($attrInstance) { if ($attrInstance->getBackend()->isStatic()) { - $this->getSelect()->order($entityField.' '.$dir); + $orderExpr = $entityField; } else { $this->_addAttributeJoin($attribute, 'left'); - if (isset($this->_joinAttributes[$attribute])) { - $this->getSelect()->order($attribute.' '.$dir); + if (isset($this->_joinAttributes[$attribute])||isset($this->_joinFields[$attribute])) { + $orderExpr = $attribute; } else { - $this->getSelect()->order($this->_getAttributeTableAlias($attribute).'.value '.$dir); + $orderExpr = $this->_getAttributeTableAlias($attribute).'.value'; } } + + if (in_array($attrInstance->getFrontendClass(), $this->_castToIntMap)) { + $orderExpr = Mage::getResourceHelper('eav')->getCastToIntExpression( + $this->_prepareOrderExpression($orderExpr) + ); + } + + $orderExpr .= ' ' . $dir; + $this->getSelect()->order($orderExpr); } return $this; } + /** + * Retrieve attribute expression by specified column + * + * @param string $field + * @return string|Zend_Db_Expr + */ + protected function _prepareOrderExpression($field) + { + foreach ($this->getSelect()->getPart(Zend_Db_Select::COLUMNS) as $columnEntry) { + if ($columnEntry[2] != $field) { + continue; + } + if ($columnEntry[1] instanceof Zend_Db_Expr) { + return $columnEntry[1]; + } + } + return $field; + } + /** * Add attribute to entities in collection * @@ -360,7 +418,7 @@ public function addAttributeToSort($attribute, $dir='asc') * @param false|string $joinType flag for joining attribute * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function addAttributeToSelect($attribute, $joinType=false) + public function addAttributeToSelect($attribute, $joinType = false) { if (is_array($attribute)) { Mage::getSingleton('eav/config')->loadCollectionAttributes($this->getEntity()->getType(), $attribute); @@ -369,10 +427,11 @@ public function addAttributeToSelect($attribute, $joinType=false) } return $this; } - if ($joinType!==false && !$this->getEntity()->getAttribute($attribute)->isStatic()) { + if ($joinType !== false && !$this->getEntity()->getAttribute($attribute)->isStatic()) { $this->_addAttributeJoin($attribute, $joinType); - } elseif ('*'===$attribute) { - $attributes = $this->getEntity() + } elseif ('*' === $attribute) { + $entity = clone $this->getEntity(); + $attributes = $entity ->loadAllAttributes() ->getAttributesByCode(); foreach ($attributes as $attrCode=>$attr) { @@ -382,11 +441,14 @@ public function addAttributeToSelect($attribute, $joinType=false) if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; } else { - //$attrInstance = $this->getEntity()->getAttribute($attribute); - $attrInstance = Mage::getSingleton('eav/config')->getCollectionAttribute($this->getEntity()->getType(), $attribute); + $attrInstance = Mage::getSingleton('eav/config') + ->getCollectionAttribute($this->getEntity()->getType(), $attribute); } if (empty($attrInstance)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute requested: %s', (string)$attribute)); + throw Mage::exception( + 'Mage_Eav', + Mage::helper('eav')->__('Invalid attribute requested: %s', (string)$attribute) + ); } $this->_selectAttributes[$attrInstance->getAttributeCode()] = $attrInstance->getId(); } @@ -396,7 +458,7 @@ public function addAttributeToSelect($attribute, $joinType=false) public function addEntityTypeToSelect($entityType, $prefix) { $this->_selectEntityTypes[$entityType] = array( - 'prefix'=>$prefix, + 'prefix' => $prefix, ); return $this; } @@ -432,19 +494,21 @@ public function addExpressionAttributeToSelect($alias, $expression, $attribute) { // validate alias if (isset($this->_joinFields[$alias])) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Joint field or attribute expression with this alias is already declared.')); + throw Mage::exception( + 'Mage_Eav', + Mage::helper('eav')->__('Joint field or attribute expression with this alias is already declared') + ); } - if(!is_array($attribute)) { + if (!is_array($attribute)) { $attribute = array($attribute); } $fullExpression = $expression; // Replacing multiple attributes - foreach($attribute as $attributeItem) { + foreach ($attribute as $attributeItem) { if (isset($this->_staticFields[$attributeItem])) { $attrField = sprintf('e.%s', $attributeItem); - } - else { + } else { $attributeInstance = $this->getAttribute($attributeItem); if ($attributeInstance->getBackend()->isStatic()) { @@ -459,7 +523,7 @@ public function addExpressionAttributeToSelect($alias, $expression, $attribute) $fullExpression = str_replace('{{' . $attributeItem . '}}', $attrField, $fullExpression); } - $this->getSelect()->columns(array($alias=>$fullExpression)); + $this->getSelect()->columns(array($alias => $fullExpression)); $this->_joinFields[$alias] = array( 'table' => false, @@ -477,7 +541,7 @@ public function addExpressionAttributeToSelect($alias, $expression, $attribute) */ public function groupByAttribute($attribute) { - if(is_array($attribute)) { + if (is_array($attribute)) { foreach ($attribute as $attributeItem) { $this->groupByAttribute($attributeItem); } @@ -494,10 +558,10 @@ public function groupByAttribute($attribute) if (isset($this->_joinAttributes[$attribute])) { $attrInstance = $this->_joinAttributes[$attribute]['attribute']; - $entityField = $this->_getAttributeTableAlias($attribute).'.'.$attrInstance->getAttributeCode(); + $entityField = $this->_getAttributeTableAlias($attribute) . '.' . $attrInstance->getAttributeCode(); } else { $attrInstance = $this->getEntity()->getAttribute($attribute); - $entityField = 'e.'.$attribute; + $entityField = 'e.' . $attribute; } if ($attrInstance->getBackend()->isStatic()) { @@ -536,7 +600,10 @@ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType { // validate alias if (isset($this->_joinAttributes[$alias])) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid alias, already exists in joint attributes.')); + throw Mage::exception( + 'Mage_Eav', + Mage::helper('eav')->__('Invalid alias, already exists in joint attributes') + ); } // validate bind attribute @@ -545,14 +612,14 @@ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType } if (!$bindAttribute || (!$bindAttribute->isStatic() && !$bindAttribute->getId())) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid foreign key.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid foreign key')); } // try to explode combined entity/attribute if supplied if (is_string($attribute)) { $attrArr = explode('/', $attribute); if (isset($attrArr[1])) { - $entity = $attrArr[0]; + $entity = $attrArr[0]; $attribute = $attrArr[1]; } } @@ -569,7 +636,7 @@ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType } } if (!$entity || !$entity->getTypeId()) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity type.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid entity type')); } // cache entity @@ -582,7 +649,7 @@ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType $attribute = $entity->getAttribute($attribute); } if (!$attribute) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute type.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute type')); } if (empty($filter)) { @@ -607,7 +674,8 @@ public function joinAttribute($alias, $attribute, $bind, $filter=null, $joinType * Join regular table field and use an attribute as fk * * Examples: - * ('country_name', 'directory/country_name', 'name', 'country_id=shipping_country', "{{table}}.language_code='en'", 'left') + * ('country_name', 'directory/country_name', 'name', 'country_id=shipping_country', + * "{{table}}.language_code='en'", 'left') * * @param string $alias 'country_name' * @param string $table 'directory/country_name' @@ -621,7 +689,10 @@ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType=' { // validate alias if (isset($this->_joinFields[$alias])) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Joined field with this alias is already declared.')); + throw Mage::exception( + 'Mage_Eav', + Mage::helper('eav')->__('Joined field with this alias is already declared') + ); } // validate table @@ -632,7 +703,8 @@ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType=' // validate bind list($pk, $fk) = explode('=', $bind); - $bindCond = $tableAlias.'.'.$pk.'='.$this->_getAttributeFieldName($fk); + $pk = $this->getSelect()->getAdapter()->quoteColumnAs(trim($pk), null); + $bindCond = $tableAlias . '.' . trim($pk) . '=' . $this->_getAttributeFieldName(trim($fk)); // process join type switch ($joinType) { @@ -646,7 +718,7 @@ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType=' $condArr = array($bindCond); // add where condition if needed - if (!is_null($cond)) { + if ($cond !== null) { if (is_array($cond)) { foreach ($cond as $k=>$v) { $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v); @@ -655,15 +727,16 @@ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType=' $condArr[] = str_replace('{{table}}', $tableAlias, $cond); } } - $cond = '('.join(') AND (', $condArr).')'; + $cond = '(' . implode(') AND (', $condArr) . ')'; // join table - $this->getSelect()->$joinMethod(array($tableAlias=>$table), $cond, ($field ? array($alias=>$field) : array())); + $this->getSelect() + ->$joinMethod(array($tableAlias => $table), $cond, ($field ? array($alias=>$field) : array())); // save joined attribute $this->_joinFields[$alias] = array( - 'table'=>$tableAlias, - 'field'=>$field, + 'table' => $tableAlias, + 'field' => $field, ); return $this; @@ -679,13 +752,12 @@ public function joinField($alias, $table, $field, $bind, $cond=null, $joinType=' * @param string $joinType * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function joinTable($table, $bind, $fields=null, $cond=null, $joinType='inner') + public function joinTable($table, $bind, $fields = null, $cond = null, $joinType = 'inner') { $tableAlias = null; if (is_array($table)) { list($tableAlias, $tableName) = each($table); - } - else { + } else { $tableName = $table; } @@ -699,11 +771,14 @@ public function joinTable($table, $bind, $fields=null, $cond=null, $joinType='in // validate fields and aliases if (!$fields) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid joint fields.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid joint fields')); } foreach ($fields as $alias=>$field) { if (isset($this->_joinFields[$alias])) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('A joint field with this alias (%s) is already declared.', $alias)); + throw Mage::exception( + 'Mage_Eav', + Mage::helper('eav')->__('A joint field with this alias (%s) is already declared', $alias) + ); } $this->_joinFields[$alias] = array( 'table' => $tableAlias, @@ -727,16 +802,16 @@ public function joinTable($table, $bind, $fields=null, $cond=null, $joinType='in $condArr = array($bindCond); // add where condition if needed - if (!is_null($cond)) { + if ($cond !== null) { if (is_array($cond)) { - foreach ($cond as $k=>$v) { + foreach ($cond as $k => $v) { $condArr[] = $this->_getConditionSql($tableAlias.'.'.$k, $v); } } else { $condArr[] = str_replace('{{table}}', $tableAlias, $cond); } } - $cond = '('.join(') AND (', $condArr).')'; + $cond = '('.implode(') AND (', $condArr).')'; // join table $this->getSelect()->$joinMethod(array($tableAlias => $tableName), $cond, $fields); @@ -750,9 +825,9 @@ public function joinTable($table, $bind, $fields=null, $cond=null, $joinType='in * @param string $attribute * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function removeAttributeToSelect($attribute=null) + public function removeAttributeToSelect($attribute = null) { - if (is_null($attribute)) { + if ($attribute === null) { $this->_selectAttributes = array(); } else { unset($this->_selectAttributes[$attribute]); @@ -789,6 +864,9 @@ public function load($printQuery = false, $logQuery = false) $this->_beforeLoad(); Varien_Profiler::stop('__EAV_COLLECTION_BEFORE_LOAD__'); + $this->_renderFilters(); + $this->_renderOrders(); + Varien_Profiler::start('__EAV_COLLECTION_LOAD_ENT__'); $this->_loadEntities($printQuery, $logQuery); Varien_Profiler::stop('__EAV_COLLECTION_LOAD_ENT__'); @@ -814,15 +892,16 @@ public function load($printQuery = false, $logQuery = false) * * @return Mage_Eav_Model_Entity_Collection_Abstract */ - protected function _getAllIdsSelect($limit=null, $offset=null) + protected function _getAllIdsSelect($limit = null, $offset = null) { $idsSelect = clone $this->getSelect(); $idsSelect->reset(Zend_Db_Select::ORDER); $idsSelect->reset(Zend_Db_Select::LIMIT_COUNT); $idsSelect->reset(Zend_Db_Select::LIMIT_OFFSET); $idsSelect->reset(Zend_Db_Select::COLUMNS); - $idsSelect->columns('e.'.$this->getEntity()->getIdFieldName()); + $idsSelect->columns('e.' . $this->getEntity()->getIdFieldName()); $idsSelect->limit($limit, $offset); + return $idsSelect; } @@ -831,7 +910,7 @@ protected function _getAllIdsSelect($limit=null, $offset=null) * * @return array */ - public function getAllIds($limit=null, $offset=null) + public function getAllIds($limit = null, $offset = null) { return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); } @@ -850,6 +929,7 @@ public function getAllIdsSql() $idsSelect->reset(Zend_Db_Select::COLUMNS); $idsSelect->reset(Zend_Db_Select::GROUP); $idsSelect->columns('e.'.$this->getEntity()->getIdFieldName()); + return $idsSelect; } @@ -897,7 +977,7 @@ public function importFromArray($arr) if (!isset($this->_items[$entityId])) { $this->_items[$entityId] = $this->getNewEmptyItem(); $this->_items[$entityId]->setData($row); - } else { + } else { $this->_items[$entityId]->addData($row); } } @@ -919,15 +999,24 @@ public function exportToArray() return $result; } - + /** + * Retreive row id field name + * + * @return string + */ public function getRowIdFieldName() { - if (is_null($this->_idFieldName)) { + if ($this->_idFieldName === null) { $this->_setIdFieldName($this->getEntity()->getIdFieldName()); } return $this->getIdFieldName(); } + /** + * Set row id field name + * @param string $fieldName + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ public function setRowIdFieldName($fieldName) { return $this->_setIdFieldName($fieldName); @@ -936,12 +1025,12 @@ public function setRowIdFieldName($fieldName) /** * Load entities records into items * + * @throws Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function _loadEntities($printQuery = false, $logQuery = false) { $entity = $this->getEntity(); -// $entityIdField = $entity->getEntityIdField(); if ($this->_pageSize) { $this->getSelect()->limitPage($this->getCurPage(), $this->_pageSize); @@ -950,10 +1039,15 @@ public function _loadEntities($printQuery = false, $logQuery = false) $this->printLogQuery($printQuery, $logQuery); try { - $rows = $this->_fetchAll($this->getSelect()); + /** + * Prepare select query + * @var string $query + */ + $query = $this->_prepareSelect($this->getSelect()); + $rows = $this->_fetchAll($query); } catch (Exception $e) { - Mage::printException($e, $this->getSelect()); - $this->printLogQuery(true, true, $this->getSelect()); + Mage::printException($e, $query); + $this->printLogQuery(true, true, $query); throw $e; } @@ -963,17 +1057,18 @@ public function _loadEntities($printQuery = false, $logQuery = false) $this->addItem($object); if (isset($this->_itemsById[$object->getId()])) { $this->_itemsById[$object->getId()][] = $object; - } - else { + } else { $this->_itemsById[$object->getId()] = array($object); } } + return $this; } /** * Load attributes into loaded entities * + * @throws Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ public function _loadAttributes($printQuery = false, $logQuery = false) @@ -983,9 +1078,9 @@ public function _loadAttributes($printQuery = false, $logQuery = false) } $entity = $this->getEntity(); - $entityIdField = $entity->getEntityIdField(); $tableAttributes = array(); + $attributeTypes = array(); foreach ($this->_selectAttributes as $attributeCode => $attributeId) { if (!$attributeId) { continue; @@ -993,27 +1088,39 @@ public function _loadAttributes($printQuery = false, $logQuery = false) $attribute = Mage::getSingleton('eav/config')->getCollectionAttribute($entity->getType(), $attributeCode); if ($attribute && !$attribute->isStatic()) { $tableAttributes[$attribute->getBackendTable()][] = $attributeId; + if (!isset($attributeTypes[$attribute->getBackendTable()])) { + $attributeTypes[$attribute->getBackendTable()] = $attribute->getBackendType(); + } } } $selects = array(); foreach ($tableAttributes as $table=>$attributes) { - $selects[] = $this->_getLoadAttributesSelect($table, $attributes); - } - if (!empty($selects)) { - try { - $select = implode(' UNION ', $selects); - $values = $this->_fetchAll($select); - } catch (Exception $e) { - Mage::printException($e, $select); - $this->printLogQuery(true, true, $select); - throw $e; - } + $select = $this->_getLoadAttributesSelect($table, $attributes); + $selects[$attributeTypes[$table]][] = $this->_addLoadAttributesSelectValues( + $select, + $table, + $attributeTypes[$table] + ); + } + $selectGroups = Mage::getResourceHelper('eav')->getLoadAttributesSelectGroups($selects); + foreach ($selectGroups as $selects) { + if (!empty($selects)) { + try { + $select = implode(' UNION ALL ', $selects); + $values = $this->getConnection()->fetchAll($select); + } catch (Exception $e) { + Mage::printException($e, $select); + $this->printLogQuery(true, true, $select); + throw $e; + } - foreach ($values as $value) { - $this->_setItemAttributeValue($value); + foreach ($values as $value) { + $this->_setItemAttributeValue($value); + } } } + return $this; } @@ -1023,17 +1130,34 @@ public function _loadAttributes($printQuery = false, $logQuery = false) * @param string $table * @return Mage_Eav_Model_Entity_Collection_Abstract */ - protected function _getLoadAttributesSelect($table, $attributeIds=array()) + protected function _getLoadAttributesSelect($table, $attributeIds = array()) { if (empty($attributeIds)) { $attributeIds = $this->_selectAttributes; } + $helper = Mage::getResourceHelper('eav'); $entityIdField = $this->getEntity()->getEntityIdField(); $select = $this->getConnection()->select() - ->from($table, array($entityIdField, 'attribute_id', 'value')) - ->where('entity_type_id=?', $this->getEntity()->getTypeId()) - ->where("$entityIdField in (?)", array_keys($this->_itemsById)) - ->where('attribute_id in (?)', $attributeIds); + ->from($table, array($entityIdField, 'attribute_id')) + ->where('entity_type_id =?', $this->getEntity()->getTypeId()) + ->where("$entityIdField IN (?)", array_keys($this->_itemsById)) + ->where('attribute_id IN (?)', $attributeIds); + return $select; + } + + /** + * @param Varien_Db_Select $select + * @param string $table + * @param string $type + * @return Varien_Db_Select + */ + protected function _addLoadAttributesSelectValues($select, $table, $type) + { + $helper = Mage::getResourceHelper('eav'); + $select->columns(array( + 'value' => $helper->prepareEavAttributeValue($table. '.value', $type), + )); + return $select; } @@ -1043,6 +1167,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds=array()) * $valueInfo is _getLoadAttributesSelect fetch result row * * @param array $valueInfo + * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ protected function _setItemAttributeValue($valueInfo) @@ -1050,8 +1175,8 @@ protected function _setItemAttributeValue($valueInfo) $entityIdField = $this->getEntity()->getEntityIdField(); $entityId = $valueInfo[$entityIdField]; if (!isset($this->_itemsById[$entityId])) { - Mage::throwException('Mage_Eav', - Mage::helper('eav')->__('Data integrity: No header row found for attribute.') + throw Mage::exception('Mage_Eav', + Mage::helper('eav')->__('Data integrity: No header row found for attribute') ); } $attributeCode = array_search($valueInfo['attribute_id'], $this->_selectAttributes); @@ -1066,6 +1191,7 @@ protected function _setItemAttributeValue($valueInfo) foreach ($this->_itemsById[$entityId] as $object) { $object->setData($attributeCode, $valueInfo['value']); } + return $this; } @@ -1077,11 +1203,18 @@ protected function _setItemAttributeValue($valueInfo) */ protected function _getAttributeTableAlias($attributeCode) { - return '_table_'.$attributeCode; + return 'at_' . $attributeCode; } + /** + * Retreive attribute field name by attribute code + * + * @param string $attributeCode + * @return string + */ protected function _getAttributeFieldName($attributeCode) { + $attributeCode = trim($attributeCode); if (isset($this->_joinAttributes[$attributeCode]['condition_alias'])) { return $this->_joinAttributes[$attributeCode]['condition_alias']; } @@ -1090,23 +1223,24 @@ protected function _getAttributeFieldName($attributeCode) } if (isset($this->_joinFields[$attributeCode])) { $attr = $this->_joinFields[$attributeCode]; - return $attr['table'] ? $attr['table'] .'.'.$attr['field'] : $attr['field']; + return $attr['table'] ? $attr['table'] . '.' . $attr['field'] : $attr['field']; } $attribute = $this->getAttribute($attributeCode); if (!$attribute) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s.', $attributeCode)); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s', $attributeCode)); } if ($attribute->isStatic()) { if (isset($this->_joinAttributes[$attributeCode])) { - $fieldName = $this->_getAttributeTableAlias($attributeCode).'.'.$attributeCode; + $fieldName = $this->_getAttributeTableAlias($attributeCode) . '.' . $attributeCode; } else { - $fieldName = 'e.'.$attributeCode; + $fieldName = 'e.' . $attributeCode; } } else { - $fieldName = $this->_getAttributeTableAlias($attributeCode).'.value'; + $fieldName = $this->_getAttributeTableAlias($attributeCode) . '.value'; } + return $fieldName; } @@ -1115,14 +1249,17 @@ protected function _getAttributeFieldName($attributeCode) * * @param string $attributeCode * @param string $joinType inner|left + * @throws Mage_Eav_Exception * @return Mage_Eav_Model_Entity_Collection_Abstract */ - protected function _addAttributeJoin($attributeCode, $joinType='inner') + protected function _addAttributeJoin($attributeCode, $joinType = 'inner') { if (!empty($this->_filterAttributes[$attributeCode])) { return $this; } + $adapter = $this->getConnection(); + $attrTable = $this->_getAttributeTableAlias($attributeCode); if (isset($this->_joinAttributes[$attributeCode])) { $attribute = $this->_joinAttributes[$attributeCode]['attribute']; @@ -1134,36 +1271,40 @@ protected function _addAttributeJoin($attributeCode, $joinType='inner') if ($fkAttribute->getBackend()->isStatic()) { if (isset($this->_joinAttributes[$fkName])) { - $fk = $fkTable.".".$fkAttribute->getAttributeCode(); + $fk = $fkTable . '.' . $fkAttribute->getAttributeCode(); } else { - $fk = "e.".$fkAttribute->getAttributeCode(); + $fk = 'e.' . $fkAttribute->getAttributeCode(); } } else { $this->_addAttributeJoin($fkAttribute->getAttributeCode(), $joinType); - $fk = "$fkTable.value"; + $fk = $fkTable . '.value'; } - $pk = $attrTable.'.'.$this->_joinAttributes[$attributeCode]['filter']; + $pk = $attrTable . '.' . $this->_joinAttributes[$attributeCode]['filter']; } else { $entity = $this->getEntity(); $entityIdField = $entity->getEntityIdField(); $attribute = $entity->getAttribute($attributeCode); - $fk = "e.$entityIdField"; - $pk = "$attrTable.$entityIdField"; + $fk = 'e.' . $entityIdField; + $pk = $attrTable . '.' . $entityIdField; } if (!$attribute) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s.', $attributeCode)); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Invalid attribute name: %s', $attributeCode)); } if ($attribute->getBackend()->isStatic()) { - $attrFieldName = "$attrTable.".$attribute->getAttributeCode(); + $attrFieldName = $attrTable . '.' . $attribute->getAttributeCode(); } else { - $attrFieldName = "$attrTable.value"; + $attrFieldName = $attrTable . '.value'; } + $fk = $adapter->quoteColumnAs($fk, null); + $pk = $adapter->quoteColumnAs($pk, null); + $condArr = array("$pk = $fk"); if (!$attribute->getBackend()->isStatic()) { - $condArr[] = $this->getConnection()->quoteInto("$attrTable.attribute_id=?", $attribute->getId()); + $condArr[] = $this->getConnection()->quoteInto( + $adapter->quoteColumnAs("$attrTable.attribute_id", null) . ' = ?', $attribute->getId()); } /** @@ -1202,8 +1343,8 @@ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $con { $this->getSelect()->$method( array($tableAlias => $attribute->getBackend()->getTable()), - '('.join(') AND (', $condition).')', - array($fieldCode=>$fieldAlias) + '('.implode(') AND (', $condition).')', + array($fieldCode => $fieldAlias) ); return $this; } @@ -1217,35 +1358,41 @@ protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $con * @param string $joinType * @return string */ - protected function _getAttributeConditionSql($attribute, $condition, $joinType='inner') + protected function _getAttributeConditionSql($attribute, $condition, $joinType = 'inner') { if (isset($this->_joinFields[$attribute])) { + return $this->_getConditionSql($this->_getAttributeFieldName($attribute), $condition); } if (isset($this->_staticFields[$attribute])) { - return $this->_getConditionSql(sprintf('e.%s', $attribute), $condition); + return $this->_getConditionSql($this->getConnection()->quoteIdentifier('e.' . $attribute), $condition); } // process linked attribute if (isset($this->_joinAttributes[$attribute])) { - $entity = $this->getAttribute($attribute)->getEntity(); + $entity = $this->getAttribute($attribute)->getEntity(); $entityTable = $entity->getEntityTable(); } else { - $entity = $this->getEntity(); + $entity = $this->getEntity(); $entityTable = 'e'; } if ($entity->isAttributeStatic($attribute)) { - $conditionSql = $this->_getConditionSql('e.'.$attribute, $condition); + $conditionSql = $this->_getConditionSql( + $this->getConnection()->quoteIdentifier('e.' . $attribute), + $condition + ); } else { $this->_addAttributeJoin($attribute, $joinType); if (isset($this->_joinAttributes[$attribute]['condition_alias'])) { $field = $this->_joinAttributes[$attribute]['condition_alias']; + } else { + $field = $this->_getAttributeTableAlias($attribute) . '.value'; + } - else { - $field = $this->_getAttributeTableAlias($attribute).'.value'; - } + $conditionSql = $this->_getConditionSql($field, $condition); } + return $conditionSql; } @@ -1258,33 +1405,52 @@ protected function _getAttributeConditionSql($attribute, $condition, $joinType=' * @param string $dir * @return Mage_Eav_Model_Entity_Collection_Abstract */ - public function setOrder($attribute, $dir='desc') + public function setOrder($attribute, $dir = self::SORT_ORDER_ASC) { if (is_array($attribute)) { foreach ($attribute as $attr) { - $this->addAttributeToSort($attr, $dir); + parent::setOrder($attr, $dir); } - } else { - $this->addAttributeToSort($attribute, $dir); } - return $this; + return parent::setOrder($attribute, $dir); } - + /** + * Retreive array of attributes + * + * @param array $arrAttributes + * @return array + */ public function toArray($arrAttributes = array()) { $arr = array(); - foreach ($this->_items as $k=>$item) { + foreach ($this->_items as $k => $item) { $arr[$k] = $item->toArray($arrAttributes); } return $arr; } - protected function _beforeLoad() + /** + * Treat "order by" items as attributes to sort + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ + protected function _renderOrders() { + if (!$this->_isOrdersRendered) { + foreach ($this->_orders as $attribute => $direction) { + $this->addAttributeToSort($attribute, $direction); + } + $this->_isOrdersRendered = true; + } return $this; } + /** + * After load method + * + * @return Mage_Eav_Model_Entity_Collection_Abstract + */ protected function _afterLoad() { return $this; @@ -1300,11 +1466,11 @@ protected function _reset() parent::_reset(); $this->_selectEntityTypes = array(); - $this->_selectAttributes = array(); - $this->_filterAttributes = array(); - $this->_joinEntities = array(); - $this->_joinAttributes = array(); - $this->_joinFields = array(); + $this->_selectAttributes = array(); + $this->_filterAttributes = array(); + $this->_joinEntities = array(); + $this->_joinAttributes = array(); + $this->_joinFields = array(); return $this; } @@ -1318,4 +1484,20 @@ public function getLoadedIds() { return array_keys($this->_items); } + + /** + * Prepare select for load + * + * @param Varien_Db_Select $select OPTIONAL + * @return string + */ + public function _prepareSelect(Varien_Db_Select $select) + { + if ($this->_useAnalyticFunction) { + $helper = Mage::getResourceHelper('core'); + return $helper->getQueryUsingAnalyticFunction($select); + } + + return (string)$select; + } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Increment/Abstract.php b/app/code/core/Mage/Eav/Model/Entity/Increment/Abstract.php index ca1e59c1..b4328a9b 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Increment/Abstract.php +++ b/app/code/core/Mage/Eav/Model/Entity/Increment/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,8 +34,7 @@ * - pad_char * - last_id */ -abstract class Mage_Eav_Model_Entity_Increment_Abstract - extends Varien_Object +abstract class Mage_Eav_Model_Entity_Increment_Abstract extends Varien_Object implements Mage_Eav_Model_Entity_Increment_Interface { public function getPadLength() @@ -46,7 +45,7 @@ public function getPadLength() } return $padLength; } - + public function getPadChar() { $padChar = $this->getData('pad_char'); @@ -55,17 +54,16 @@ public function getPadChar() } return $padChar; } - + public function format($id) { $result = $this->getPrefix(); $result.= str_pad((string)$id, $this->getPadLength(), $this->getPadChar(), STR_PAD_LEFT); return $result; } - + public function frontendFormat($id) { - $result = $id; - return $result; + return $id; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Increment/Alphanum.php b/app/code/core/Mage/Eav/Model/Entity/Increment/Alphanum.php index 81ae7444..654ea528 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Increment/Alphanum.php +++ b/app/code/core/Mage/Eav/Model/Entity/Increment/Alphanum.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,30 +34,29 @@ * - pad_char * - last_id */ -class Mage_Eav_Model_Entity_Increment_Alphanum - extends Mage_Eav_Model_Entity_Increment_Abstract +class Mage_Eav_Model_Entity_Increment_Alphanum extends Mage_Eav_Model_Entity_Increment_Abstract { public function getAllowedChars() { return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; } - + public function getNextId() { $lastId = $this->getLastId(); - + if (strpos($lastId, $this->getPrefix())===0) { $lastId = substr($lastId, strlen($this->getPrefix())); } - + $lastId = str_pad((string)$lastId, $this->getPadLength(), $this->getPadChar(), STR_PAD_LEFT); - + $nextId = ''; $bumpNextChar = true; $chars = $this->getAllowedChars(); $lchars = strlen($chars); $lid = strlen($lastId)-1; - + for ($i = $lid; $i >= 0; $i--) { $p = strpos($chars, $lastId{$i}); if (false===$p) { @@ -73,7 +72,7 @@ public function getNextId() } $nextId = $chars{$p}.$nextId; } - + return $this->format($nextId); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Increment/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Increment/Interface.php index ef51d8dd..e190c60d 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Increment/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Increment/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/Model/Entity/Increment/Numeric.php b/app/code/core/Mage/Eav/Model/Entity/Increment/Numeric.php index 99e29b4f..d4cfd0ba 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Increment/Numeric.php +++ b/app/code/core/Mage/Eav/Model/Entity/Increment/Numeric.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,21 +34,20 @@ * - pad_char * - last_id */ -class Mage_Eav_Model_Entity_Increment_Numeric - extends Mage_Eav_Model_Entity_Increment_Abstract +class Mage_Eav_Model_Entity_Increment_Numeric extends Mage_Eav_Model_Entity_Increment_Abstract { public function getNextId() { $last = $this->getLastId(); - - if (strpos($last, $this->getPrefix())===0) { + + if (strpos($last, $this->getPrefix()) === 0) { $last = (int)substr($last, strlen($this->getPrefix())); } else { $last = (int)$last; } - + $next = $last+1; - + return $this->format($next); } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Interface.php b/app/code/core/Mage/Eav/Model/Entity/Interface.php index 75728070..92323ee2 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Interface.php +++ b/app/code/core/Mage/Eav/Model/Entity/Interface.php @@ -20,12 +20,11 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ interface Mage_Eav_Model_Entity_Interface { - } diff --git a/app/code/core/Mage/Eav/Model/Entity/Setup.php b/app/code/core/Mage/Eav/Model/Entity/Setup.php index baae8ca5..e2201b80 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Setup.php +++ b/app/code/core/Mage/Eav/Model/Entity/Setup.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,10 +34,35 @@ */ class Mage_Eav_Model_Entity_Setup extends Mage_Core_Model_Resource_Setup { - protected $_attributeTableFields; - protected $_generalGroupName = 'General'; + /** + * General Attribute Group Name + * + * @var string + */ + protected $_generalGroupName = 'General'; + + /** + * Default attribute group name to id pairs + * + * @var array + */ + public $defaultGroupIdAssociations = array( + 'General' => 1 + ); - public $defaultGroupIdAssociations = array('General'=>1); + /** + * Default attribute group name + * + * @var string + */ + protected $_defaultGroupName = 'Default'; + + /** + * Default attribute set name + * + * @var string + */ + protected $_defaultAttributeSetName = 'Default'; /** * Clean cache @@ -58,7 +83,7 @@ public function cleanCache() public function installDefaultGroupIds() { $setIds = $this->getAllAttributeSetIds(); - foreach ($this->defaultGroupIdAssociations as $defaultGroupName=>$defaultGroupId) { + foreach ($this->defaultGroupIdAssociations as $defaultGroupName => $defaultGroupId) { foreach ($setIds as $set) { $groupId = $this->getTableRow('eav/attribute_group', 'attribute_group_name', $defaultGroupName, 'attribute_group_id', 'attribute_set_id', $set @@ -69,11 +94,11 @@ public function installDefaultGroupIds() ); } $this->updateTableRow('eav/attribute_group', - 'attribute_group_id', $groupId, - 'default_id', $defaultGroupId + 'attribute_group_id', $groupId, 'default_id', $defaultGroupId ); } } + return $this; } @@ -92,14 +117,18 @@ public function installDefaultGroupIds() public function addEntityType($code, array $params) { $data = array( - 'entity_type_code' => $code, - 'entity_model' => $params['entity_model'], - 'attribute_model' => isset($params['attribute_model']) ? $params['attribute_model'] : '', - 'entity_table' => isset($params['table']) ? $params['table'] : 'eav/entity', - 'increment_model' => isset($params['increment_model']) ? $params['increment_model'] : '', - 'increment_per_store' => isset($params['increment_per_store']) ? $params['increment_per_store'] : 0, - 'additional_attribute_table' => isset($params['additional_attribute_table']) ? $params['additional_attribute_table'] : '', - 'entity_attribute_collection'=> isset($params['entity_attribute_collection']) ? $params['entity_attribute_collection'] : '', + 'entity_type_code' => $code, + 'entity_model' => $params['entity_model'], + 'attribute_model' => $this->_getValue($params, 'attribute_model'), + 'entity_table' => $this->_getValue($params, 'table', 'eav/entity'), + 'value_table_prefix' => $this->_getValue($params, 'table_prefix'), + 'entity_id_field' => $this->_getValue($params, 'id_field'), + 'increment_model' => $this->_getValue($params, 'increment_model'), + 'increment_per_store' => $this->_getValue($params, 'increment_per_store', 0), + 'increment_pad_length' => $this->_getValue($params, 'increment_pad_length', 8), + 'increment_pad_char' => $this->_getValue($params, 'increment_pad_char', 0), + 'additional_attribute_table' => $this->_getValue($params, 'additional_attribute_table'), + 'entity_attribute_collection' => $this->_getValue($params, 'entity_attribute_collection'), ); if ($this->getEntityType($code, 'entity_type_id')) { @@ -108,8 +137,14 @@ public function addEntityType($code, array $params) $this->_conn->insert($this->getTable('eav/entity_type'), $data); } - $this->addAttributeSet($code, 'Default'); - $this->addAttributeGroup($code, 'Default', $this->_generalGroupName); + if (!empty($params['default_group'])) { + $defaultGroup = $params['default_group']; + } else { + $defaultGroup = $this->_defaultGroupName; + } + + $this->addAttributeSet($code, $this->_defaultAttributeSetName); + $this->addAttributeGroup($code, $this->_defaultGroupName, $this->_generalGroupName); return $this; } @@ -122,11 +157,10 @@ public function addEntityType($code, array $params) * @param string $value * @return Mage_Eav_Model_Entity_Setup */ - public function updateEntityType($code, $field, $value=null) + public function updateEntityType($code, $field, $value = null) { $this->updateTableRow('eav/entity_type', - 'entity_type_id', $this->getEntityTypeId($code), - $field, $value + 'entity_type_id', $this->getEntityTypeId($code), $field, $value ); return $this; } @@ -138,11 +172,10 @@ public function updateEntityType($code, $field, $value=null) * @param string $field * @return mixed */ - public function getEntityType($id, $field=null) + public function getEntityType($id, $field = null) { return $this->getTableRow('eav/entity_type', - is_numeric($id) ? 'entity_type_id' : 'entity_type_code', $id, - $field + is_numeric($id) ? 'entity_type_id' : 'entity_type_code', $id, $field ); } @@ -158,8 +191,9 @@ public function getEntityTypeId($entityTypeId) $entityTypeId = $this->getEntityType($entityTypeId, 'entity_type_id'); } if (!is_numeric($entityTypeId)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong entity ID.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong entity ID')); } + return $entityTypeId; } @@ -173,10 +207,10 @@ public function removeEntityType($id) { if (is_numeric($id)) { $this->deleteTableRow('eav/entity_type', 'entity_type_id', $id); - } - else { + } else { $this->deleteTableRow('eav/entity_type', 'entity_type_code', (string)$id); } + return $this; } @@ -189,15 +223,17 @@ public function removeEntityType($id) * @param int $sortOrder * @return int */ - public function getAttributeSetSortOrder($entityTypeId, $sortOrder=null) + public function getAttributeSetSortOrder($entityTypeId, $sortOrder = null) { if (!is_numeric($sortOrder)) { - $sortOrder = $this->_conn->fetchOne("select max(sort_order) - from ".$this->getTable('eav/attribute_set')." - where entity_type_id=".$this->getEntityTypeId($entityTypeId) - ); - $sortOrder++; + $bind = array('entity_type_id' => $this->getEntityTypeId($entityTypeId)); + $select = $this->_conn->select() + ->from($this->getTable('eav/attribute_set'), 'MAX(sort_order)') + ->where('entity_type_id = :entity_type_id'); + + $sortOrder = $this->_conn->fetchOne($select, $bind) + 1; } + return $sortOrder; } @@ -209,16 +245,17 @@ public function getAttributeSetSortOrder($entityTypeId, $sortOrder=null) * @param int $sortOrder * @return Mage_Eav_Model_Entity_Setup */ - public function addAttributeSet($entityTypeId, $name, $sortOrder=null) + public function addAttributeSet($entityTypeId, $name, $sortOrder = null) { $data = array( - 'entity_type_id'=>$this->getEntityTypeId($entityTypeId), - 'attribute_set_name'=>$name, - 'sort_order'=>$this->getAttributeSetSortOrder($entityTypeId, $sortOrder), + 'entity_type_id' => $this->getEntityTypeId($entityTypeId), + 'attribute_set_name' => $name, + 'sort_order' => $this->getAttributeSetSortOrder($entityTypeId, $sortOrder), ); - if ($id = $this->getAttributeSet($entityTypeId, $name, 'attribute_set_id')) { - $this->updateAttributeSet($entityTypeId, $id, $data); + $setId = $this->getAttributeSet($entityTypeId, $name, 'attribute_set_id'); + if ($setId) { + $this->updateAttributeSet($entityTypeId, $setId, $data); } else { $this->_conn->insert($this->getTable('eav/attribute_set'), $data); @@ -237,7 +274,7 @@ public function addAttributeSet($entityTypeId, $name, $sortOrder=null) * @param mixed $value * @return Mage_Eav_Model_Entity_Setup */ - public function updateAttributeSet($entityTypeId, $id, $field, $value=null) + public function updateAttributeSet($entityTypeId, $id, $field, $value = null) { $this->updateTableRow('eav/attribute_set', 'attribute_set_id', $this->getAttributeSetId($entityTypeId, $id), @@ -255,7 +292,7 @@ public function updateAttributeSet($entityTypeId, $id, $field, $value=null) * @param string $field * @return mixed */ - public function getAttributeSet($entityTypeId, $id, $field=null) + public function getAttributeSet($entityTypeId, $id, $field = null) { return $this->getTableRow('eav/attribute_set', is_numeric($id) ? 'attribute_set_id' : 'attribute_set_name', $id, @@ -278,8 +315,9 @@ public function getAttributeSetId($entityTypeId, $setId) $setId = $this->getAttributeSet($entityTypeId, $setId, 'attribute_set_id'); } if (!is_numeric($setId)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong attribute set ID.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong attribute set ID')); } + return $setId; } @@ -305,7 +343,7 @@ public function removeAttributeSet($entityTypeId, $id) public function setDefaultSetToEntityType($entityType, $attributeSet = 'Default') { $entityTypeId = $this->getEntityTypeId($entityType); - $setId = $this->getAttributeSetId($entityTypeId, $attributeSet); + $setId = $this->getAttributeSetId($entityTypeId, $attributeSet); $this->updateEntityType($entityTypeId, 'default_attribute_set_id', $setId); return $this; } @@ -315,14 +353,18 @@ public function setDefaultSetToEntityType($entityType, $attributeSet = 'Default' * * @return array */ - public function getAllAttributeSetIds($entityTypeId=null) + public function getAllAttributeSetIds($entityTypeId = null) { - $where = ''; - if (!is_null($entityTypeId)) { - $where = " WHERE `entity_type_id` = '" . $this->getEntityTypeId($entityTypeId) . "'"; + $select = $this->_conn->select() + ->from($this->getTable('eav/attribute_set'), 'attribute_set_id'); + + $bind = array(); + if ($entityTypeId !== null) { + $bind['entity_type_id'] = $this->getEntityTypeId($entityTypeId); + $select->where('entity_type_id = :entity_type_id'); } - $sql = "SELECT `attribute_set_id` FROM `{$this->getTable('eav/attribute_set')}`" . $where; - return $this->_conn->fetchCol($sql); + + return $this->_conn->fetchCol($select, $bind); } /** @@ -333,10 +375,17 @@ public function getAllAttributeSetIds($entityTypeId=null) */ public function getDefaultAttributeSetId($entityType) { + $bind = array('entity_type' => $entityType); + if (is_numeric($entityType)) { + $where = 'entity_type_id = :entity_type'; + } else { + $where = 'entity_type_code = :entity_type'; + } $select = $this->getConnection()->select() ->from($this->getTable('eav/entity_type'), 'default_attribute_set_id') - ->where(is_numeric($entityType) ? 'entity_type_id=?' : 'entity_type_code=?', $entityType); - return $this->getConnection()->fetchOne($select); + ->where($where); + + return $this->getConnection()->fetchOne($select, $bind); } /******************* ATTRIBUTE GROUPS *****************/ @@ -349,15 +398,17 @@ public function getDefaultAttributeSetId($entityType) * @param int $sortOrder * @return int */ - public function getAttributeGroupSortOrder($entityTypeId, $setId, $sortOrder=null) + public function getAttributeGroupSortOrder($entityTypeId, $setId, $sortOrder = null) { if (!is_numeric($sortOrder)) { - $sortOrder = $this->_conn->fetchOne("select max(sort_order) - from ".$this->getTable('eav/attribute_group')." - where attribute_set_id=".$this->getAttributeSetId($entityTypeId, $setId) - ); - $sortOrder++; + $bind = array('attribute_set_id' => $this->getAttributeSetId($entityTypeId, $setId)); + $select = $this->_conn->select() + ->from($this->getTable('eav/attribute_group'), 'MAX(sort_order)') + ->where('attribute_set_id = :attribute_set_id'); + + $sortOrder = $this->_conn->fetchOne($select, $bind) + 1; } + return $sortOrder; } @@ -370,24 +421,27 @@ public function getAttributeGroupSortOrder($entityTypeId, $setId, $sortOrder=nul * @param int $sortOrder * @return Mage_Eav_Model_Entity_Setup */ - public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder=null) + public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = null) { - $setId = $this->getAttributeSetId($entityTypeId, $setId); - $data = array( - 'attribute_set_id'=>$setId, - 'attribute_group_name'=>$name, + $setId = $this->getAttributeSetId($entityTypeId, $setId); + $data = array( + 'attribute_set_id' => $setId, + 'attribute_group_name' => $name, ); + if (isset($this->defaultGroupIdAssociations[$name])) { $data['default_id'] = $this->defaultGroupIdAssociations[$name]; } - if (!is_null($sortOrder)) { + + if ($sortOrder !== null) { $data['sort_order'] = $sortOrder; } - if ($id = $this->getAttributeGroup($entityTypeId, $setId, $name, 'attribute_group_id')) { - $this->updateAttributeGroup($entityTypeId, $setId, $id, $data); + $groupId = $this->getAttributeGroup($entityTypeId, $setId, $name, 'attribute_group_id'); + if ($groupId) { + $this->updateAttributeGroup($entityTypeId, $setId, $groupId, $data); } else { - if (is_null($sortOrder)) { + if ($sortOrder === null) { $data['sort_order'] = $this->getAttributeGroupSortOrder($entityTypeId, $setId, $sortOrder); } $this->_conn->insert($this->getTable('eav/attribute_group'), $data); @@ -406,13 +460,14 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder=null) * @param mixed $value * @return Mage_Eav_Model_Entity_Setup */ - public function updateAttributeGroup($entityTypeId, $setId, $id, $field, $value=null) + public function updateAttributeGroup($entityTypeId, $setId, $id, $field, $value = null) { $this->updateTableRow('eav/attribute_group', 'attribute_group_id', $this->getAttributeGroupId($entityTypeId, $setId, $id), $field, $value, 'attribute_set_id', $this->getAttributeSetId($entityTypeId, $setId) ); + return $this; } @@ -425,7 +480,7 @@ public function updateAttributeGroup($entityTypeId, $setId, $id, $field, $value= * @param string $field * @return mixed */ - public function getAttributeGroup($entityTypeId, $setId, $id, $field=null) + public function getAttributeGroup($entityTypeId, $setId, $id, $field = null) { $searchId = $id; if (is_numeric($id)) { @@ -464,7 +519,7 @@ public function getAttributeGroupId($entityTypeId, $setId, $groupId) } if (!is_numeric($groupId)) { - throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong attribute group ID.')); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong attribute group ID')); } return $groupId; } @@ -500,13 +555,14 @@ public function getDefaultAttributeGroupId($entityType, $attributeSetId = null) if (!is_numeric($attributeSetId)) { $attributeSetId = $this->getDefaultAttributeSetId($entityType); } - + $bind = array('attribute_set_id' => $attributeSetId); $select = $this->getConnection()->select() ->from($this->getTable('eav/attribute_group'), 'attribute_group_id') - ->where('attribute_set_id=?', $attributeSetId) - ->order('default_id DESC, sort_order') + ->where('attribute_set_id = :attribute_set_id') + ->order(array('default_id ' . Varien_Db_Select::SQL_DESC, 'sort_order')) ->limit(1); - return $this->getConnection()->fetchOne($select); + + return $this->getConnection()->fetchOne($select, $bind); } /******************* ATTRIBUTES *****************/ @@ -521,6 +577,9 @@ public function getDefaultAttributeGroupId($entityType, $attributeSetId = null) */ protected function _getValue($array, $key, $default = null) { + if (isset($array[$key]) && is_bool($array[$key])) { + $array[$key] = (int) $array[$key]; + } return isset($array[$key]) ? $array[$key] : $default; } @@ -532,25 +591,50 @@ protected function _getValue($array, $key, $default = null) */ protected function _prepareValues($attr) { - $data = array(); $data = array( - 'backend_model' => $this->_getValue($attr, 'backend', ''), - 'backend_type' => $this->_getValue($attr, 'type', 'varchar'), - 'backend_table' => $this->_getValue($attr, 'table', ''), - 'frontend_model' => $this->_getValue($attr, 'frontend', ''), - 'frontend_input' => $this->_getValue($attr, 'input', 'text'), - 'frontend_label' => $this->_getValue($attr, 'label', ''), - 'frontend_class' => $this->_getValue($attr, 'frontend_class', ''), - 'source_model' => $this->_getValue($attr, 'source', ''), - 'is_required' => $this->_getValue($attr, 'required', 1), - 'is_user_defined' => $this->_getValue($attr, 'user_defined', 0), - 'default_value' => $this->_getValue($attr, 'default', ''), - 'is_unique' => $this->_getValue($attr, 'unique', 0), - 'note' => $this->_getValue($attr, 'note', ''), + 'backend_model' => $this->_getValue($attr, 'backend'), + 'backend_type' => $this->_getValue($attr, 'type', 'varchar'), + 'backend_table' => $this->_getValue($attr, 'table'), + 'frontend_model' => $this->_getValue($attr, 'frontend'), + 'frontend_input' => $this->_getValue($attr, 'input', 'text'), + 'frontend_label' => $this->_getValue($attr, 'label'), + 'frontend_class' => $this->_getValue($attr, 'frontend_class'), + 'source_model' => $this->_getValue($attr, 'source'), + 'is_required' => $this->_getValue($attr, 'required', 1), + 'is_user_defined' => $this->_getValue($attr, 'user_defined', 0), + 'default_value' => $this->_getValue($attr, 'default'), + 'is_unique' => $this->_getValue($attr, 'unique', 0), + 'note' => $this->_getValue($attr, 'note'), + 'is_global' => $this->_getValue($attr, 'global', + Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_GLOBAL + ), ); + return $data; } + /** + * Validate attribute data before insert into table + * + * @param array $data + * @throws Mage_Eav_Exception + * @return true + */ + protected function _validateAttributeData($data) + { + $attributeCodeMaxLength = Mage_Eav_Model_Entity_Attribute::ATTRIBUTE_CODE_MAX_LENGTH; + + if (isset($data['attribute_code']) && + !Zend_Validate::is($data['attribute_code'], 'StringLength', array('max' => $attributeCodeMaxLength))) + { + throw Mage::exception('Mage_Eav', + Mage::helper('eav')->__('Maximum length of attribute code must be less then %s symbols', $attributeCodeMaxLength) + ); + } + + return true; + } + /** * Add attribute to an entity type * @@ -572,24 +656,31 @@ public function addAttribute($entityTypeId, $code, array $attr) $this->_prepareValues($attr) ); + $this->_validateAttributeData($data); + $sortOrder = isset($attr['sort_order']) ? $attr['sort_order'] : null; - if ($id = $this->getAttribute($entityTypeId, $code, 'attribute_id')) { - $this->updateAttribute($entityTypeId, $id, $data, null, $sortOrder); + $attributeId = $this->getAttribute($entityTypeId, $code, 'attribute_id'); + if ($attributeId) { + $this->updateAttribute($entityTypeId, $attributeId, $data, null, $sortOrder); } else { $this->_insertAttribute($data); } - if (!empty($attr['group'])) { - $sets = $this->_conn->fetchAll('select * from '.$this->getTable('eav/attribute_set').' where entity_type_id=?', $entityTypeId); - foreach ($sets as $set) { - $this->addAttributeGroup($entityTypeId, $set['attribute_set_id'], $attr['group']); - $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], $attr['group'], $code, $sortOrder); - } - } - if (empty($attr['user_defined'])) { - $sets = $this->_conn->fetchAll('select * from '.$this->getTable('eav/attribute_set').' where entity_type_id=?', $entityTypeId); + if (!empty($attr['group']) || empty($attr['user_defined'])) { + $select = $this->_conn->select() + ->from($this->getTable('eav/attribute_set')) + ->where('entity_type_id = :entity_type_id'); + $sets = $this->_conn->fetchAll($select, array('entity_type_id' => $entityTypeId)); foreach ($sets as $set) { - $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], $this->_generalGroupName, $code, $sortOrder); + if (!empty($attr['group'])) { + $this->addAttributeGroup($entityTypeId, $set['attribute_set_id'], + $attr['group']); + $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], + $attr['group'], $code, $sortOrder); + } else { + $this->addAttributeToSet($entityTypeId, $set['attribute_set_id'], + $this->_generalGroupName, $code, $sortOrder); + } } } @@ -609,15 +700,15 @@ public function addAttribute($entityTypeId, $code, array $attr) */ public function addAttributeOption($option) { - if (isset($option['value'])) { - $optionTable = $this->getTable('eav/attribute_option'); - $optionValueTable = $this->getTable('eav/attribute_option_value'); + $optionTable = $this->getTable('eav/attribute_option'); + $optionValueTable = $this->getTable('eav/attribute_option_value'); + if (isset($option['value'])) { foreach ($option['value'] as $optionId => $values) { $intOptionId = (int) $optionId; if (!empty($option['delete'][$optionId])) { if ($intOptionId) { - $condition = $this->_conn->quoteInto('option_id=?', $intOptionId); + $condition = array('option_id =?' => $intOptionId); $this->_conn->delete($optionTable, $condition); } continue; @@ -629,20 +720,20 @@ public function addAttributeOption($option) 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, ); $this->_conn->insert($optionTable, $data); - $intOptionId = $this->_conn->lastInsertId(); + $intOptionId = $this->_conn->lastInsertId($optionTable); } else { $data = array( 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, ); - $this->_conn->update($optionTable, $data, $this->_conn->quoteInto('option_id=?', $intOptionId)); + $this->_conn->update($optionTable, $data, array('option_id=?' => $intOptionId)); } // Default value if (!isset($values[0])) { - Mage::throwException(Mage::helper('eav')->__('Default option value is not defined.')); + Mage::throwException(Mage::helper('eav')->__('Default option value is not defined')); } - - $this->_conn->delete($optionValueTable, $this->_conn->quoteInto('option_id=?', $intOptionId)); + $condition = array('option_id =?' => $intOptionId); + $this->_conn->delete($optionValueTable, $condition); foreach ($values as $storeId => $value) { $data = array( 'option_id' => $intOptionId, @@ -652,6 +743,23 @@ public function addAttributeOption($option) $this->_conn->insert($optionValueTable, $data); } } + } else if (isset($option['values'])) { + foreach ($option['values'] as $sortOrder => $label) { + // add option + $data = array( + 'attribute_id' => $option['attribute_id'], + 'sort_order' => $sortOrder, + ); + $this->_conn->insert($optionTable, $data); + $intOptionId = $this->_conn->lastInsertId($optionTable); + + $data = array( + 'option_id' => $intOptionId, + 'store_id' => 0, + 'value' => $label, + ); + $this->_conn->insert($optionValueTable, $data); + } } } @@ -665,7 +773,7 @@ public function addAttributeOption($option) * @param int $sortOrder * @return Mage_Eav_Model_Entity_Setup */ - public function updateAttribute($entityTypeId, $id, $field, $value=null, $sortOrder=null) + public function updateAttribute($entityTypeId, $id, $field, $value = null, $sortOrder = null) { $this->_updateAttribute($entityTypeId, $id, $field, $value, $sortOrder); $this->_updateAttributeAdditionalData($entityTypeId, $id, $field, $value); @@ -682,9 +790,9 @@ public function updateAttribute($entityTypeId, $id, $field, $value=null, $sortOr * @param int $sortOrder * @return Mage_Eav_Model_Entity_Setup */ - protected function _updateAttribute($entityTypeId, $id, $field, $value=null, $sortOrder=null) + protected function _updateAttribute($entityTypeId, $id, $field, $value = null, $sortOrder = null) { - if (!is_null($sortOrder)) { + if ($sortOrder !== null) { $this->updateTableRow('eav/entity_attribute', 'attribute_id', $this->getAttributeId($entityTypeId, $id), 'sort_order', $sortOrder @@ -696,15 +804,14 @@ protected function _updateAttribute($entityTypeId, $id, $field, $value=null, $so $bind = array(); foreach ($field as $k => $v) { if (isset($attributeFields[$k])) { - $bind[$k] = $v; + $bind[$k] = $this->getConnection()->prepareColumnValue($attributeFields[$k], $v); } } if (!$bind) { return $this; } $field = $bind; - } - else { + } else { if (!isset($attributeFields[$field])) { return $this; } @@ -715,6 +822,7 @@ protected function _updateAttribute($entityTypeId, $id, $field, $value=null, $so $field, $value, 'entity_type_id', $this->getEntityTypeId($entityTypeId) ); + return $this; } @@ -727,28 +835,27 @@ protected function _updateAttribute($entityTypeId, $id, $field, $value=null, $so * @param mixed $value * @return Mage_Eav_Model_Entity_Setup */ - protected function _updateAttributeAdditionalData($entityTypeId, $id, $field, $value=null) + protected function _updateAttributeAdditionalData($entityTypeId, $id, $field, $value = null) { $additionalTable = $this->getEntityType($entityTypeId, 'additional_attribute_table'); if (!$additionalTable) { return $this; } - $additionalTableExists = $this->getConnection()->showTableStatus($this->getTable($additionalTable)); + $additionalTableExists = $this->getConnection()->isTableExists($this->getTable($additionalTable)); if ($additionalTable && $additionalTableExists) { $attributeFields = $this->getConnection()->describeTable($this->getTable($additionalTable)); if (is_array($field)) { $bind = array(); foreach ($field as $k => $v) { if (isset($attributeFields[$k])) { - $bind[$k] = $v; + $bind[$k] = $this->getConnection()->prepareColumnValue($attributeFields[$k], $v); } } if (!$bind) { return $this; } $field = $bind; - } - else { + } else { if (!isset($attributeFields[$field])) { return $this; } @@ -758,6 +865,7 @@ protected function _updateAttributeAdditionalData($entityTypeId, $id, $field, $v $field, $value ); } + return $this; } @@ -773,7 +881,7 @@ public function getAttribute($entityTypeId, $id, $field = null) { $additionalTable = $this->getEntityType($entityTypeId, 'additional_attribute_table'); $entityTypeId = $this->getEntityTypeId($entityTypeId); - $idField = (is_numeric($id) ? 'attribute_id' : 'attribute_code'); + $idField = is_numeric($id) ? 'attribute_id' : 'attribute_code'; if (!$additionalTable) { return $this->getTableRow('eav/attribute', $idField, $id, $field, 'entity_type_id', $entityTypeId); } @@ -781,15 +889,19 @@ public function getAttribute($entityTypeId, $id, $field = null) $mainTable = $this->getTable('eav/attribute'); if (empty($this->_setupCache[$mainTable][$entityTypeId][$id])) { $additionalTable = $this->getTable($additionalTable); + $bind = array( + 'id' => $id, + 'entity_type_id' => $entityTypeId + ); $select = $this->_conn->select() ->from(array('main' => $mainTable)) ->join( array('additional' => $additionalTable), - 'main.attribute_id=additional.attribute_id') - ->where("main.{$idField}=?", $id) - ->where("main.entity_type_id=?", $entityTypeId); - $row = $this->_conn->fetchRow($select); + 'main.attribute_id = additional.attribute_id') + ->where("main.{$idField} = :id") + ->where('main.entity_type_id = :entity_type_id'); + $row = $this->_conn->fetchRow($select, $bind); if (!$row) { $this->_setupCache[$mainTable][$entityTypeId][$id] = false; } else { @@ -799,9 +911,10 @@ public function getAttribute($entityTypeId, $id, $field = null) } $row = $this->_setupCache[$mainTable][$entityTypeId][$id]; - if (!is_null($field)) { + if ($field !== null) { return isset($row[$field]) ? $row[$field] : false; } + return $row; } @@ -818,7 +931,6 @@ public function getAttributeId($entityTypeId, $id) $id = $this->getAttribute($entityTypeId, $id, 'attribute_id'); } if (!is_numeric($id)) { - //throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong attribute ID.')); return false; } return $id; @@ -833,21 +945,27 @@ public function getAttributeId($entityTypeId, $id) */ public function getAttributeTable($entityTypeId, $id) { - $entityKeyName = is_numeric($entityTypeId) ? 'entity_type_id' : 'entity_type_code'; + $entityKeyName = is_numeric($entityTypeId) ? 'entity_type_id' : 'entity_type_code'; $attributeKeyName = is_numeric($id) ? 'attribute_id' : 'attribute_code'; + $bind = array( + 'id' => $id, + 'entity_type_id' => $entityTypeId + ); $select = $this->getConnection()->select() ->from( - array('e' => $this->getTable('eav/entity_type')), + array('entity_type' => $this->getTable('eav/entity_type')), array('entity_table')) ->join( - array('a' => $this->getTable('eav/attribute')), - 'a.entity_type_id=e.entity_type_id', + array('attribute' => $this->getTable('eav/attribute')), + 'attribute.entity_type_id = entity_type.entity_type_id', array('backend_type')) - ->where("e.{$entityKeyName}=?", $entityTypeId) - ->where("a.{$attributeKeyName}=?", $id) + ->where("entity_type.{$entityKeyName} = :entity_type_id") + ->where("attribute.{$attributeKeyName} = :id") ->limit(1); - if ($result = $this->getConnection()->fetchRow($select)) { + + $result = $this->getConnection()->fetchRow($select, $bind); + if ($result) { $table = $this->getTable($result['entity_table']); if ($result['backend_type'] != 'static') { $table .= '_' . $result['backend_type']; @@ -887,15 +1005,17 @@ public function removeAttribute($entityTypeId, $code) * @param int $sortOrder * @return Mage_Eav_Model_Entity_Setup */ - public function getAttributeSortOrder($entityTypeId, $setId, $groupId, $sortOrder=null) + public function getAttributeSortOrder($entityTypeId, $setId, $groupId, $sortOrder = null) { if (!is_numeric($sortOrder)) { - $sortOrder = $this->_conn->fetchOne("select max(sort_order) - from ".$this->getTable('eav/entity_attribute')." - where attribute_group_id=".$this->getAttributeGroupId($entityTypeId, $setId, $groupId) - ); - $sortOrder++; + $bind = array('attribute_group_id' => $this->getAttributeGroupId($entityTypeId, $setId, $groupId)); + $select = $this->_conn->select() + ->from($this->getTable('eav/entity_attribute'), 'MAX(sort_order)') + ->where('attribute_group_id = :attribute_group_id'); + + $sortOrder = $this->_conn->fetchOne($select, $bind) + 1; } + return $sortOrder; } @@ -911,28 +1031,39 @@ public function getAttributeSortOrder($entityTypeId, $setId, $groupId, $sortOrde */ public function addAttributeToSet($entityTypeId, $setId, $groupId, $attributeId, $sortOrder=null) { - $entityTypeId = $this->getEntityTypeId($entityTypeId); - $setId = $this->getAttributeSetId($entityTypeId, $setId); - $groupId = $this->getAttributeGroupId($entityTypeId, $setId, $groupId); - $attributeId = $this->getAttributeId($entityTypeId, $attributeId); - $generalGroupId = $this->getAttributeGroupId($entityTypeId, $setId, $this->_generalGroupName); - - $oldId = $this->_conn->fetchOne("select entity_attribute_id from ".$this->getTable('eav/entity_attribute')." where attribute_set_id=$setId and attribute_id=$attributeId"); - if ($oldId) { - if ($groupId && $groupId != $generalGroupId) { - $newGroupData = array('attribute_group_id'=>$groupId); - $condition = $this->_conn->quoteInto('entity_attribute_id = ?', $oldId); - $this->_conn->update($this->getTable('eav/entity_attribute'), $newGroupData, $condition); + $entityTypeId = $this->getEntityTypeId($entityTypeId); + $setId = $this->getAttributeSetId($entityTypeId, $setId); + $groupId = $this->getAttributeGroupId($entityTypeId, $setId, $groupId); + $attributeId = $this->getAttributeId($entityTypeId, $attributeId); + $table = $this->getTable('eav/entity_attribute'); + + $bind = array( + 'attribute_set_id' => $setId, + 'attribute_id' => $attributeId + ); + $select = $this->_conn->select() + ->from($table) + ->where('attribute_set_id = :attribute_set_id') + ->where('attribute_id = :attribute_id'); + $result = $this->_conn->fetchRow($select, $bind); + + if ($result) { + if ($result['attribute_group_id'] != $groupId) { + $where = array('entity_attribute_id =?' => $result['entity_attribute_id']); + $data = array('attribute_group_id' => $groupId); + $this->_conn->update($table, $data, $where); } - return $this; + } else { + $data = array( + 'entity_type_id' => $entityTypeId, + 'attribute_set_id' => $setId, + 'attribute_group_id' => $groupId, + 'attribute_id' => $attributeId, + 'sort_order' => $this->getAttributeSortOrder($entityTypeId, $setId, $groupId, $sortOrder), + ); + + $this->_conn->insert($table, $data); } - $this->_conn->insert($this->getTable('eav/entity_attribute'), array( - 'entity_type_id' =>$entityTypeId, - 'attribute_set_id' =>$setId, - 'attribute_group_id'=>$groupId, - 'attribute_id' =>$attributeId, - 'sort_order' =>$this->getAttributeSortOrder($entityTypeId, $setId, $groupId, $sortOrder), - )); return $this; } @@ -954,43 +1085,48 @@ public function addAttributeToGroup($entityType, $setId, $groupId, $attributeId, $groupId = $this->getAttributeGroupId($entityType, $setId, $groupId); $attributeId = $this->getAttributeId($entityType, $attributeId); - $bind = array( + $data = array( 'entity_type_id' => $entityType, 'attribute_set_id' => $setId, 'attribute_group_id' => $groupId, 'attribute_id' => $attributeId, ); + $bind = array( + 'entity_type_id' => $entityType, + 'attribute_set_id' => $setId, + 'attribute_id' => $attributeId + ); $select = $this->getConnection()->select() ->from($this->getTable('eav/entity_attribute')) - ->where('entity_type_id=?', $entityType) - ->where('attribute_set_id=?', $setId) - ->where('attribute_id=?', $attributeId); - $row = $this->getConnection()->fetchRow($select); + ->where('entity_type_id = :entity_type_id') + ->where('attribute_set_id = :attribute_set_id') + ->where('attribute_id = :attribute_id'); + $row = $this->getConnection()->fetchRow($select, $bind); if ($row) { // update - if (!is_null($sortOrder)) { - $bind['sort_order'] = $sortOrder; + if ($sortOrder !== null) { + $data['sort_order'] = $sortOrder; } $this->getConnection()->update( $this->getTable('eav/entity_attribute'), - $bind, + $data, $this->getConnection()->quoteInto('entity_attribute_id=?', $row['entity_attribute_id']) ); - } - else { - if (is_null($sortOrder)) { + } else { + if ($sortOrder === null) { $select = $this->getConnection()->select() - ->from($this->getTable('eav/entity_attribute'), 'MAX(sort_order) + 10') - ->where('entity_type_id=?', $entityType) - ->where('attribute_set_id=?', $setId) - ->where('attribute_group_id=?', $groupId); - $sortOrder = $this->getConnection()->fetchOne($select); + ->from($this->getTable('eav/entity_attribute'), 'MAX(sort_order)') + ->where('entity_type_id = :entity_type_id') + ->where('attribute_set_id = :attribute_set_id') + ->where('attribute_id = :attribute_id'); + + $sortOrder = $this->getConnection()->fetchOne($select, $bind) + 10; } $sortOrder = is_numeric($sortOrder) ? $sortOrder : 1; - $bind['sort_order'] = $sortOrder; - $this->getConnection()->insert($this->getTable('eav/entity_attribute'), $bind); + $data['sort_order'] = $sortOrder; + $this->getConnection()->insert($this->getTable('eav/entity_attribute'), $data); } return $this; @@ -1004,11 +1140,11 @@ public function addAttributeToGroup($entityType, $setId, $groupId, $attributeId, * @param array $entities * @return Mage_Eav_Model_Entity_Setup */ - public function installEntities($entities=null) + public function installEntities($entities = null) { $this->cleanCache(); - if (is_null($entities)) { + if ($entities === null) { $entities = $this->getDefaultEntities(); } @@ -1016,33 +1152,33 @@ public function installEntities($entities=null) $this->addEntityType($entityName, $entity); $frontendPrefix = isset($entity['frontend_prefix']) ? $entity['frontend_prefix'] : ''; - $backendPrefix = isset($entity['backend_prefix']) ? $entity['backend_prefix'] : ''; - $sourcePrefix = isset($entity['source_prefix']) ? $entity['source_prefix'] : ''; + $backendPrefix = isset($entity['backend_prefix']) ? $entity['backend_prefix'] : ''; + $sourcePrefix = isset($entity['source_prefix']) ? $entity['source_prefix'] : ''; - foreach ($entity['attributes'] as $attrCode=>$attr) { + foreach ($entity['attributes'] as $attrCode => $attr) { if (!empty($attr['backend'])) { - if ('_'===$attr['backend']) { + if ('_' === $attr['backend']) { $attr['backend'] = $backendPrefix; - } elseif ('_'===$attr['backend']{0}) { + } elseif ('_' === $attr['backend']{0}) { $attr['backend'] = $backendPrefix.$attr['backend']; } else { $attr['backend'] = $attr['backend']; } } if (!empty($attr['frontend'])) { - if ('_'===$attr['frontend']) { + if ('_' === $attr['frontend']) { $attr['frontend'] = $frontendPrefix; - } elseif ('_'===$attr['frontend']{0}) { + } elseif ('_' === $attr['frontend']{0}) { $attr['frontend'] = $frontendPrefix.$attr['frontend']; } else { $attr['frontend'] = $attr['frontend']; } } if (!empty($attr['source'])) { - if ('_'===$attr['source']) { + if ('_' === $attr['source']) { $attr['source'] = $sourcePrefix; - } elseif ('_'===$attr['source']{0}) { - $attr['source'] = $sourcePrefix.$attr['source']; + } elseif ('_' === $attr['source']{0}) { + $attr['source'] = $sourcePrefix . $attr['source']; } else { $attr['source'] = $attr['source']; } @@ -1060,79 +1196,175 @@ public function installEntities($entities=null) /****************************** CREATE ENTITY TABLES ***********************************/ /** - * Enter description here... + * Create entity tables * - * @param unknown_type $baseName + * @param string $baseName * @param array $options * - no-main * - no-default-types * - types * @return unknown */ - public function createEntityTables($baseName, array $options=array()) + public function createEntityTables($baseTableName, array $options = array()) { - $sql = ''; - - if (empty($options['no-main'])) { - $sql = " -DROP TABLE IF EXISTS `{$baseName}`; -CREATE TABLE `{$baseName}` ( -`entity_id` int(10) unsigned NOT NULL auto_increment, -`entity_type_id` smallint(8) unsigned NOT NULL default '0', -`attribute_set_id` smallint(5) unsigned NOT NULL default '0', -`increment_id` varchar(50) NOT NULL default '', -`parent_id` int(10) unsigned NULL default '0', -`store_id` smallint(5) unsigned NOT NULL default '0', -`created_at` datetime NOT NULL default '0000-00-00 00:00:00', -`updated_at` datetime NOT NULL default '0000-00-00 00:00:00', -`is_active` tinyint(1) unsigned NOT NULL default '1', -PRIMARY KEY (`entity_id`), -CONSTRAINT `FK_{$baseName}_type` FOREIGN KEY (`entity_type_id`) REFERENCES `eav_entity_type` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE, -CONSTRAINT `FK_{$baseName}_store` FOREIGN KEY (`store_id`) REFERENCES `core_store` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + $isNoCreateMainTable = $this->_getValue($options, 'no-main', false); + $isNoDefaultTypes = $this->_getValue($options, 'no-default-types', false); + $customTypes = $this->_getValue($options, 'types', array()); + $tables = array(); + + if (!$isNoCreateMainTable) { + /** + * Create table main eav table + */ + $connection = $this->getConnection(); + $mainTable = $connection + ->newTable($this->getTable($baseTableName)) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Set Id') + ->addColumn('increment_id', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => false, + 'default' => '', + ), 'Increment Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Created At') + ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Updated At') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '1', + ), 'Defines Is Entity Active') + ->addIndex($this->getIdxName($baseTableName, array('entity_type_id')), + array('entity_type_id')) + ->addIndex($this->getIdxName($baseTableName, array('store_id')), + array('store_id')) + ->addForeignKey($this->getFkName($baseTableName, 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $this->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($this->getFkName($baseTableName, 'store_id', 'core/store', 'store_id'), + 'store_id', $this->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Main Table'); + + $tables[$this->getTable($baseTableName)] = $mainTable; } - $types = array( - 'datetime'=>'datetime', - 'decimal'=>'decimal(12,4)', - 'int'=>'int', - 'text'=>'text', - 'varchar'=>'varchar(255)', - ); - if (!empty($options['types']) && is_array($options['types'])) { - if ($options['no-default-types']) { - $types = array(); + $types = array(); + if (!$isNoDefaultTypes) { + $types = array( + 'datetime' => array(Varien_Db_Ddl_Table::TYPE_DATETIME, null), + 'decimal' => array(Varien_Db_Ddl_Table::TYPE_DECIMAL, '12,4'), + 'int' => array(Varien_Db_Ddl_Table::TYPE_INTEGER, null), + 'text' => array(Varien_Db_Ddl_Table::TYPE_TEXT, '64k'), + 'varchar' => array(Varien_Db_Ddl_Table::TYPE_TEXT, '255'), + 'char' => array(Varien_Db_Ddl_Table::TYPE_TEXT, '255') + ); + } + + if (!empty($customTypes)) { + foreach ($customTypes as $type => $fieldType) { + if (count($fieldType) != 2) { + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Wrong type definition for %s', $type)); + } + $types[$type] = $fieldType; } - $types = array_merge($types, $options['types']); } - foreach ($types as $type=>$fieldType) { - $sql .= " -DROP TABLE IF EXISTS `{$baseName}_{$type}`; -CREATE TABLE `{$baseName}_{$type}` ( -`value_id` int(11) NOT NULL auto_increment, -`entity_type_id` smallint(8) unsigned NOT NULL default '0', -`attribute_id` smallint(5) unsigned NOT NULL default '0', -`store_id` smallint(5) unsigned NOT NULL default '0', -`entity_id` int(10) unsigned NOT NULL default '0', -`value` {$fieldType} NOT NULL, -PRIMARY KEY (`value_id`), -UNIQUE KEY `IDX_BASE` (`entity_type_id`,`entity_id`,`attribute_id`,`store_id`), -".($type!=='text' ? " -KEY `value_by_attribute` (`attribute_id`,`value`), -KEY `value_by_entity_type` (`entity_type_id`,`value`), -" : "")." -CONSTRAINT `FK_{$baseName}_{$type}` FOREIGN KEY (`entity_id`) REFERENCES `{$baseName}` (`entity_id`) ON DELETE CASCADE ON UPDATE CASCADE, -CONSTRAINT `FK_{$baseName}_{$type}_attribute` FOREIGN KEY (`attribute_id`) REFERENCES `eav_attribute` (`attribute_id`) ON DELETE CASCADE ON UPDATE CASCADE, -CONSTRAINT `FK_{$baseName}_{$type}_entity_type` FOREIGN KEY (`entity_type_id`) REFERENCES `eav_entity_type` (`entity_type_id`) ON DELETE CASCADE ON UPDATE CASCADE, -CONSTRAINT `FK_{$baseName}_{$type}_store` FOREIGN KEY (`store_id`) REFERENCES `core_store` (`store_id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + /** + * Create table array($baseTableName, $type) + */ + foreach ($types as $type => $fieldType) { + $eavTableName = array($baseTableName, $type); + + $eavTable = $connection->newTable($this->getTable($eavTableName)); + $eavTable + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', $fieldType[0], $fieldType[1], array( + 'nullable' => false, + ), 'Attribute Value') + ->addIndex($this->getIdxName($eavTableName, array('entity_type_id')), + array('entity_type_id')) + ->addIndex($this->getIdxName($eavTableName, array('attribute_id')), + array('attribute_id')) + ->addIndex($this->getIdxName($eavTableName, array('store_id')), + array('store_id')) + ->addIndex($this->getIdxName($eavTableName, array('entity_id')), + array('entity_id')); + if ($type !== 'text') { + $eavTable->addIndex($this->getIdxName($eavTableName, array('attribute_id', 'value')), + array('attribute_id', 'value')); + $eavTable->addIndex($this->getIdxName($eavTableName, array('entity_type_id', 'value')), + array('entity_type_id', 'value')); + } + + $eavTable + ->addForeignKey($this->getFkName($eavTableName, 'entity_id', $baseTableName, 'entity_id'), + 'entity_id', $this->getTable($baseTableName), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($this->getFkName($eavTableName, 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $this->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($this->getFkName($eavTableName, 'store_id', 'core/store', 'store_id'), + 'store_id', $this->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Table'); + + $tables[$this->getTable($eavTableName)] = $eavTable; } + $connection->beginTransaction(); try { - $this->_conn->multi_query($sql); + foreach ($tables as $tableName => $table) { + $connection->createTable($table); + } + $connection->commit(); } catch (Exception $e) { - throw $e; + $connection->rollBack(); + throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Can\'t create table: %s', $tableName)); } return $this; @@ -1143,23 +1375,26 @@ public function createEntityTables($baseName, array $options=array()) * * @return array */ - protected function _getAttributeTableFields() { + protected function _getAttributeTableFields() + { return $this->getConnection()->describeTable($this->getTable('eav/attribute')); } /** * Insert attribute and filter data * + * @param array $data * @return Mage_Eav_Model_Entity_Setup */ - protected function _insertAttribute(array $data) { + protected function _insertAttribute(array $data) + { $bind = array(); $fields = $this->_getAttributeTableFields(); foreach ($data as $k => $v) { if (isset($fields[$k])) { - $bind[$k] = $v; + $bind[$k] = $this->getConnection()->prepareColumnValue($fields[$k], $v); } } if (!$bind) { @@ -1167,17 +1402,19 @@ protected function _insertAttribute(array $data) { } $this->getConnection()->insert($this->getTable('eav/attribute'), $bind); - $attributeId = $this->getConnection()->lastInsertId(); + $attributeId = $this->getConnection()->lastInsertId($this->getTable('eav/attribute')); $this->_insertAttributeAdditionalData( $data['entity_type_id'], array_merge(array('attribute_id' => $attributeId), $data) ); + return $this; } /** * Insert attribute additional data * + * @param int $entityTypeId * @param array $data * @return Mage_Eav_Model_Entity_Setup */ @@ -1187,13 +1424,13 @@ protected function _insertAttributeAdditionalData($entityTypeId, array $data) if (!$additionalTable) { return $this; } - $additionalTableExists = $this->getConnection()->showTableStatus($this->getTable($additionalTable)); + $additionalTableExists = $this->getConnection()->isTableExists($this->getTable($additionalTable)); if ($additionalTable && $additionalTableExists) { $bind = array(); $fields = $this->getConnection()->describeTable($this->getTable($additionalTable)); foreach ($data as $k => $v) { if (isset($fields[$k])) { - $bind[$k] = $v; + $bind[$k] = $this->getConnection()->prepareColumnValue($fields[$k], $v); } } if (!$bind) { @@ -1201,6 +1438,7 @@ protected function _insertAttributeAdditionalData($entityTypeId, array $data) } $this->getConnection()->insert($this->getTable($additionalTable), $bind); } + return $this; } } diff --git a/app/code/core/Mage/Eav/Model/Entity/Store.php b/app/code/core/Mage/Eav/Model/Entity/Store.php index 28447c06..b092089f 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Store.php +++ b/app/code/core/Mage/Eav/Model/Entity/Store.php @@ -20,18 +20,46 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Entity_Store extends Mage_Core_Model_Abstract +/** + * Enter description here ... + * + * @method Mage_Eav_Model_Resource_Entity_Store _getResource() + * @method Mage_Eav_Model_Resource_Entity_Store getResource() + * @method int getEntityTypeId() + * @method Mage_Eav_Model_Entity_Store setEntityTypeId(int $value) + * @method int getStoreId() + * @method Mage_Eav_Model_Entity_Store setStoreId(int $value) + * @method string getIncrementPrefix() + * @method Mage_Eav_Model_Entity_Store setIncrementPrefix(string $value) + * @method string getIncrementLastId() + * @method Mage_Eav_Model_Entity_Store setIncrementLastId(string $value) + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Entity_Store extends Mage_Core_Model_Abstract { + /** + * Resource initialization + */ protected function _construct() { $this->_init('eav/entity_store'); } - + + /** + * Load entity by store + * + * @param int $entityTypeId + * @param int $storeId + * @return Mage_Eav_Model_Entity_Store + */ public function loadByEntityStore($entityTypeId, $storeId) { $this->_getResource()->loadByEntityStore($this, $entityTypeId, $storeId); diff --git a/app/code/core/Mage/Eav/Model/Entity/Type.php b/app/code/core/Mage/Eav/Model/Entity/Type.php index 16302ad0..6d7a187b 100644 --- a/app/code/core/Mage/Eav/Model/Entity/Type.php +++ b/app/code/core/Mage/Eav/Model/Entity/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,37 +28,61 @@ /** * Entity type model * - * @category Mage - * @package Mage_Eav + * @method Mage_Eav_Model_Resource_Entity_Type _getResource() + * @method Mage_Eav_Model_Resource_Entity_Type getResource() + * @method Mage_Eav_Model_Entity_Type setEntityTypeCode(string $value) + * @method string getEntityModel() + * @method Mage_Eav_Model_Entity_Type setEntityModel(string $value) + * @method Mage_Eav_Model_Entity_Type setAttributeModel(string $value) + * @method Mage_Eav_Model_Entity_Type setEntityTable(string $value) + * @method Mage_Eav_Model_Entity_Type setValueTablePrefix(string $value) + * @method Mage_Eav_Model_Entity_Type setEntityIdField(string $value) + * @method int getIsDataSharing() + * @method Mage_Eav_Model_Entity_Type setIsDataSharing(int $value) + * @method string getDataSharingKey() + * @method Mage_Eav_Model_Entity_Type setDataSharingKey(string $value) + * @method Mage_Eav_Model_Entity_Type setDefaultAttributeSetId(int $value) + * @method string getIncrementModel() + * @method Mage_Eav_Model_Entity_Type setIncrementModel(string $value) + * @method int getIncrementPerStore() + * @method Mage_Eav_Model_Entity_Type setIncrementPerStore(int $value) + * @method int getIncrementPadLength() + * @method Mage_Eav_Model_Entity_Type setIncrementPadLength(int $value) + * @method string getIncrementPadChar() + * @method Mage_Eav_Model_Entity_Type setIncrementPadChar(string $value) + * @method string getAdditionalAttributeTable() + * @method Mage_Eav_Model_Entity_Type setAdditionalAttributeTable(string $value) + * @method Mage_Eav_Model_Entity_Type setEntityAttributeCollection(string $value) + * + * @category Mage + * @package Mage_Eav * @author Magento Core Team */ class Mage_Eav_Model_Entity_Type extends Mage_Core_Model_Abstract { - /** - * Enter description here... + * Collection of attributes * * @var Mage_Eav_Model_Mysql4_Entity_Attribute_Collection */ protected $_attributes; /** - * Enter description here... + * Array of attributes * * @var array */ - protected $_attributesBySet = array(); + protected $_attributesBySet = array(); /** - * Enter description here... + * Collection of sets * * @var Mage_Eav_Model_Mysql4_Entity_Attribute_Set_Collection */ protected $_sets; /** - * Enter description here... - * + * Resource initialization */ protected function _construct() { @@ -66,7 +90,7 @@ protected function _construct() } /** - * Enter description here... + * Load type by code * * @param string $code * @return Mage_Eav_Model_Entity_Type @@ -86,14 +110,13 @@ public function loadByCode($code) */ public function getAttributeCollection($setId = null) { - if (is_null($setId)) { - if (is_null($this->_attributes)) { + if ($setId === null) { + if ($this->_attributes === null) { $this->_attributes = $this->_getAttributeCollection() ->setEntityTypeFilter($this); } $collection = $this->_attributes; - } - else { + } else { if (!isset($this->_attributesBySet[$setId])) { $this->_attributesBySet[$setId] = $this->_getAttributeCollection() ->setEntityTypeFilter($this) @@ -101,20 +124,23 @@ public function getAttributeCollection($setId = null) } $collection = $this->_attributesBySet[$setId]; } + return $collection; } /** - * Enter description here... + * Init and retreive attribute collection * * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection */ protected function _getAttributeCollection() { $collection = Mage::getModel('eav/entity_attribute')->getCollection(); - if ($objectsModel = $this->getAttributeModel()) { + $objectsModel = $this->getAttributeModel(); + if ($objectsModel) { $collection->setModel($objectsModel); } + return $collection; } @@ -133,26 +159,22 @@ public function getAttributeSetCollection() } /** - * Enter description here... + * Retreive new incrementId * * @param int $storeId * @return string */ - public function fetchNewIncrementId($storeId=null) + public function fetchNewIncrementId($storeId = null) { if (!$this->getIncrementModel()) { return false; } - if (!$this->getIncrementPerStore()) { - $storeId = 0; - } - elseif (is_null($storeId)) { + if (!$this->getIncrementPerStore() || ($storeId === null)) { /** * store_id null we can have for entity from removed store */ $storeId = 0; - //throw Mage::exception('Mage_Eav', Mage::helper('eav')->__('Valid store_id is expected.')); } // Start transaction to run SELECT ... FOR UPDATE @@ -192,9 +214,9 @@ public function fetchNewIncrementId($storeId=null) } /** - * Enter description here... + * Retreive entity id field * - * @return string + * @return string|null */ public function getEntityIdField() { @@ -202,9 +224,9 @@ public function getEntityIdField() } /** - * Enter description here... + * Retreive entity table name * - * @return string + * @return string|null */ public function getEntityTable() { @@ -212,43 +234,71 @@ public function getEntityTable() } /** - * Enter description here... + * Retrieve entity table prefix name * * @return string */ public function getValueTablePrefix() { - if (empty($this->_data['value_table_prefix'])) { - $this->_data['value_table_prefix'] = $this->_getResource()->getTable($this->getEntityTable()); + $prefix = $this->getEntityTablePrefix(); + if ($prefix) { + return $this->getResource()->getTable($prefix); } - return $this->_data['value_table_prefix']; + + return null; } /** - * Get default attribute set identifier for etity type + * Retrieve entity table prefix * * @return string */ + public function getEntityTablePrefix() + { + $tablePrefix = trim($this->_data['value_table_prefix']); + + if (empty($tablePrefix)) { + $tablePrefix = $this->getEntityTable(); + } + + return $tablePrefix; + } + + /** + * Get default attribute set identifier for etity type + * + * @return string|null + */ public function getDefaultAttributeSetId() { return isset($this->_data['default_attribute_set_id']) ? $this->_data['default_attribute_set_id'] : null; } /** - * Enter description here... + * Retreive entity type id * - * @return string + * @return string|null */ public function getEntityTypeId() { return isset($this->_data['entity_type_id']) ? $this->_data['entity_type_id'] : null; } + /** + * Retreive entity type code + * + * @return string|null + */ public function getEntityTypeCode() { return isset($this->_data['entity_type_code']) ? $this->_data['entity_type_code'] : null; } + /** + * Retreive attribute codes + * + * @return array|null + */ public function getAttributeCodes() { return isset($this->_data['attribute_codes']) ? $this->_data['attribute_codes'] : null; @@ -264,11 +314,15 @@ public function getAttributeModel() if (empty($this->_data['attribute_model'])) { return Mage_Eav_Model_Entity::DEFAULT_ATTRIBUTE_MODEL; } - else { - return $this->_data['attribute_model']; - } + + return $this->_data['attribute_model']; } + /** + * Retreive resource entity object + * + * @return Mage_Core_Model_Resource_Abstract + */ public function getEntity() { return Mage::getResourceSingleton($this->_data['entity_model']); @@ -281,7 +335,8 @@ public function getEntity() */ public function getEntityAttributeCollection() { - if ($collection = $this->_getData('entity_attribute_collection')) { + $collection = $this->_getData('entity_attribute_collection'); + if ($collection) { return $collection; } return 'eav/entity_attribute_collection'; diff --git a/app/code/core/Mage/Eav/Model/Form.php b/app/code/core/Mage/Eav/Model/Form.php new file mode 100644 index 00000000..432081d1 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Form.php @@ -0,0 +1,553 @@ + + */ +abstract class Mage_Eav_Model_Form +{ + /** + * Current module pathname + * + * @var string + */ + protected $_moduleName = ''; + + /** + * Current EAV entity type code + * + * @var string + */ + protected $_entityTypeCode = ''; + + /** + * Current store instance + * + * @var Mage_Core_Model_Store + */ + protected $_store; + + /** + * Current entity type instance + * + * @var Mage_Eav_Model_Entity_Type + */ + protected $_entityType; + + /** + * Current entity instance + * + * @var Mage_Core_Model_Abstract + */ + protected $_entity; + + /** + * Current form code + * + * @var string + */ + protected $_formCode; + + /** + * Array of form attributes + * + * @var array + */ + protected $_attributes; + + /** + * Array of form system attributes + * + * @var array + */ + protected $_systemAttributes; + + /** + * Array of form user defined attributes + * + * @var array + */ + protected $_userAttributes; + + /** + * Is AJAX request flag + * + * @var boolean + */ + protected $_isAjax = false; + + /** + * Whether the invisible form fields need to be filtered/ignored + * + * @var bool + */ + protected $_ignoreInvisible = true; + + /** + * Checks correct module choice + * + * @throws Mage_Core_Exception + */ + public function __construct() + { + if (empty($this->_moduleName)) { + Mage::throwException(Mage::helper('eav')->__('Current module pathname is undefined')); + } + if (empty($this->_entityTypeCode)) { + Mage::throwException(Mage::helper('eav')->__('Current module EAV entity is undefined')); + } + } + + /** + * Get EAV Entity Form Attribute Collection + * + * @return mixed + */ + protected function _getFormAttributeCollection() + { + return Mage::getResourceModel($this->_moduleName . '/form_attribute_collection'); + } + + /** + * Set current store + * + * @param Mage_Core_Model_Store|string|int $store + * @return Mage_Eav_Model_Form + */ + public function setStore($store) + { + $this->_store = Mage::app()->getStore($store); + return $this; + } + + /** + * Set entity instance + * + * @param Mage_Core_Model_Abstract $entity + * @return Mage_Eav_Model_Form + */ + public function setEntity(Mage_Core_Model_Abstract $entity) + { + $this->_entity = $entity; + if ($entity->getEntityTypeId()) { + $this->setEntityType($entity->getEntityTypeId()); + } + return $this; + } + + /** + * Set entity type instance + * + * @param Mage_Eav_Model_Entity_Type|string|int $entityType + * @return Mage_Eav_Model_Form + */ + public function setEntityType($entityType) + { + $this->_entityType = Mage::getSingleton('eav/config')->getEntityType($entityType); + return $this; + } + + /** + * Set form code + * + * @param string $formCode + * @return Mage_Eav_Model_Form + */ + public function setFormCode($formCode) + { + $this->_formCode = $formCode; + return $this; + } + + /** + * Return current store instance + * + * @return Mage_Core_Model_Store + */ + public function getStore() + { + if (is_null($this->_store)) { + $this->_store = Mage::app()->getStore(); + } + return $this->_store; + } + + /** + * Return current form code + * + * @throws Mage_Core_Exception + * @return string + */ + public function getFormCode() + { + if (empty($this->_formCode)) { + Mage::throwException(Mage::helper('eav')->__('Form code is not defined')); + } + return $this->_formCode; + } + + /** + * Return entity type instance + * Return EAV entity type if entity type is not defined + * + * @return Mage_Eav_Model_Entity_Type + */ + public function getEntityType() + { + if (is_null($this->_entityType)) { + $this->setEntityType($this->_entityTypeCode); + } + return $this->_entityType; + } + + /** + * Return current entity instance + * + * @throws Mage_Core_Exception + * @return Mage_Core_Model_Abstract + */ + public function getEntity() + { + if (is_null($this->_entity)) { + Mage::throwException(Mage::helper('eav')->__('Entity instance is not defined')); + } + return $this->_entity; + } + + /** + * Return array of form attributes + * + * @return array + */ + public function getAttributes() + { + if (is_null($this->_attributes)) { + /* @var $collection Mage_Eav_Model_Resource_Form_Attribute_Collection */ + $collection = $this->_getFormAttributeCollection(); + + $collection->setStore($this->getStore()) + ->setEntityType($this->getEntityType()) + ->addFormCodeFilter($this->getFormCode()) + ->setSortOrder(); + + $this->_attributes = array(); + $this->_userAttributes = array(); + foreach ($collection as $attribute) { + /* @var $attribute Mage_Eav_Model_Entity_Attribute */ + $this->_attributes[$attribute->getAttributeCode()] = $attribute; + if ($attribute->getIsUserDefined()) { + $this->_userAttributes[$attribute->getAttributeCode()] = $attribute; + } else { + $this->_systemAttributes[$attribute->getAttributeCode()] = $attribute; + } + } + } + return $this->_attributes; + } + + /** + * Return attribute instance by code or false + * + * @param string $attributeCode + * @return Mage_Eav_Model_Entity_Attribute|false + */ + public function getAttribute($attributeCode) + { + $attributes = $this->getAttributes(); + if (isset($attributes[$attributeCode])) { + return $attributes[$attributeCode]; + } + return false; + } + + /** + * Return array of form user defined attributes + * + * @return array + */ + public function getUserAttributes() + { + if (is_null($this->_userAttributes)) { + // load attributes + $this->getAttributes(); + } + return $this->_userAttributes; + } + + /** + * Return array of form system attributes + * + * @return array + */ + public function getSystemAttributes() + { + if (is_null($this->_systemAttributes)) { + // load attributes + $this->getAttributes(); + } + return $this->_systemAttributes; + } + + /** + * Return attribute data model by attribute + * + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @return Mage_Eav_Model_Attribute_Data_Abstract + */ + protected function _getAttributeDataModel(Mage_Eav_Model_Entity_Attribute $attribute) + { + $dataModel = Mage_Eav_Model_Attribute_Data::factory($attribute, $this->getEntity()); + $dataModel->setIsAjaxRequest($this->getIsAjaxRequest()); + + return $dataModel; + } + + /** + * Prepare request with data and returns it + * + * @param array $data + * @return Zend_Controller_Request_Http + */ + public function prepareRequest(array $data) + { + $request = clone Mage::app()->getRequest(); + $request->setParamSources(); + $request->clearParams(); + $request->setParams($data); + + return $request; + } + + /** + * Extract data from request and return associative data array + * + * @param Zend_Controller_Request_Http $request + * @param string $scope the request scope + * @param boolean $scopeOnly search value only in scope or search value in global too + * @return array + */ + public function extractData(Zend_Controller_Request_Http $request, $scope = null, $scopeOnly = true) + { + $data = array(); + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $dataModel = $this->_getAttributeDataModel($attribute); + $dataModel->setRequestScope($scope); + $dataModel->setRequestScopeOnly($scopeOnly); + $data[$attribute->getAttributeCode()] = $dataModel->extractValue($request); + } + return $data; + } + + /** + * Validate data array and return true or array of errors + * + * @param array $data + * @return boolean|array + */ + public function validateData(array $data) + { + $errors = array(); + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $dataModel = $this->_getAttributeDataModel($attribute); + $dataModel->setExtractedData($data); + if (!isset($data[$attribute->getAttributeCode()])) { + $data[$attribute->getAttributeCode()] = null; + } + $result = $dataModel->validateValue($data[$attribute->getAttributeCode()]); + if ($result !== true) { + $errors = array_merge($errors, $result); + } + } + + if (count($errors) == 0) { + return true; + } + + return $errors; + } + + /** + * Compact data array to current entity + * + * @param array $data + * @return Mage_Eav_Model_Form + */ + public function compactData(array $data) + { + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $dataModel = $this->_getAttributeDataModel($attribute); + $dataModel->setExtractedData($data); + if (!isset($data[$attribute->getAttributeCode()])) { + $data[$attribute->getAttributeCode()] = false; + } + $dataModel->compactValue($data[$attribute->getAttributeCode()]); + } + + return $this; + } + + /** + * Restore data array from SESSION to current entity + * + * @param array $data + * @return Mage_Eav_Model_Form + */ + public function restoreData(array $data) + { + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $dataModel = $this->_getAttributeDataModel($attribute); + $dataModel->setExtractedData($data); + if (!isset($data[$attribute->getAttributeCode()])) { + $data[$attribute->getAttributeCode()] = false; + } + $dataModel->restoreValue($data[$attribute->getAttributeCode()]); + } + return $this; + } + + /** + * Return array of entity formated values + * + * @param string $format + * @return array + */ + public function outputData($format = Mage_Eav_Model_Attribute_Data::OUTPUT_FORMAT_TEXT) + { + $data = array(); + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $dataModel = $this->_getAttributeDataModel($attribute); + $dataModel->setExtractedData($data); + $data[$attribute->getAttributeCode()] = $dataModel->outputValue($format); + } + return $data; + } + + /** + * Restore entity original data + * + * @return Mage_Eav_Model_Form + */ + public function resetEntityData() + { + foreach ($this->getAttributes() as $attribute) { + if ($this->_isAttributeOmitted($attribute)) { + continue; + } + $value = $this->getEntity()->getOrigData($attribute->getAttributeCode()); + $this->getEntity()->setData($attribute->getAttributeCode(), $value); + } + return $this; + } + + /** + * Set is AJAX Request flag + * + * @param boolean $flag + * @return Mage_Eav_Model_Form + */ + public function setIsAjaxRequest($flag = true) + { + $this->_isAjax = (bool)$flag; + return $this; + } + + /** + * Return is AJAX Request + * + * @return boolean + */ + public function getIsAjaxRequest() + { + return $this->_isAjax; + } + + /** + * Set default attribute values for new entity + * + * @return Mage_Eav_Model_Form + */ + public function initDefaultValues() + { + if (!$this->getEntity()->getId()) { + foreach ($this->getAttributes() as $attribute) { + $default = $attribute->getDefaultValue(); + if ($default != '') { + $this->getEntity()->setData($attribute->getAttributeCode(), $default); + } + } + } + return $this; + } + + /** + * Combined getter/setter whether to omit invisible attributes during rendering/validation + * + * @param mixed $setValue + * @return bool|Mage_Eav_Model_Form + */ + public function ignoreInvisible($setValue = null) + { + if (null !== $setValue) { + $this->_ignoreInvisible = (bool)$setValue; + return $this; + } + return $this->_ignoreInvisible; + } + + /** + * Whether the specified attribute needs to skip rendering/validation + * + * @param Mage_Eav_Model_Attribute $attribute + * @return bool + */ + protected function _isAttributeOmitted($attribute) + { + if ($this->_ignoreInvisible && !$attribute->getIsVisible()) { + return true; + } + return false; + } +} diff --git a/app/code/core/Mage/Eav/Model/Form/Element.php b/app/code/core/Mage/Eav/Model/Form/Element.php index 462cd784..b32afc09 100644 --- a/app/code/core/Mage/Eav/Model/Form/Element.php +++ b/app/code/core/Mage/Eav/Model/Form/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,6 +28,17 @@ /** * Eav Form Element Model * + * @method Mage_Eav_Model_Resource_Form_Element _getResource() + * @method Mage_Eav_Model_Resource_Form_Element getResource() + * @method int getTypeId() + * @method Mage_Eav_Model_Form_Element setTypeId(int $value) + * @method int getFieldsetId() + * @method Mage_Eav_Model_Form_Element setFieldsetId(int $value) + * @method int getAttributeId() + * @method Mage_Eav_Model_Form_Element setAttributeId(int $value) + * @method int getSortOrder() + * @method Mage_Eav_Model_Form_Element setSortOrder(int $value) + * * @category Mage * @package Mage_Eav * @author Magento Core Team diff --git a/app/code/core/Mage/Eav/Model/Form/Fieldset.php b/app/code/core/Mage/Eav/Model/Form/Fieldset.php index 34916219..f37af7cc 100644 --- a/app/code/core/Mage/Eav/Model/Form/Fieldset.php +++ b/app/code/core/Mage/Eav/Model/Form/Fieldset.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,6 +28,15 @@ /** * Eav Form Fieldset Model * + * @method Mage_Eav_Model_Resource_Form_Fieldset _getResource() + * @method Mage_Eav_Model_Resource_Form_Fieldset getResource() + * @method int getTypeId() + * @method Mage_Eav_Model_Form_Fieldset setTypeId(int $value) + * @method string getCode() + * @method Mage_Eav_Model_Form_Fieldset setCode(string $value) + * @method int getSortOrder() + * @method Mage_Eav_Model_Form_Fieldset setSortOrder(int $value) + * * @category Mage * @package Mage_Eav * @author Magento Core Team diff --git a/app/code/core/Mage/Eav/Model/Form/Type.php b/app/code/core/Mage/Eav/Model/Form/Type.php index cc8cec4c..4d5b3007 100644 --- a/app/code/core/Mage/Eav/Model/Form/Type.php +++ b/app/code/core/Mage/Eav/Model/Form/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,6 +28,19 @@ /** * Eav Form Type Model * + * @method Mage_Eav_Model_Resource_Form_Type _getResource() + * @method Mage_Eav_Model_Resource_Form_Type getResource() + * @method string getCode() + * @method Mage_Eav_Model_Form_Type setCode(string $value) + * @method string getLabel() + * @method Mage_Eav_Model_Form_Type setLabel(string $value) + * @method int getIsSystem() + * @method Mage_Eav_Model_Form_Type setIsSystem(int $value) + * @method string getTheme() + * @method Mage_Eav_Model_Form_Type setTheme(string $value) + * @method int getStoreId() + * @method Mage_Eav_Model_Form_Type setStoreId(int $value) + * * @category Mage * @package Mage_Eav * @author Magento Core Team diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Config.php b/app/code/core/Mage/Eav/Model/Mysql4/Config.php index 3eee79da..c093d753 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Config.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Config.php @@ -20,76 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Config extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Config extends Mage_Eav_Model_Resource_Config { - protected static $_entityTypes; - protected static $_attributes = array(); - - protected function _construct() - { - $this->_init('eav/entity_type', 'entity_type_id'); - } - - /** - * Load all entity types - * - * @return Mage_Eav_Model_Mysql4_Config - */ - protected function _loadTypes() - { - if (!$this->_getReadAdapter()) { - self::$_entityTypes = array(); - return $this; - } - if (is_null(self::$_entityTypes)) { - self::$_entityTypes = array(); - $select = $this->_getReadAdapter()->select()->from($this->getMainTable()); - $data = $this->_getReadAdapter()->fetchAll($select); - foreach ($data as $row) { - self::$_entityTypes['by_id'][$row['entity_type_id']] = $row; - self::$_entityTypes['by_code'][$row['entity_type_code']] = $row; - } - } - - return $this; - } - - protected function _loadTypeAttributes($typeId) - { - if (!isset(self::$_attributes[$typeId])) { - $select = $this->_getReadAdapter()->select()->from($this->getTable('eav/attribute')) - ->where('entity_type_id=?', $typeId); - self::$_attributes[$typeId] = $this->_getReadAdapter()->fetchAll($select); - } - return self::$_attributes[$typeId]; - } - - public function fetchEntityTypeData($entityType) - { - $this->_loadTypes(); - - if (is_numeric($entityType)) { - $info = isset(self::$_entityTypes['by_id'][$entityType]) ? self::$_entityTypes['by_id'][$entityType] : null; - } - else { - $info = isset(self::$_entityTypes['by_code'][$entityType]) ? self::$_entityTypes['by_code'][$entityType] : null; - } - - $data = array(); - if ($info) { - $data['entity'] = $info; - $attributes = $this->_loadTypeAttributes($info['entity_type_id']); - $data['attributes'] = array(); - foreach ($attributes as $attribute) { - $data['attributes'][$attribute['attribute_id']] = $attribute; - $data['attributes'][$attribute['attribute_code']] = $attribute['attribute_id']; - } - } - return $data; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php index bbe93f8b..b1501265 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,522 +28,10 @@ /** * EAV attribute model * - * @category Mage - * @package Mage_Eav + * @category Mage + * @package Mage_Eav + * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Attribute extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Entity_Attribute extends Mage_Eav_Model_Resource_Entity_Attribute { - protected static $_entityAttributes = null; - - protected function _construct() - { - $this->_init('eav/attribute', 'attribute_id'); - } - - /** - * Initialize unique fields - * - * @return Mage_Core_Model_Mysql4_Abstract - */ - protected function _initUniqueFields() - { - $this->_uniqueFields = array(array( - 'field' => array('attribute_code','entity_type_id'), - 'title' => Mage::helper('eav')->__('Attribute with the same code') - )); - return $this; - } - - protected function _loadTypeAttributes($entityTypeId) - { - if (!isset(self::$_entityAttributes[$entityTypeId])) { - $select = $this->_getReadAdapter()->select()->from($this->getMainTable()) - ->where('entity_type_id=?', $entityTypeId); - $data = $this->_getReadAdapter()->fetchAll($select); - foreach ($data as $row) { - self::$_entityAttributes[$entityTypeId][$row['attribute_code']] = $row; - } - } - return $this; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @param int $entityTypeId - * @param string $code - * @return boolean - */ - public function loadByCode(Mage_Core_Model_Abstract $object, $entityTypeId, $code) - { - $select = $this->_getLoadSelect('attribute_code', $code, $object) - ->where('entity_type_id=?', $entityTypeId); - $data = $this->_getReadAdapter()->fetchRow($select); - - if ($data) { - $object->setData($data); - $this->_afterLoad($object); - return true; - } - return false; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return int - */ - private function _getMaxSortOrder(Mage_Core_Model_Abstract $object) - { - if( intval($object->getAttributeGroupId()) > 0 ) { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->getTable('entity_attribute'), new Zend_Db_Expr("MAX(`sort_order`)")) - ->where("{$this->getTable('entity_attribute')}.attribute_set_id = ?", $object->getAttributeSetId()) - ->where("{$this->getTable('entity_attribute')}.attribute_group_id = ?", $object->getAttributeGroupId()); - $maxOrder = $read->fetchOne($select); - return $maxOrder; - } - - return 0; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - public function deleteEntity(Mage_Core_Model_Abstract $object) - { - $write = $this->_getWriteAdapter(); - $condition = $write->quoteInto($this->getTable('entity_attribute').'.entity_attribute_id = ?', $object->getEntityAttributeId()); - /** - * Delete attribute values - */ - $select = $write->select() - ->from($this->getTable('entity_attribute')) - ->where($condition); - $data = $write->fetchRow($select); - if (!empty($data)) { - /** - * @todo !!!! need fix retrieving attribute entity, this realization is temprary - */ - $attribute = Mage::getModel('eav/entity_attribute') - ->load($data['attribute_id']) - ->setEntity(Mage::getSingleton('catalog/product')->getResource()); - - if ($this->isUsedBySuperProducts($attribute, $data['attribute_set_id'])) { - Mage::throwException(Mage::helper('eav')->__("Attribute '%s' used in configurable products.", $attribute->getAttributeCode())); - } - - if ($backendTable = $attribute->getBackend()->getTable()) { - $clearCondition = array( - $write->quoteInto('entity_type_id=?',$attribute->getEntityTypeId()), - $write->quoteInto('attribute_id=?',$attribute->getId()), - $write->quoteInto('entity_id IN ( - SELECT entity_id FROM '.$attribute->getEntity()->getEntityTable().' WHERE attribute_set_id=?)', - $data['attribute_set_id']) - ); - $write->delete($backendTable, $clearCondition); - } - } - - $write->delete($this->getTable('entity_attribute'), $condition); - return $this; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - $frontendLabel = $object->getFrontendLabel(); - if (is_array($frontendLabel)) { - if (!isset($frontendLabel[0]) || is_null($frontendLabel[0]) || $frontendLabel[0]=='') { - Mage::throwException(Mage::helper('eav')->__('Frontend label is not defined.')); - } - $object->setFrontendLabel($frontendLabel[0]); - $object->setStoreLabels($frontendLabel); - } - - /** - * @todo need use default source model of entity type !!! - */ - if (!$object->getId()) { - if ($object->getFrontendInput()=='select') { - $object->setSourceModel('eav/entity_attribute_source_table'); - } - } - - return parent::_beforeSave($object); - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $this->_saveStoreLabels($object) - ->_saveAdditionalAttributeData($object) - ->saveInSetIncluding($object) - ->_saveOption($object); - return parent::_afterSave($object); - } - - /** - * Save store labels - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _saveStoreLabels(Mage_Core_Model_Abstract $object) - { - $storeLabels = $object->getStoreLabels(); - if (is_array($storeLabels)) { - if ($object->getId()) { - $condition = $this->_getWriteAdapter()->quoteInto('attribute_id = ?', $object->getId()); - $this->_getWriteAdapter()->delete($this->getTable('eav/attribute_label'), $condition); - } - foreach ($storeLabels as $storeId => $label) { - if ($storeId == 0 || !strlen($label)) { - continue; - } - $this->_getWriteAdapter()->insert( - $this->getTable('eav/attribute_label'), - array( - 'attribute_id' => $object->getId(), - 'store_id' => $storeId, - 'value' => $label - ) - ); - } - } - return $this; - } - - /** - * Save additional data of attribute - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _saveAdditionalAttributeData(Mage_Core_Model_Abstract $object) - { - if ($additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId())) { - $describe = $this->describeTable($this->getTable($additionalTable)); - $data = array(); - foreach (array_keys($describe) as $field) { - if (null !== ($value = $object->getData($field))) { - $data[$field] = $value; - } - } - $select = $this->_getWriteAdapter()->select() - ->from($this->getTable($additionalTable), array('attribute_id')) - ->where('attribute_id = ?', $object->getId()); - if ($this->_getWriteAdapter()->fetchOne($select)) { - $this->_getWriteAdapter()->update( - $this->getTable($additionalTable), - $data, - $this->_getWriteAdapter()->quoteInto('attribute_id = ?', $object->getId()) - ); - } else { - $this->_getWriteAdapter()->insert($this->getTable($additionalTable), $data); - } - } - return $this; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - public function saveInSetIncluding(Mage_Core_Model_Abstract $object) - { - $attrId = $object->getId(); - $setId = (int) $object->getAttributeSetId(); - $groupId= (int) $object->getAttributeGroupId(); - - if ($setId && $groupId && $object->getEntityTypeId()) { - $write = $this->_getWriteAdapter(); - $table = $this->getTable('entity_attribute'); - - - $data = array( - 'entity_type_id' => $object->getEntityTypeId(), - 'attribute_set_id' => $setId, - 'attribute_group_id' => $groupId, - 'attribute_id' => $attrId, - 'sort_order' => (($object->getSortOrder()) ? $object->getSortOrder() : $this->_getMaxSortOrder($object) + 1), - ); - - $condition = "$table.attribute_id = '$attrId' - AND $table.attribute_set_id = '$setId'"; - $write->delete($table, $condition); - $write->insert($table, $data); - - } - return $this; - } - - /** - * Enter description here... - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _saveOption(Mage_Core_Model_Abstract $object) - { - $option = $object->getOption(); - if (is_array($option)) { - $write = $this->_getWriteAdapter(); - $optionTable = $this->getTable('attribute_option'); - $optionValueTable = $this->getTable('attribute_option_value'); - $stores = Mage::getModel('core/store') - ->getResourceCollection() - ->setLoadDefault(true) - ->load(); - - if (isset($option['value'])) { - $attributeDefaultValue = array(); - if (!is_array($object->getDefault())) { - $object->setDefault(array()); - } - - foreach ($option['value'] as $optionId => $values) { - $intOptionId = (int) $optionId; - if (!empty($option['delete'][$optionId])) { - if ($intOptionId) { - $condition = $write->quoteInto('option_id=?', $intOptionId); - $write->delete($optionTable, $condition); - } - - continue; - } - - if (!$intOptionId) { - $data = array( - 'attribute_id' => $object->getId(), - 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, - ); - $write->insert($optionTable, $data); - $intOptionId = $write->lastInsertId(); - } - else { - $data = array( - 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, - ); - $write->update($optionTable, $data, $write->quoteInto('option_id=?', $intOptionId)); - } - - if (in_array($optionId, $object->getDefault())) { - if ($object->getFrontendInput() == 'multiselect') { - $attributeDefaultValue[] = $intOptionId; - } else if ($object->getFrontendInput() == 'select') { - $attributeDefaultValue = array($intOptionId); - } - } - - - // Default value - if (!isset($values[0])) { - Mage::throwException(Mage::helper('eav')->__('Default option value is not defined.')); - } - - $write->delete($optionValueTable, $write->quoteInto('option_id=?', $intOptionId)); - foreach ($stores as $store) { - if (isset($values[$store->getId()]) && (!empty($values[$store->getId()]) || $values[$store->getId()] == "0")) { - $data = array( - 'option_id' => $intOptionId, - 'store_id' => $store->getId(), - 'value' => $values[$store->getId()], - ); - $write->insert($optionValueTable, $data); - } - } - } - - $write->update($this->getMainTable(), array( - 'default_value' => implode(',', $attributeDefaultValue) - ), $write->quoteInto($this->getIdFieldName() . '=?', $object->getId())); - } - } - return $this; - } - - public function isUsedBySuperProducts(Mage_Core_Model_Abstract $object, $attributeSet=null) - { - $read = $this->_getReadAdapter(); - $attrTable = $this->getTable('catalog/product_super_attribute'); - $productTable = $this->getTable('catalog/product'); - $select = $read->select() - ->from(array('_main_table' => $attrTable), 'COUNT(*)') - ->join(array('_entity'=> $productTable), '_main_table.product_id = _entity.entity_id') - ->where("_main_table.attribute_id = ?", $object->getAttributeId()) - ->group('_main_table.attribute_id') - ->limit(1); - - if (!is_null($attributeSet)) { - $select->where('_entity.attribute_set_id = ?', $attributeSet); - } - $valueCount = $read->fetchOne($select); - return $valueCount; - } - - /** - * Return attribute id - * - * @param string $entityType - * @param string $code - * @return int - */ - public function getIdByCode($entityType, $code) - { - $select = $this->_getReadAdapter()->select() - ->from(array('a'=>$this->getTable('eav/attribute')), array('a.attribute_id')) - ->join(array('t'=>$this->getTable('eav/entity_type')), 'a.entity_type_id = t.entity_type_id', array()) - ->where('t.entity_type_code = ?', $entityType) - ->where('a.attribute_code = ?', $code); - - return $this->_getReadAdapter()->fetchOne($select); - } - - public function getAttributeCodesByFrontendType($type) - { - $select = $this->_getReadAdapter()->select(); - $select - ->from($this->getTable('eav/attribute'), 'attribute_code') - ->where('frontend_input = ?', $type); - - $result = $this->_getReadAdapter()->fetchCol($select); - - if ($result) { - return $result; - } else { - return array(); - } - } - - /** - * Retrieve Select For Flat Attribute update - * - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute - * @param int $store - * @return Varien_Db_Select - */ - public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store) - { - $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; - if ($attribute->getFlatAddChildData()) { - $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; - } - $select = $this->_getReadAdapter()->select() - ->joinLeft( - array('t1' => $attribute->getBackend()->getTable()), - $joinCondition, - array() - ) - ->joinLeft( - array('t2' => $attribute->getBackend()->getTable()), - "t2.entity_id = t1.entity_id" - . " AND t1.entity_type_id = t2.entity_type_id" - . " AND t1.attribute_id = t2.attribute_id" - . " AND t2.store_id = {$store}", - array($attribute->getAttributeCode() => "IF(t2.value_id>0, t2.value, t1.value)")) - ->where("t1.entity_type_id=?", $attribute->getEntityTypeId()) - ->where("t1.attribute_id=?", $attribute->getId()) - ->where("t1.store_id=?", 0); - if ($attribute->getFlatAddChildData()) { - $select->where("e.is_child=?", 0); - } - return $select; - } - - /** - * Describe table - * - * @param string $table - * @return array - */ - public function describeTable($table) { - return $this->_getReadAdapter()->describeTable($table); - } - - /** - * Retrieve additional attribute table name for specified entity type - * - * @param integer $entityTypeId - * @return string - */ - public function getAdditionalAttributeTable($entityTypeId) - { - return Mage::getResourceSingleton('eav/entity_type')->getAdditionalAttributeTable($entityTypeId); - } - - /** - * Load additional attribute data. - * Load label of current active store - * - * @param Varien_Object $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute - */ - protected function _afterLoad(Mage_Core_Model_Abstract $object) - { - if ($entityType = $object->getData('entity_type')) { - $additionalTable = $entityType->getAdditionalAttributeTable(); - } else { - $additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId()); - } - if ($additionalTable) { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable($additionalTable)) - ->where('attribute_id = ?', $object->getId()); - if ($result = $this->_getReadAdapter()->fetchRow($select)) { - $object->addData($result); - } - } - return $this; - } - - /** - * Retrieve store labels by given attribute id - * - * @param integer $attributeId - * @return array - */ - public function getStoreLabelsByAttributeId($attributeId) - { - $values = array(); - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/attribute_label')) - ->where('attribute_id = ?', $attributeId); - foreach ($this->_getReadAdapter()->fetchAll($select) as $row) { - $values[$row['store_id']] = $row['value']; - } - return $values; - } - - /** - * Load by given attributes ids and return only exist attribute ids - * - * @param array $attributeIds - * @return array - */ - public function getValidAttributeIds($attributeIds) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable(), array('attribute_id')) - ->where('attribute_id in (?)', $attributeIds); - return $this->_getReadAdapter()->fetchCol($select); - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php index 67d26f7b..af3b7607 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,361 +28,10 @@ /** * EAV attribute resource collection * - * @category Mage - * @package Mage_Eav + * @category Mage + * @package Mage_Eav + * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Eav_Model_Mysql4_Entity_Attribute_Collection extends Mage_Eav_Model_Resource_Entity_Attribute_Collection { - /** - * Add attribute set info flag - * - * @var boolean - */ - protected $_addSetInfoFlag = false; - - /** - * Resource model initialization - */ - public function _construct() - { - $this->_init('eav/entity_attribute'); - } - - /** - * Return array of fields to load attribute values - * - * @return array - */ - protected function _getLoadDataFields() - { - $fields = array( - 'attribute_id', - 'entity_type_id', - 'attribute_code', - 'attribute_model', - 'backend_model', - 'backend_type', - 'backend_table', - 'frontend_input', - 'source_model', - ); - return $fields; - } - - /** - * Specify select columns which are used for load arrtibute values - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function useLoadDataFields() - { - $this->getSelect()->reset(Zend_Db_Select::COLUMNS); - $this->getSelect()->columns($this->_getLoadDataFields()); - return $this; - } - - /** - * Specify attribute entity type filter - * - * @param Mage_Eav_Model_Entity_Type | int $type - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setEntityTypeFilter($type) - { - if ($type instanceof Mage_Eav_Model_Entity_Type) { - $additionalTable = $type->getAdditionalAttributeTable(); - $id = $type->getId(); - } else { - $additionalTable = $this->getResource()->getAdditionalAttributeTable($type); - $id = $type; - } - $this->getSelect()->where('main_table.entity_type_id=?', $id); - if ($additionalTable) { - $this->getSelect()->join( - array('additional_table' => $this->getTable($additionalTable)), - 'additional_table.attribute_id=main_table.attribute_id' - ); - } - return $this; - } - - /** - * Specify attribute set filter - * - * @param int $setId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setAttributeSetFilter($setId) - { - if (is_array($setId)) { - if (!empty($setId)) { - $this->join('entity_attribute', 'entity_attribute.attribute_id=main_table.attribute_id', 'attribute_id'); - $this->getSelect()->where('entity_attribute.attribute_set_id IN(?)', $setId); - } - } - elseif($setId) { - $this->join('entity_attribute', 'entity_attribute.attribute_id=main_table.attribute_id', '*'); - $this->getSelect()->where('entity_attribute.attribute_set_id=?', $setId); - $this->setOrder('sort_order', 'asc'); - } - return $this; - } - - /** - * Specify multiple attribute sets filter - * Result will be ordered by sort_order - * - * @param array $setIds - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setAttributeSetsFilter(array $setIds) - { - $this->join('entity_attribute', 'entity_attribute.attribute_id=main_table.attribute_id', 'attribute_id'); - $this->getSelect()->distinct(true); - $this->getSelect()->where('entity_attribute.attribute_set_id IN(?)', $setIds); - $this->setOrder('sort_order', 'asc'); - return $this; - } - - /** - * Filter for selecting of attributes that is in all sets - * - * @param array $setIds - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setInAllAttributeSetsFilter(array $setIds) - { - foreach ($setIds as $setId) { - $setId = (int) $setId; - if (!$setId) { - continue; - } - $this->getSelect()->join(array('entity_attribute_'.$setId=>$this->getTable('entity_attribute')), 'entity_attribute_' . $setId . '.attribute_id=main_table.attribute_id and entity_attribute_' . $setId . '.attribute_set_id=' . $setId, 'attribute_id'); - } - - $this->getSelect()->distinct(true); - $this->setOrder('is_user_defined', 'asc'); - return $this; - } - - /** - * Add filter which exclude attributes assigned to attribute set - * - * @param int $setId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setAttributeSetExcludeFilter($setId) - { - $this->join('entity_attribute', 'entity_attribute.attribute_id=main_table.attribute_id', '*'); - $this->getSelect()->where('entity_attribute.attribute_set_id != ?', $setId); - $this->setOrder('sort_order', 'asc'); - return $this; - } - - /** - * Exclude attributes filter - * - * @param array $attributes - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setAttributesExcludeFilter($attributes) - { - $this->getSelect()->where('main_table.attribute_id NOT IN(?)', $attributes); - return $this; - } - - /** - * Filter by attribute group id - * - * @param int $groupId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setAttributeGroupFilter($groupId) - { - $this->join('entity_attribute', 'entity_attribute.attribute_id=main_table.attribute_id', '*'); - $this->getSelect()->where('entity_attribute.attribute_group_id=?', $groupId); - $this->setOrder('sort_order', 'asc'); - return $this; - } - - /** - * Declare group by attribute id condition for collection select - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addAttributeGrouping() - { - $this->getSelect()->group('entity_attribute.attribute_id'); - return $this; - } - - /** - * Specify "is_unique" filter as true - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addIsUniqueFilter() - { - $this->getSelect()->where('main_table.is_unique>0'); - return $this; - } - - /** - * Specify "is_unique" filter as false - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addIsNotUniqueFilter() - { - $this->getSelect()->where('main_table.is_unique=0'); - return $this; - } - - /** - * Specify filter to select just attributes with options - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addHasOptionsFilter() - { - $this->getSelect() - ->joinLeft( - array('ao'=>$this->getTable('eav/attribute_option')), 'ao.attribute_id = main_table.attribute_id', 'option_id' - ) - ->group('main_table.attribute_id') - ->where('(main_table.frontend_input = ? and option_id > 0) or (main_table.frontend_input <> ?) or (main_table.is_user_defined = 0)', 'select', 'select'); - - return $this; - } - - /** - * Apply filter by attribute frontend input type - * - * @param string $frontendInputType - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setFrontendInputTypeFilter($frontendInputType) - { - $this->getSelect() - ->where('main_table.frontend_input = ?', $frontendInputType); - return $this; - } - - /** - * Flag for adding information about attributes sets to result - * - * @param bool $flag - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addSetInfo($flag=true) - { - $this->_addSetInfoFlag = $flag; - return $this; - } - - /** - * Ad information about attribute sets to collection result data - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - protected function _addSetInfo() - { - if ($this->_addSetInfoFlag) { - $attributeIds = array(); - foreach ($this->_data as &$dataItem) { - $attributeIds[] = $dataItem['attribute_id']; - } - $attributeToSetInfo = array(); - - if (count($attributeIds) > 0) { - $select = $this->getConnection()->select() - ->from( - array('entity' => $this->getTable('entity_attribute')), - array('attribute_id','attribute_set_id', 'attribute_group_id', 'sort_order') - ) - ->joinLeft( - array('group' => $this->getTable('attribute_group')), - 'entity.attribute_group_id=group.attribute_group_id', - array('group_sort_order' => 'sort_order') - ) - ->where('attribute_id IN (?)', $attributeIds); - $result = $this->getConnection()->fetchAll($select); - - foreach ($result as $row) { - $data = array( - 'group_id' => $row['attribute_group_id'], - 'group_sort' => $row['group_sort_order'], - 'sort' => $row['sort_order'] - ); - $attributeToSetInfo[$row['attribute_id']][$row['attribute_set_id']] = $data; - } - } - - foreach ($this->_data as &$attributeData) { - if (isset($attributeToSetInfo[$attributeData['attribute_id']])) { - $setInfo = $attributeToSetInfo[$attributeData['attribute_id']]; - } else { - $setInfo = array(); - } - - $attributeData['attribute_set_info'] = $setInfo; - } - - unset($attributeToSetInfo); - unset($attributeIds); - } - return $this; - } - - protected function _afterLoadData() - { - $this->_addSetInfo(); - - return parent::_afterLoadData(); - } - - /** - * Load is used in configurable products flag - * - * @deprecated - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function checkConfigurableProducts() - { - return $this; - } - - /** - * Specify collection attribute codes filter - * - * @param string || array $code - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function setCodeFilter($code) - { - if (empty($code)) { - return $this; - } - if (!is_array($code)) { - $code = array($code); - } - $this->getSelect()->where('main_table.attribute_code IN(?)', $code); - return $this; - } - - /** - * Add store label to attribute by specified store id - * - * @param integer $storeId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Collection - */ - public function addStoreLabel($storeId) - { - $this->getSelect()->joinLeft( - array('al' => $this->getTable('eav/attribute_label')), - 'al.attribute_id = main_table.attribute_id AND al.store_id = ' . (int) $storeId, - array('store_label' => new Zend_Db_Expr('IFNULL(al.value, main_table.frontend_label)')) - ); - return $this; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group.php index 0a441110..1710f951 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group.php @@ -20,87 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Group extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Entity_Attribute_Group extends Mage_Eav_Model_Resource_Entity_Attribute_Group { - protected function _construct() - { - $this->_init('eav/attribute_group', 'attribute_group_id'); - } - - /** - * Checks if attribute group exists - * - * @param Mage_Eav_Model_Entity_Attribute_Group $object - * @return boolean - */ - public function itemExists($object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where('attribute_set_id = ?', $object->getAttributeSetId()) - ->where('attribute_group_name = ?', $object->getAttributeGroupName()); - if ($this->_getReadAdapter()->fetchRow($select)) { - return true; - } - return false; - } - - /** - * Perform actions before object save - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - if (!$object->getSortOrder()) { - $object->setSortOrder($this->_getMaxSortOrder($object) + 1); - } - return parent::_beforeSave($object); - } - - /** - * Perform actions after object save - * - * @param Mage_Core_Model_Abstract $object - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - if ($object->getAttributes()) { - foreach ($object->getAttributes() as $attribute) { - $attribute->setAttributeGroupId($object->getId()); - $attribute->save(); - } - } - return parent::_afterSave($object); - } - - private function _getMaxSortOrder($object) - { - $read = $this->_getReadAdapter(); - $select = $read->select() - ->from($this->getMainTable(), new Zend_Db_Expr("MAX(`sort_order`)")) - ->where("{$this->getMainTable()}.attribute_set_id = ?", $object->getAttributeSetId()); - $maxOrder = $read->fetchOne($select); - return $maxOrder; - } - - /** - * Set any group default if old one was removed - * - * @param integer $attributeSetId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Group - */ - public function updateDefaultGroup($attributeSetId) - { - $this->_getWriteAdapter()->query( - "UPDATE `{$this->getMainTable()}` SET default_id = 1 WHERE attribute_set_id = :attribute_set_id ORDER BY default_id DESC LIMIT 1", - array('attribute_set_id' => $attributeSetId) - ); - return $this; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group/Collection.php index 38252181..6f7c9a66 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Group/Collection.php @@ -20,32 +20,19 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Group_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Entity_Attribute_Group_Collection + extends Mage_Eav_Model_Resource_Entity_Attribute_Group_Collection { - /** - * Init resource model for collection - * - */ - public function _construct() - { - $this->_init('eav/entity_attribute_group'); - } - - /** - * Set Attribute Set Filter - * - * @param int $setId - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Group_Collection - */ - public function setAttributeSetFilter($setId) - { - $this->getSelect()->where('main_table.attribute_set_id=?', $setId); - $this->getSelect()->order('main_table.sort_order'); - return $this; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php index af0438c2..db33e113 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option.php @@ -20,107 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Entity attribute option resource model * - * @category Mage - * @package Mage_Eav + * @category Mage + * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Option extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Entity_Attribute_Option extends Mage_Eav_Model_Resource_Entity_Attribute_Option { - public function _construct() - { - $this->_init('eav/attribute_option', 'option_id'); - } - - /** - * Add Join with option value for collection select - * - * @param Mage_Eav_Model_Entity_Collection_Abstract $collection - * @param Mage_Eav_Model_Entity_Attribute $attribute - * @param Zend_Db_Expr $valueExpr - * - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Option - */ - public function addOptionValueToCollection($collection, $attribute, $valueExpr) { - $attributeCode = $attribute->getAttributeCode(); - $optionTable1 = $attributeCode . '_option_value_t1'; - $optionTable2 = $attributeCode . '_option_value_t2'; - - $collection->getSelect() - ->joinLeft( - array($optionTable1 => $this->getTable('eav/attribute_option_value')), - "`{$optionTable1}`.`option_id`={$valueExpr}" - . " AND `{$optionTable1}`.`store_id`='0'", - array()) - ->joinLeft( - array($optionTable2 => $this->getTable('eav/attribute_option_value')), - "`{$optionTable2}`.`option_id`={$valueExpr}" - . " AND `{$optionTable1}`.`store_id`='{$collection->getStoreId()}'", - array($attributeCode => "IFNULL(`{$optionTable2}`.`value`, `{$optionTable1}`.`value`)") - ); - - return $this; - } - - /** - * Retrieve Select for update Flat data - * - * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute - * @param int $store - * @param bool $hasValueField flag which require option value - * @return Varien_Db_Select - */ - public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store, $hasValueField=true) - { - $attributeTable = $attribute->getBackend()->getTable(); - $attributeCode = $attribute->getAttributeCode(); - - $joinCondition = "`e`.`entity_id`=`t1`.`entity_id`"; - if ($attribute->getFlatAddChildData()) { - $joinCondition .= " AND `e`.`child_id`=`t1`.`entity_id`"; - } - - $valueExpr = new Zend_Db_Expr("IF(t2.value_id>0, t2.value, t1.value)"); - $select = $this->_getReadAdapter()->select() - ->joinLeft( - array('t1' => $attributeTable), - $joinCondition, - array() - ) - ->joinLeft( - array('t2' => $attributeTable), - "`t2`.`entity_id`=`t1`.`entity_id`" - . " AND `t1`.`entity_type_id`=`t2`.`entity_type_id`" - . " AND `t1`.`attribute_id`=`t2`.`attribute_id`" - . " AND `t2`.`store_id`={$store}", - array($attributeCode => $valueExpr)); - if (($attribute->getFrontend()->getInputType() != 'multiselect') && $hasValueField) { - $select->joinLeft( - array('to1' => $this->getTable('eav/attribute_option_value')), - "`to1`.`option_id`={$valueExpr}" - . " AND `to1`.`store_id`='0'", - array()) - ->joinLeft( - array('to2' => $this->getTable('eav/attribute_option_value')), - "`to2`.`option_id`={$valueExpr}" - . " AND `to2`.`store_id`='{$store}'", - array($attributeCode . '_value' => "IFNULL(`to2`.`value`, `to1`.`value`)") - ); - } - $select - ->where('t1.entity_type_id=?', $attribute->getEntityTypeId()) - ->where('t1.attribute_id=?', $attribute->getId()) - ->where('t1.store_id=?', 0); - - if ($attribute->getFlatAddChildData()) { - $select->where("e.is_child=?", 0); - } - return $select; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option/Collection.php index 3cfa9948..6a80e468 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Option/Collection.php @@ -20,89 +20,19 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Entity attribute option collection * - * @category Mage - * @package Mage_Eav + * @category Mage + * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Option_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Eav_Model_Mysql4_Entity_Attribute_Option_Collection + extends Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection { - protected $_optionValueTable; - - public function _construct() - { - $this->_init('eav/entity_attribute_option'); - $this->_optionValueTable = Mage::getSingleton('core/resource')->getTableName('eav/attribute_option_value'); - } - - public function setAttributeFilter($setId) - { - $this->getSelect()->where('main_table.attribute_id=?', $setId); - return $this; - } - - public function setStoreFilter($storeId=null, $useDefaultValue=true) - { - if (is_null($storeId)) { - $storeId = Mage::app()->getStore()->getId(); - } - $sortBy = 'store_default_value'; - if ($useDefaultValue) { - $this->getSelect() - ->join(array('store_default_value'=>$this->_optionValueTable), - 'store_default_value.option_id=main_table.option_id', - array('default_value'=>'value')) - ->joinLeft(array('store_value'=>$this->_optionValueTable), - 'store_value.option_id=main_table.option_id AND '.$this->getConnection()->quoteInto('store_value.store_id=?', $storeId), - array('store_value'=>'value', - 'value' => new Zend_Db_Expr('IF(store_value.value_id>0, store_value.value,store_default_value.value)'))) - ->where($this->getConnection()->quoteInto('store_default_value.store_id=?', 0)); - } - else { - $sortBy = 'store_value'; - $this->getSelect() - ->joinLeft(array('store_value'=>$this->_optionValueTable), - 'store_value.option_id=main_table.option_id AND '.$this->getConnection()->quoteInto('store_value.store_id=?', $storeId), - 'value') - ->where($this->getConnection()->quoteInto('store_value.store_id=?', $storeId)); - } - $this->setOrder("{$sortBy}.value", 'ASC'); - - return $this; - } - - public function setIdFilter($id) - { - if (is_array($id)) { - $this->getSelect()->where('main_table.option_id IN (?)', $id); - } - else { - $this->getSelect()->where('main_table.option_id=?', $id); - } - return $this; - } - - public function toOptionArray($valueKey = 'value') - { - return $this->_toOptionArray('option_id', $valueKey); - } - - public function setPositionOrder($dir = 'ASC', $sortAlpha = false) - { - $this->setOrder('main_table.sort_order', $dir); - // sort alphabetically by values in admin - if ($sortAlpha) { - $this->getSelect()->joinLeft(array('sort_alpha_value' => $this->_optionValueTable), - 'sort_alpha_value.option_id=main_table.option_id AND sort_alpha_value.store_id=0', 'value' - ); - $this->setOrder('sort_alpha_value.value', $dir); - } - return $this; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set.php index d9dc8d7c..0c7c483f 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,122 +28,10 @@ /** * Eav attribute set resource model * - * @category Mage - * @package Mage_Eav - * @author Magento Core Team + * @category Mage + * @package Mage_Eav + * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Set extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Entity_Attribute_Set extends Mage_Eav_Model_Resource_Entity_Attribute_Set { - /** - * Initialize connection - * - */ - protected function _construct() - { - $this->_init('eav/attribute_set', 'attribute_set_id'); - } - - /** - * Perform actions after object save - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Eav_Model_Mysql4_Entity_Attribute_Set - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - if ($object->getGroups()) { - /* @var $group Mage_Eav_Model_Entity_Attribute_Group */ - foreach ($object->getGroups() as $group) { - $group->setAttributeSetId($object->getId()); - if ($group->itemExists() && !$group->getId()) { - continue; - } - $group->save(); - } - } - if ($object->getRemoveGroups()) { - foreach ($object->getRemoveGroups() as $group) { - /* @var $group Mage_Eav_Model_Entity_Attribute_Group */ - $group->delete(); - } - Mage::getResourceModel('eav/entity_attribute_group')->updateDefaultGroup($object->getId()); - } - if ($object->getRemoveAttributes()) { - foreach ($object->getRemoveAttributes() as $attribute) { - /* @var $attribute Mage_Eav_Model_Entity_Attribute */ - $attribute->deleteEntity(); - } - } - return parent::_afterSave($object); - } - - /** - * Validate attribute set name - * - * @param Mage_Eav_Model_Entity_Attribute_Set $object - * @param string $name - * @return bool - */ - public function validate($object,$name) - { - $read = $this->_getReadAdapter(); - $select = $read->select()->from($this->getMainTable()) - ->where("attribute_set_name=?",$name) - ->where("entity_type_id=?",$object->getEntityTypeId()); - - if ($object->getId()) { - $select->where("attribute_set_id!=?",$object->getId()); - } - - if (!$read->fetchOne($select)) { - return true; - } - - return false; - } - - /** - * Retrieve Set info by attributes - * - * @param array $attributeIds - * @param int $setId - * @return array - */ - public function getSetInfo(array $attributeIds, $setId = null) - { - $setInfo = array(); - $attributeToSetInfo = array(); - if (count($attributeIds) > 0) { - $select = $this->_getReadAdapter()->select() - ->from( - array('entity' => $this->getTable('entity_attribute')), - array('attribute_id','attribute_set_id', 'attribute_group_id', 'sort_order')) - ->joinLeft( - array('group' => $this->getTable('attribute_group')), - 'entity.attribute_group_id=group.attribute_group_id', - array('group_sort_order' => 'sort_order')) - ->where('entity.attribute_id IN (?)', $attributeIds); - if (is_numeric($setId)) { - $select->where('entity.attribute_set_id=?', $setId); - } - $result = $this->_getReadAdapter()->fetchAll($select); - - foreach ($result as $row) { - $data = array( - 'group_id' => $row['attribute_group_id'], - 'group_sort' => $row['group_sort_order'], - 'sort' => $row['sort_order'] - ); - $attributeToSetInfo[$row['attribute_id']][$row['attribute_set_id']] = $data; - } - } - - foreach ($attributeIds as $atttibuteId) { - $setInfo[$atttibuteId] = isset($attributeToSetInfo[$atttibuteId]) - ? $attributeToSetInfo[$atttibuteId] - : array(); - } - - return $setInfo; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set/Collection.php index 375714c2..0ed3fffa 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Attribute/Set/Collection.php @@ -20,31 +20,19 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Entity_Attribute_Set_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Entity_Attribute_Set_Collection + extends Mage_Eav_Model_Resource_Entity_Attribute_Set_Collection { - public function _construct() - { - $this->_init('eav/entity_attribute_set'); - } - - public function setEntityTypeFilter($typeId) - { - $this->getSelect()->where('main_table.entity_type_id=?', $typeId); - return $this; - } - - public function toOptionArray() - { - return parent::_toOptionArray('attribute_set_id', 'attribute_set_name'); - } - - public function toOptionHash() - { - return parent::_toOptionHash('attribute_set_id', 'attribute_set_name'); - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Store.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Store.php index 93fb2ec6..b834a35e 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Store.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Store.php @@ -20,44 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Entity_Store extends Mage_Core_Model_Mysql4_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Entity_Store extends Mage_Eav_Model_Resource_Entity_Store { - protected function _construct() - { - $this->_init('eav/entity_store', 'entity_store_id'); - } - - /** - * Load an object by entity type and store - * - * @param Varien_Object $object - * @param integer $id - * @param string $field field to load by (defaults to model id) - * @return boolean - */ - public function loadByEntityStore(Mage_Core_Model_Abstract $object, $entityTypeId, $storeId) - { - $read = $this->_getWriteAdapter(); - - $select = $read->select()->from($this->getMainTable()) - ->forUpdate(true) - ->where('entity_type_id=?', $entityTypeId) - ->where('store_id=?', $storeId); - $data = $read->fetchRow($select); - - if (!$data) { - return false; - } - - $object->setData($data); - - $this->_afterLoad($object); - - return true; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php index 70eb6d2f..e17da653 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -28,45 +28,10 @@ /** * EAV entity type resource model * - * @category Mage - * @package Mage_Eav + * @category Mage + * @package Mage_Eav + * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Entity_Type extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Entity_Type extends Mage_Eav_Model_Resource_Entity_Type { - - /** - * Enter description here... - * - */ - protected function _construct() - { - $this->_init('eav/entity_type', 'entity_type_id'); - } - - /** - * Enter description here... - * - * @param unknown_type $object - * @param string $code - * @return Mage_Eav_Model_Mysql4_Entity_Type - */ - public function loadByCode($object, $code) - { - return $this->load($object, $code, 'entity_type_code'); - } - - /** - * Retrieve additional attribute table name for specified entity type - * - * @param integer $entityTypeId - * @return string - */ - public function getAdditionalAttributeTable($entityTypeId) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable(), array('additional_attribute_table')) - ->where('entity_type_id = ?', $entityTypeId); - return $this->_getReadAdapter()->fetchOne($select); - } - } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type/Collection.php index 5328e8f9..fd1bb325 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Entity/Type/Collection.php @@ -20,15 +20,18 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Eav_Model_Mysql4_Entity_Type_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Eav + * @author Magento Core Team + */ +class Mage_Eav_Model_Mysql4_Entity_Type_Collection extends Mage_Eav_Model_Resource_Entity_Type_Collection { - public function _construct() - { - $this->_init('eav/entity_type'); - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php index 100d3b9d..62872236 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,37 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Element extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Form_Element extends Mage_Eav_Model_Resource_Form_Element { - /** - * Initialize connection and define main table - * - */ - protected function _construct() - { - $this->_init('eav/form_element', 'element_id'); - $this->addUniqueField(array( - 'field' => array('type_id', 'attribute_id'), - 'title' => Mage::helper('eav')->__('Form Element with the same attribute') - )); - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @param Mage_Eav_Model_Form_Element $object - * @return Varien_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - $select = parent::_getLoadSelect($field, $value, $object); - $select->join( - $this->getTable('eav/attribute'), - $this->getTable('eav/attribute').'.attribute_id='.$this->getMainTable().'.attribute_id', - array('attribute_code', 'entity_type_id') - ); - return $select; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php index 5a4485e2..63b65266 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Element/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,107 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Element_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Eav_Model_Mysql4_Form_Element_Collection extends Mage_Eav_Model_Resource_Form_Element_Collection { - /** - * Initialize collection model - * - */ - protected function _construct() - { - $this->_init('eav/form_element'); - } - - /** - * Add Form Type filter to collection - * - * @param Mage_Eav_Model_Form_Type|int $type - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - public function addTypeFilter($type) - { - if ($type instanceof Mage_Eav_Model_Form_Type) { - $type = $type->getId(); - } - - $this->addFieldToFilter('type_id', $type); - - return $this; - } - - - /** - * Add Form Fieldset filter to collection - * - * @param Mage_Eav_Model_Form_Fieldset|int $fieldset - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - public function addFieldsetFilter($fieldset) - { - if ($fieldset instanceof Mage_Eav_Model_Form_Fieldset) { - $fieldset = $fieldset->getId(); - } - - $this->addFieldToFilter('main_table.fieldset_id', $fieldset); - - return $this; - } - - /** - * Add Attribute filter to collection - * - * @param Mage_Eav_Model_Entity_Attribute_Abstract|int $attribute - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - public function addAttributeFilter($attribute) - { - if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { - $attribute = $attribute->getId(); - } - - $this->addFieldToFilter('main_table.attribute_id', $attribute); - - return $this; - } - - /** - * Set order by element sort order - * - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - public function setSortOrder() - { - $this->setOrder('main_table.sort_order', self::SORT_ORDER_ASC); - - return $this; - } - - /** - * Join attribute data - * - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - protected function _joinAttributeData() - { - $this->getSelect()->join( - array('eav_attribute' => $this->getTable('eav/attribute')), - 'main_table.attribute_id=eav_attribute.attribute_id', - array('attribute_code', 'entity_type_id') - ); - - return $this; - } - - /** - * Load data (join attribute data) - * - * @return Mage_Eav_Model_Mysql4_Form_Element_Collection - */ - public function load($printQuery = false, $logQuery = false) - { - if (!$this->isLoaded()) { - $this->_joinAttributeData(); - } - return parent::load($printQuery, $logQuery); - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php index a4fd6566..ebce9444 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,130 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Fieldset extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Form_Fieldset extends Mage_Eav_Model_Resource_Form_Fieldset { - /** - * Initialize connection and define main table - * - */ - protected function _construct() - { - $this->_init('eav/form_fieldset', 'fieldset_id'); - $this->addUniqueField(array( - 'field' => array('type_id', 'code'), - 'title' => Mage::helper('eav')->__('Form Fieldset with the same code') - )); - } - - /** - * After save (save labels) - * - * @param Mage_Eav_Model_Form_Fieldset $object - * @return Mage_Eav_Model_Mysql4_Form_Fieldset - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - if ($object->hasLabels()) { - $new = $object->getLabels(); - $old = $this->getLabels($object); - - $write = $this->_getWriteAdapter(); - - $insert = array_diff(array_keys($new), array_keys($old)); - $delete = array_diff(array_keys($old), array_keys($new)); - $update = array(); - - foreach ($new as $storeId => $label) { - if (isset($old[$storeId]) && $old[$storeId] != $label) { - $update[$storeId] = $label; - } else if (isset($old[$storeId]) && empty($label)) { - $delete[] = $storeId; - } - } - - if (!empty($insert)) { - $data = array(); - foreach ($insert as $storeId) { - $label = $new[$storeId]; - if (empty($label)) { - continue; - } - $data[] = array( - 'fieldset_id' => (int)$object->getId(), - 'store_id' => (int)$storeId, - 'label' => $label - ); - } - if ($data) { - $write->insertMultiple($this->getTable('eav/form_fieldset_label'), $data); - } - } - - if (!empty($delete)) { - $where = join(' AND ', array( - $write->quoteInto('fieldset_id=?', $object->getId()), - $write->quoteInto('store_id IN(?)', $delete) - )); - $write->delete($this->getTable('eav/form_fieldset_label'), $where); - } - - if (!empty($update)) { - foreach ($update as $storeId => $label) { - $bind = array( - 'label' => $label - ); - $where = join(' AND ', array( - $write->quoteInto('fieldset_id=?', $object->getId()), - $write->quoteInto('store_id=?', $storeId) - )); - $write->update($this->getTable('eav/form_fieldset_label'), $bind, $where); - } - } - } - - return parent::_afterSave($object); - } - - /** - * Retrieve fieldset labels for stores - * - * @param Mage_Eav_Model_Form_Fieldset $object - * @return array - */ - public function getLabels($object) - { - if (!$object->getId()) { - return array(); - } - - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/form_fieldset_label'), array('store_id', 'label')) - ->where('fieldset_id=?', $object->getId()); - return $this->_getReadAdapter()->fetchPairs($select); - } - - /** - * Retrieve select object for load object data - * - * @param string $field - * @param mixed $value - * @param Mage_Eav_Model_Form_Fieldset $object - * @return Varien_Db_Select - */ - protected function _getLoadSelect($field, $value, $object) - { - $select = parent::_getLoadSelect($field, $value, $object); - $select->joinLeft( - array('default_label' => $this->getTable('eav/form_fieldset_label')), - $this->getMainTable().'fieldset_id=default_label.fieldset_id AND default_label.store_id=0', - array()) - ->joinLeft( - array('store_label' => $this->getTable('eav/form_fieldset_label')), - $this->getMainTable().'fieldset_id=store_label.fieldset_id AND default_label.store_id=' - .(int)$object->getStoreId(), - array('label' => new Zend_Db_Expr('IFNULL(store_label.label, default_label.label)')) - ); - - return $select; - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php index 6757b3a3..e044c1c5 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Fieldset/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,98 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Fieldset_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Eav_Model_Mysql4_Form_Fieldset_Collection extends Mage_Eav_Model_Resource_Form_Fieldset_Collection { - /** - * Store scope ID - * - * @var int - */ - protected $_storeId; - - /** - * Initialize collection model - * - */ - protected function _construct() - { - $this->_init('eav/form_fieldset'); - } - - /** - * Add Form Type filter to collection - * - * @param Mage_Eav_Model_Form_Type|int $type - * @return Mage_Eav_Model_Mysql4_Form_Fieldset_Collection - */ - public function addTypeFilter($type) - { - if ($type instanceof Mage_Eav_Model_Form_Type) { - $type = $type->getId(); - } - - $this->addFieldToFilter('type_id', $type); - - return $this; - } - - /** - * Set order by fieldset sort order - * - * @return Mage_Eav_Model_Mysql4_Form_Fieldset_Collection - */ - public function setSortOrder() - { - $this->setOrder('sort_order', self::SORT_ORDER_ASC); - - return $this; - } - - /** - * Retrieve label store scope - * - * @return int - */ - public function getStoreId() - { - if (is_null($this->_storeId)) { - return Mage::app()->getStore()->getId(); - } - return $this->_storeId; - } - - /** - * Set store scope ID - * - * @param int $storeId - * @return Mage_Eav_Model_Mysql4_Form_Fieldset_Collection - */ - public function setStoreId($storeId) - { - $this->_storeId = $storeId; - return $this; - } - - /** - * Initialize select object - * - */ - protected function _initSelect() - { - parent::_initSelect(); - - $this->getSelect()->join( - array('default_label' => $this->getTable('eav/form_fieldset_label')), - 'main_table.fieldset_id=default_label.fieldset_id AND default_label.store_id=0', - array()); - if ($this->getStoreId() == 0) { - $this->getSelect()->columns('label', 'default_label'); - } else { - $this->getSelect()->joinLeft( - array('store_label' => $this->getTable('eav/form_fieldset_label')), - 'main_table.fieldset_id=store_label.fieldset_id AND store_label.store_id='.(int)$this->getStoreId(), - array('label' => new Zend_Db_Expr('IFNULL(store_label.label, default_label.label)')) - ); - } - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php index b8d762f6..15090f4b 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,116 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Type extends Mage_Core_Model_Mysql4_Abstract +class Mage_Eav_Model_Mysql4_Form_Type extends Mage_Eav_Model_Resource_Form_Type { - /** - * Initialize connection and define main table - * - */ - protected function _construct() - { - $this->_init('eav/form_type', 'type_id'); - $this->addUniqueField(array( - 'field' => array('code', 'theme', 'store_id'), - 'title' => Mage::helper('eav')->__('Form Type with the same code') - )); - } - - /** - * Load an object - * - * @param Mage_Eav_Model_Form_Type $object - * @param mixed $value - * @param string $field field to load by (defaults to model id) - * @return Mage_Eav_Model_Mysql4_Form_Type - */ - public function load(Mage_Core_Model_Abstract $object, $value, $field = null) - { - if (is_null($field) && !is_numeric($value)) { - $field = 'code'; - } - return parent::load($object, $value, $field); - } - - /** - * Retrieve form type entity types - * - * @param Mage_Eav_Model_Form_Type $object - * @return array - */ - public function getEntityTypes($object) - { - if (!$object->getId()) { - return array(); - } - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/form_type_entity'), 'entity_type_id') - ->where('type_id=?', $object->getId()); - return $this->_getReadAdapter()->fetchCol($select); - } - - /** - * Save entity types after save form type - * - * @param Mage_Eav_Model_Form_Type $object - * @see Mage_Core_Model_Mysql4_Abstract#_afterSave($object) - * @return Mage_Eav_Model_Mysql4_Form_Type - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - if ($object->hasEntityTypes()) { - $new = $object->getEntityTypes(); - $old = $this->getEntityTypes($object); - - $insert = array_diff($new, $old); - $delete = array_diff($old, $new); - - $write = $this->_getWriteAdapter(); - - if (!empty($insert)) { - $data = array(); - foreach ($insert as $entityId) { - if (empty($entityId)) { - continue; - } - $data[] = array( - 'entity_type_id' => (int)$entityId, - 'type_id' => $object->getId() - ); - } - if ($data) { - $write->insertMultiple($this->getTable('eav/form_type_entity'), $data); - } - } - if (!empty($delete)) { - $where = join(' AND ', array( - $write->quoteInto('type_id=?', $object->getId()), - $write->quoteInto('entity_type_id IN(?)', $delete) - )); - $write->delete($this->getTable('eav/form_type_entity'), $where); - } - } - - return parent::_afterSave($object); - } - - /** - * Retrieve form type filtered by given attribute - * - * @param Mage_Eav_Model_Entity_Attribute_Abstract|int $attribute - * @return array - */ - public function getFormTypesByAttribute($attribute) - { - if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { - $attribute = $attribute->getId(); - } - if (!$attribute) { - return array(); - } - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('eav/form_element')) - ->where('attribute_id = ?', $attribute); - return $this->_getReadAdapter()->fetchAll($select); - } } diff --git a/app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php index 594d7212..758d95b8 100644 --- a/app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php +++ b/app/code/core/Mage/Eav/Model/Mysql4/Form/Type/Collection.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,45 +32,6 @@ * @package Mage_Eav * @author Magento Core Team */ -class Mage_Eav_Model_Mysql4_Form_Type_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Eav_Model_Mysql4_Form_Type_Collection extends Mage_Eav_Model_Resource_Form_Type_Collection { - /** - * Initialize collection model - * - */ - protected function _construct() - { - $this->_init('eav/form_type'); - } - - /** - * Convert items array to array for select options - * - * @return array - */ - public function toOptionArray() - { - return $this->_toOptionArray('type_id', 'label'); - } - - /** - * Add Entity type filter to collection - * - * @param Mage_Eav_Model_Entity_Type|int $entity - * @return Mage_Eav_Model_Mysql4_Form_Type_Collection - */ - public function addEntityTypeFilter($entity) - { - if ($entity instanceof Mage_Eav_Model_Entity_Type) { - $entity = $entity->getId(); - } - - $this->getSelect()->join( - array('form_type_entity' => $this->getTable('eav/form_type_entity')), - 'main_table.type_id=form_type_entity.type_id', - array()) - ->where('form_type_entity.entity_type_id=?', $entity); - - return $this; - } } diff --git a/app/code/core/Mage/Eav/Model/Resource/Attribute.php b/app/code/core/Mage/Eav/Model/Resource/Attribute.php new file mode 100755 index 00000000..b111bed5 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Attribute.php @@ -0,0 +1,209 @@ + + */ +abstract class Mage_Eav_Model_Resource_Attribute extends Mage_Eav_Model_Resource_Entity_Attribute +{ + /** + * Get EAV website table + * + * Get table, where website-dependent attribute parameters are stored + * If realization doesn't demand this functionality, let this function just return null + * + * @return string|null + */ + abstract protected function _getEavWebsiteTable(); + + /** + * Get Form attribute table + * + * Get table, where dependency between form name and attribute ids are stored + * + * @return string|null + */ + abstract protected function _getFormAttributeTable(); + + /** + * Perform actions before object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Resource_Attribute + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $validateRules = $object->getData('validate_rules'); + if (is_array($validateRules)) { + $object->setData('validate_rules', serialize($validateRules)); + } + return parent::_beforeSave($object); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Core_Model_Abstract $object + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $websiteId = (int)$object->getWebsite()->getId(); + if ($websiteId) { + $adapter = $this->_getReadAdapter(); + $columns = array(); + $scopeTable = $this->_getEavWebsiteTable(); + $describe = $adapter->describeTable($scopeTable); + unset($describe['attribute_id']); + foreach (array_keys($describe) as $columnName) { + $columns['scope_' . $columnName] = $columnName; + } + $conditionSql = $adapter->quoteInto( + $this->getMainTable() . '.attribute_id = scope_table.attribute_id AND scope_table.website_id =?', + $websiteId); + $select->joinLeft( + array('scope_table' => $scopeTable), + $conditionSql, + $columns + ); + } + + return $select; + } + + /** + * Save attribute/form relations after attribute save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Resource_Attribute + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $forms = $object->getData('used_in_forms'); + $adapter = $this->_getWriteAdapter(); + if (is_array($forms)) { + $where = array('attribute_id=?' => $object->getId()); + $adapter->delete($this->_getFormAttributeTable(), $where); + + $data = array(); + foreach ($forms as $formCode) { + $data[] = array( + 'form_code' => $formCode, + 'attribute_id' => (int)$object->getId() + ); + } + + if ($data) { + $adapter->insertMultiple($this->_getFormAttributeTable(), $data); + } + } + + // update sort order + if (!$object->isObjectNew() && $object->dataHasChangedFor('sort_order')) { + $data = array('sort_order' => $object->getSortOrder()); + $where = array('attribute_id=?' => (int)$object->getId()); + $adapter->update($this->getTable('eav/entity_attribute'), $data, $where); + } + + // save scope attributes + $websiteId = (int)$object->getWebsite()->getId(); + if ($websiteId) { + $table = $this->_getEavWebsiteTable(); + $describe = $this->_getReadAdapter()->describeTable($table); + $data = array(); + if (!$object->getScopeWebsiteId() || $object->getScopeWebsiteId() != $websiteId) { + $data = $this->getScopeValues($object); + } + + $data['attribute_id'] = (int)$object->getId(); + $data['website_id'] = (int)$websiteId; + unset($describe['attribute_id']); + unset($describe['website_id']); + + $updateColumns = array(); + foreach (array_keys($describe) as $columnName) { + $data[$columnName] = $object->getData('scope_' . $columnName); + $updateColumns[] = $columnName; + } + + $adapter->insertOnDuplicate($table, $data, $updateColumns); + } + + return parent::_afterSave($object); + } + + /** + * Return scope values for attribute and website + * + * @param Mage_Eav_Model_Attribute $object + * @return array + */ + public function getScopeValues(Mage_Eav_Model_Attribute $object) + { + $adapter = $this->_getReadAdapter(); + $bind = array( + 'attribute_id' => (int)$object->getId(), + 'website_id' => (int)$object->getWebsite()->getId() + ); + $select = $adapter->select() + ->from($this->_getEavWebsiteTable()) + ->where('attribute_id = :attribute_id') + ->where('website_id = :website_id') + ->limit(1); + $result = $adapter->fetchRow($select, $bind); + + if (!$result) { + $result = array(); + } + + return $result; + } + + /** + * Return forms in which the attribute + * + * @param Mage_Core_Model_Abstract $object + * @return array + */ + public function getUsedInForms(Mage_Core_Model_Abstract $object) + { + $adapter = $this->_getReadAdapter(); + $bind = array('attribute_id' => (int)$object->getId()); + $select = $adapter->select() + ->from($this->_getFormAttributeTable(), 'form_code') + ->where('attribute_id = :attribute_id'); + + return $adapter->fetchCol($select, $bind); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Attribute/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Attribute/Collection.php new file mode 100755 index 00000000..6f5ef4e8 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Attribute/Collection.php @@ -0,0 +1,244 @@ + + */ +abstract class Mage_Eav_Model_Resource_Attribute_Collection + extends Mage_Eav_Model_Resource_Entity_Attribute_Collection +{ + /** + * code of password hash in customer's EAV tables + */ + const EAV_CODE_PASSWORD_HASH = 'password_hash'; + + /** + * Current website scope instance + * + * @var Mage_Core_Model_Website + */ + protected $_website; + + /** + * Attribute Entity Type Filter + * + * @var Mage_Eav_Model_Entity_Type + */ + protected $_entityType; + + /** + * Default attribute entity type code + * + * @return string + */ + abstract protected function _getEntityTypeCode(); + + /** + * Get EAV website table + * + * Get table, where website-dependent attribute parameters are stored + * If realization doesn't demand this functionality, let this function just return null + * + * @return string|null + */ + abstract protected function _getEavWebsiteTable(); + + /** + * Return eav entity type instance + * + * @return Mage_Eav_Model_Entity_Type + */ + public function getEntityType() + { + if ($this->_entityType === null) { + $this->_entityType = Mage::getSingleton('eav/config')->getEntityType($this->_getEntityTypeCode()); + } + return $this->_entityType; + } + + /** + * Set Website scope + * + * @param Mage_Core_Model_Website|int $website + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + public function setWebsite($website) + { + $this->_website = Mage::app()->getWebsite($website); + $this->addBindParam('scope_website_id', $this->_website->getId()); + return $this; + } + + /** + * Return current website scope instance + * + * @return Mage_Core_Model_Website + */ + public function getWebsite() + { + if ($this->_website === null) { + $this->_website = Mage::app()->getStore()->getWebsite(); + } + return $this->_website; + } + + /** + * Initialize collection select + * + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + protected function _initSelect() + { + $select = $this->getSelect(); + $connection = $this->getConnection(); + $entityType = $this->getEntityType(); + $extraTable = $entityType->getAdditionalAttributeTable(); + $mainDescribe = $this->getConnection()->describeTable($this->getResource()->getMainTable()); + $mainColumns = array(); + + foreach (array_keys($mainDescribe) as $columnName) { + $mainColumns[$columnName] = $columnName; + } + + $select->from(array('main_table' => $this->getResource()->getMainTable()), $mainColumns); + + // additional attribute data table + $extraDescribe = $connection->describeTable($this->getTable($extraTable)); + $extraColumns = array(); + foreach (array_keys($extraDescribe) as $columnName) { + if (isset($mainColumns[$columnName])) { + continue; + } + $extraColumns[$columnName] = $columnName; + } + + $this->addBindParam('mt_entity_type_id', (int)$entityType->getId()); + $select + ->join( + array('additional_table' => $this->getTable($extraTable)), + 'additional_table.attribute_id = main_table.attribute_id', + $extraColumns) + ->where('main_table.entity_type_id = :mt_entity_type_id'); + + // scope values + + $scopeDescribe = $connection->describeTable($this->_getEavWebsiteTable()); + unset($scopeDescribe['attribute_id']); + $scopeColumns = array(); + foreach (array_keys($scopeDescribe) as $columnName) { + if ($columnName == 'website_id') { + $scopeColumns['scope_website_id'] = $columnName; + } else { + if (isset($mainColumns[$columnName])) { + $alias = sprintf('scope_%s', $columnName); + $expression = $connection->getCheckSql('main_table.%s IS NULL', + 'scope_table.%s', 'main_table.%s'); + $expression = sprintf($expression, $columnName, $columnName, $columnName); + $this->addFilterToMap($columnName, $expression); + $scopeColumns[$alias] = $columnName; + } elseif (isset($extraColumns[$columnName])) { + $alias = sprintf('scope_%s', $columnName); + $expression = $connection->getCheckSql('additional_table.%s IS NULL', + 'scope_table.%s', 'additional_table.%s'); + $expression = sprintf($expression, $columnName, $columnName, $columnName); + $this->addFilterToMap($columnName, $expression); + $scopeColumns[$alias] = $columnName; + } + } + } + + $select->joinLeft( + array('scope_table' => $this->_getEavWebsiteTable()), + 'scope_table.attribute_id = main_table.attribute_id AND scope_table.website_id = :scope_website_id', + $scopeColumns + ); + $websiteId = $this->getWebsite() ? (int)$this->getWebsite()->getId() : 0; + $this->addBindParam('scope_website_id', $websiteId); + + return $this; + } + + /** + * Specify attribute entity type filter. + * Entity type is defined. + * + * @param int $type + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + public function setEntityTypeFilter($type) + { + return $this; + } + + /** + * Specify filter by "is_visible" field + * + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + public function addVisibleFilter() + { + return $this->addFieldToFilter('is_visible', 1); + } + + /** + * Exclude system hidden attributes + * + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + public function addSystemHiddenFilter() + { + $field = '(CASE WHEN additional_table.is_system = 1 AND additional_table.is_visible = 0 THEN 1 ELSE 0 END)'; + return $this->addFieldToFilter($field, 0); + } + + /** + * Exclude system hidden attributes but include password hash + * + * @return Mage_Customer_Model_Entity_Attribute_Collection + */ + public function addSystemHiddenFilterWithPasswordHash() + { + $field = '(CASE WHEN additional_table.is_system = 1 AND additional_table.is_visible = 0 + AND main_table.attribute_code != "' . self::EAV_CODE_PASSWORD_HASH . '" THEN 1 ELSE 0 END)'; + $this->addFieldToFilter($field, 0); + return $this; + } + + /** + * Add exclude hidden frontend input attribute filter to collection + * + * @return Mage_Eav_Model_Resource_Attribute_Collection + */ + public function addExcludeHiddenFrontendFilter() + { + return $this->addFieldToFilter('main_table.frontend_input', array('neq' => 'hidden')); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Config.php b/app/code/core/Mage/Eav/Model/Resource/Config.php new file mode 100755 index 00000000..e651e0be --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Config.php @@ -0,0 +1,134 @@ + + */ +class Mage_Eav_Model_Resource_Config extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Array of entity types + * + * @var array + */ + protected static $_entityTypes = array(); + + /** + * Array of attributes + * + * @var array + */ + protected static $_attributes = array(); + + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/entity_type', 'entity_type_id'); + } + + /** + * Load all entity types + * + * @return Mage_Eav_Model_Resource_Config + */ + protected function _loadTypes() + { + $adapter = $this->_getReadAdapter(); + if (!$adapter) { + return $this; + } + if (empty(self::$_entityTypes)) { + $select = $adapter->select()->from($this->getMainTable()); + $data = $adapter->fetchAll($select); + foreach ($data as $row) { + self::$_entityTypes['by_id'][$row['entity_type_id']] = $row; + self::$_entityTypes['by_code'][$row['entity_type_code']] = $row; + } + } + + return $this; + } + + /** + * Load attribute types + * + * @param ind $typeId + * @return array + */ + protected function _loadTypeAttributes($typeId) + { + if (!isset(self::$_attributes[$typeId])) { + $adapter = $this->_getReadAdapter(); + $bind = array('entity_type_id' => $typeId); + $select = $adapter->select() + ->from($this->getTable('eav/attribute')) + ->where('entity_type_id = :entity_type_id'); + + self::$_attributes[$typeId] = $adapter->fetchAll($select, $bind); + } + + return self::$_attributes[$typeId]; + } + + /** + * Retrieve entity type data + * + * @param string $entityType + * @return array + */ + public function fetchEntityTypeData($entityType) + { + $this->_loadTypes(); + + if (is_numeric($entityType)) { + $info = isset(self::$_entityTypes['by_id'][$entityType]) + ? self::$_entityTypes['by_id'][$entityType] : null; + } else { + $info = isset(self::$_entityTypes['by_code'][$entityType]) + ? self::$_entityTypes['by_code'][$entityType] : null; + } + + $data = array(); + if ($info) { + $data['entity'] = $info; + $attributes = $this->_loadTypeAttributes($info['entity_type_id']); + $data['attributes'] = array(); + foreach ($attributes as $attribute) { + $data['attributes'][$attribute['attribute_id']] = $attribute; + $data['attributes'][$attribute['attribute_code']] = $attribute['attribute_id']; + } + } + + return $data; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute.php new file mode 100755 index 00000000..9c3bd6c2 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute.php @@ -0,0 +1,551 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Eav Entity attributes cache + * + * @var array + */ + protected static $_entityAttributes = array(); + + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('eav/attribute', 'attribute_id'); + } + + /** + * Initialize unique fields + * + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _initUniqueFields() + { + $this->_uniqueFields = array(array( + 'field' => array('attribute_code', 'entity_type_id'), + 'title' => Mage::helper('eav')->__('Attribute with the same code') + )); + return $this; + } + + /** + * Load all entity type attributes + * + * @param int $entityTypeId + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _loadTypeAttributes($entityTypeId) + { + if (!isset(self::$_entityAttributes[$entityTypeId])) { + $adapter = $this->_getReadAdapter(); + $bind = array(':entity_type_id' => $entityTypeId); + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('entity_type_id = :entity_type_id'); + + $data = $adapter->fetchAll($select, $bind); + foreach ($data as $row) { + self::$_entityAttributes[$entityTypeId][$row['attribute_code']] = $row; + } + } + + return $this; + } + + /** + * Load attribute data by attribute code + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @param int $entityTypeId + * @param string $code + * @return boolean + */ + public function loadByCode(Mage_Core_Model_Abstract $object, $entityTypeId, $code) + { + $bind = array(':entity_type_id' => $entityTypeId); + $select = $this->_getLoadSelect('attribute_code', $code, $object) + ->where('entity_type_id = :entity_type_id'); + $data = $this->_getReadAdapter()->fetchRow($select, $bind); + + if ($data) { + $object->setData($data); + $this->_afterLoad($object); + return true; + } + return false; + } + + /** + * Retrieve Max Sort order for attribute in group + * + * @param Mage_Core_Model_Abstract $object + * @return int + */ + private function _getMaxSortOrder(Mage_Core_Model_Abstract $object) + { + if (intval($object->getAttributeGroupId()) > 0) { + $adapter = $this->_getReadAdapter(); + $bind = array( + ':attribute_set_id' => $object->getAttributeSetId(), + ':attribute_group_id' => $object->getAttributeGroupId() + ); + $select = $adapter->select() + ->from($this->getTable('eav/entity_attribute'), new Zend_Db_Expr("MAX(sort_order)")) + ->where('attribute_set_id = :attribute_set_id') + ->where('attribute_group_id = :attribute_group_id'); + + return $adapter->fetchOne($select, $bind); + } + + return 0; + } + + /** + * Delete entity + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + public function deleteEntity(Mage_Core_Model_Abstract $object) + { + if (!$object->getEntityAttributeId()) { + return $this; + } + + $this->_getWriteAdapter()->delete($this->getTable('eav/entity_attribute'), array( + 'entity_attribute_id = ?' => $object->getEntityAttributeId() + )); + + return $this; + } + + /** + * Validate attribute data before save + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + $frontendLabel = $object->getFrontendLabel(); + if (is_array($frontendLabel)) { + if (!isset($frontendLabel[0]) || is_null($frontendLabel[0]) || $frontendLabel[0] == '') { + Mage::throwException(Mage::helper('eav')->__('Frontend label is not defined')); + } + $object->setFrontendLabel($frontendLabel[0]) + ->setStoreLabels($frontendLabel); + } + + /** + * @todo need use default source model of entity type !!! + */ + if (!$object->getId()) { + if ($object->getFrontendInput() == 'select') { + $object->setSourceModel('eav/entity_attribute_source_table'); + } + } + + return parent::_beforeSave($object); + } + + /** + * Save additional attribute data after save attribute + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $this->_saveStoreLabels($object) + ->_saveAdditionalAttributeData($object) + ->saveInSetIncluding($object) + ->_saveOption($object); + + return parent::_afterSave($object); + } + + /** + * Save store labels + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _saveStoreLabels(Mage_Core_Model_Abstract $object) + { + $storeLabels = $object->getStoreLabels(); + if (is_array($storeLabels)) { + $adapter = $this->_getWriteAdapter(); + if ($object->getId()) { + $condition = array('attribute_id =?' => $object->getId()); + $adapter->delete($this->getTable('eav/attribute_label'), $condition); + } + foreach ($storeLabels as $storeId => $label) { + if ($storeId == 0 || !strlen($label)) { + continue; + } + $bind = array ( + 'attribute_id' => $object->getId(), + 'store_id' => $storeId, + 'value' => $label + ); + $adapter->insert($this->getTable('eav/attribute_label'), $bind); + } + } + + return $this; + } + + /** + * Save additional data of attribute + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _saveAdditionalAttributeData(Mage_Core_Model_Abstract $object) + { + $additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId()); + if ($additionalTable) { + $adapter = $this->_getWriteAdapter(); + $data = $this->_prepareDataForTable($object, $this->getTable($additionalTable)); + $bind = array(':attribute_id' => $object->getId()); + $select = $adapter->select() + ->from($this->getTable($additionalTable), array('attribute_id')) + ->where('attribute_id = :attribute_id'); + $result = $adapter->fetchOne($select, $bind); + if ($result) { + $where = array('attribute_id = ?' => $object->getId()); + $adapter->update($this->getTable($additionalTable), $data, $where); + } else { + $adapter->insert($this->getTable($additionalTable), $data); + } + } + + return $this; + } + + /** + * Save in set including + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + public function saveInSetIncluding(Mage_Core_Model_Abstract $object) + { + $attributeId = (int) $object->getId(); + $setId = (int) $object->getAttributeSetId(); + $groupId = (int) $object->getAttributeGroupId(); + + if ($setId && $groupId && $object->getEntityTypeId()) { + $adapter = $this->_getWriteAdapter(); + $table = $this->getTable('eav/entity_attribute'); + + $sortOrder = (($object->getSortOrder()) ? $object->getSortOrder() : $this->_getMaxSortOrder($object) + 1); + $data = array( + 'entity_type_id' => $object->getEntityTypeId(), + 'attribute_set_id' => $setId, + 'attribute_group_id' => $groupId, + 'attribute_id' => $attributeId, + 'sort_order' => $sortOrder + ); + + $where = array( + 'attribute_id =?' => $attributeId, + 'attribute_set_id =?' => $setId + ); + + $adapter->delete($table, $where); + $adapter->insert($table, $data); + } + + return $this; + } + + /** + * Save attribute options + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _saveOption(Mage_Core_Model_Abstract $object) + { + $option = $object->getOption(); + if (is_array($option)) { + $adapter = $this->_getWriteAdapter(); + $optionTable = $this->getTable('eav/attribute_option'); + $optionValueTable = $this->getTable('eav/attribute_option_value'); + + $stores = Mage::app()->getStores(true); + if (isset($option['value'])) { + $attributeDefaultValue = array(); + if (!is_array($object->getDefault())) { + $object->setDefault(array()); + } + + foreach ($option['value'] as $optionId => $values) { + $intOptionId = (int) $optionId; + if (!empty($option['delete'][$optionId])) { + if ($intOptionId) { + $adapter->delete($optionTable, array('option_id = ?' => $intOptionId)); + } + continue; + } + + $sortOrder = !empty($option['order'][$optionId]) ? $option['order'][$optionId] : 0; + if (!$intOptionId) { + $data = array( + 'attribute_id' => $object->getId(), + 'sort_order' => $sortOrder + ); + $adapter->insert($optionTable, $data); + $intOptionId = $adapter->lastInsertId($optionTable); + } else { + $data = array('sort_order' => $sortOrder); + $where = array('option_id =?' => $intOptionId); + $adapter->update($optionTable, $data, $where); + } + + if (in_array($optionId, $object->getDefault())) { + if ($object->getFrontendInput() == 'multiselect') { + $attributeDefaultValue[] = $intOptionId; + } elseif ($object->getFrontendInput() == 'select') { + $attributeDefaultValue = array($intOptionId); + } + } + + // Default value + if (!isset($values[0])) { + Mage::throwException(Mage::helper('eav')->__('Default option value is not defined')); + } + + $adapter->delete($optionValueTable, array('option_id =?' => $intOptionId)); + foreach ($stores as $store) { + if (isset($values[$store->getId()]) + && (!empty($values[$store->getId()]) + || $values[$store->getId()] == "0") + ) { + $data = array( + 'option_id' => $intOptionId, + 'store_id' => $store->getId(), + 'value' => $values[$store->getId()], + ); + $adapter->insert($optionValueTable, $data); + } + } + } + $bind = array('default_value' => implode(',', $attributeDefaultValue)); + $where = array('attribute_id =?' => $object->getId()); + $adapter->update($this->getMainTable(), $bind, $where); + } + } + + return $this; + } + + + /** + * Retrieve attribute id by entity type code and attribute code + * + * @param string $entityType + * @param string $code + * @return int + */ + public function getIdByCode($entityType, $code) + { + $adapter = $this->_getReadAdapter(); + $bind = array( + ':entity_type_code' => $entityType, + ':attribute_code' => $code + ); + $select = $adapter->select() + ->from(array('a' => $this->getTable('eav/attribute')), array('a.attribute_id')) + ->join( + array('t' => $this->getTable('eav/entity_type')), + 'a.entity_type_id = t.entity_type_id', + array()) + ->where('t.entity_type_code = :entity_type_code') + ->where('a.attribute_code = :attribute_code'); + + return $adapter->fetchOne($select, $bind); + } + + /** + * Retrieve attribute codes by front-end type + * + * @param string $frontendType + * @return array + */ + public function getAttributeCodesByFrontendType($frontendType) + { + $adapter = $this->_getReadAdapter(); + $bind = array(':frontend_input' => $frontendType); + $select = $adapter->select() + ->from($this->getTable('eav/attribute'), 'attribute_code') + ->where('frontend_input = :frontend_input'); + + return $adapter->fetchCol($select, $bind); + } + + /** + * Retrieve Select For Flat Attribute update + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param int $storeId + * @return Varien_Db_Select + */ + public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $storeId) + { + $adapter = $this->_getReadAdapter(); + $joinConditionTemplate = "%s.entity_id=%s.entity_id" + ." AND %s.entity_type_id = ".$attribute->getEntityTypeId() + ." AND %s.attribute_id = ".$attribute->getId() + ." AND %s.store_id = %d"; + $joinCondition = sprintf($joinConditionTemplate, + 'e', 't1', 't1', 't1', 't1', + Mage_Core_Model_App::ADMIN_STORE_ID); + if ($attribute->getFlatAddChildData()) { + $joinCondition .= ' AND e.child_id = t1.entity_id'; + } + + $valueExpr = $adapter->getCheckSql('t2.value_id > 0', 't2.value', 't1.value'); + + /** @var $select Varien_Db_Select */ + $select = $adapter->select() + ->joinLeft( + array('t1' => $attribute->getBackend()->getTable()), + $joinCondition, + array()) + ->joinLeft( + array('t2' => $attribute->getBackend()->getTable()), + sprintf($joinConditionTemplate, 't1', 't2', 't2', 't2', 't2', $storeId), + array($attribute->getAttributeCode() => $valueExpr)); + if ($attribute->getFlatAddChildData()) { + $select->where("e.is_child = ?", 0); + } + + return $select; + } + + /** + * Returns the column descriptions for a table + * + * @param string $table + * @return array + */ + public function describeTable($table) + { + return $this->_getReadAdapter()->describeTable($table); + } + + /** + * Retrieve additional attribute table name for specified entity type + * + * @param integer $entityTypeId + * @return string + */ + public function getAdditionalAttributeTable($entityTypeId) + { + return Mage::getResourceSingleton('eav/entity_type')->getAdditionalAttributeTable($entityTypeId); + } + + /** + * Load additional attribute data. + * Load label of current active store + * + * @param Mage_Eav_Model_Entity_Attribute $object + * @return Mage_Eav_Model_Resource_Entity_Attribute + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + /** @var $entityType Mage_Eav_Model_Entity_Type */ + $entityType = $object->getData('entity_type'); + if ($entityType) { + $additionalTable = $entityType->getAdditionalAttributeTable(); + } else { + $additionalTable = $this->getAdditionalAttributeTable($object->getEntityTypeId()); + } + + if ($additionalTable) { + $adapter = $this->_getReadAdapter(); + $bind = array(':attribute_id' => $object->getId()); + $select = $adapter->select() + ->from($this->getTable($additionalTable)) + ->where('attribute_id = :attribute_id'); + + $result = $adapter->fetchRow($select, $bind); + if ($result) { + $object->addData($result); + } + } + + return $this; + } + + /** + * Retrieve store labels by given attribute id + * + * @param integer $attributeId + * @return array + */ + public function getStoreLabelsByAttributeId($attributeId) + { + $adapter = $this->_getReadAdapter(); + $bind = array(':attribute_id' => $attributeId); + $select = $adapter->select() + ->from($this->getTable('eav/attribute_label'), array('store_id', 'value')) + ->where('attribute_id = :attribute_id'); + + return $adapter->fetchPairs($select, $bind); + } + + /** + * Load by given attributes ids and return only exist attribute ids + * + * @param array $attributeIds + * @return array + */ + public function getValidAttributeIds($attributeIds) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), array('attribute_id')) + ->where('attribute_id IN (?)', $attributeIds); + + return $adapter->fetchCol($select); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Collection.php new file mode 100755 index 00000000..395d950b --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Collection.php @@ -0,0 +1,435 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Add attribute set info flag + * + * @var boolean + */ + protected $_addSetInfoFlag = false; + + /** + * Resource model initialization + * + */ + protected function _construct() + { + $this->_init('eav/entity_attribute'); + } + + /** + * Return array of fields to load attribute values + * + * @return array + */ + protected function _getLoadDataFields() + { + return array( + 'attribute_id', + 'entity_type_id', + 'attribute_code', + 'attribute_model', + 'backend_model', + 'backend_type', + 'backend_table', + 'frontend_input', + 'source_model', + ); + } + + /** + * Specify select columns which are used for load arrtibute values + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function useLoadDataFields() + { + $this->getSelect()->reset(Zend_Db_Select::COLUMNS); + $this->getSelect()->columns($this->_getLoadDataFields()); + + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param Mage_Eav_Model_Entity_Type | int $type + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setEntityTypeFilter($type) + { + if ($type instanceof Mage_Eav_Model_Entity_Type) { + $additionalTable = $type->getAdditionalAttributeTable(); + $id = $type->getId(); + } else { + $additionalTable = $this->getResource()->getAdditionalAttributeTable($type); + $id = $type; + } + $this->addFieldToFilter('main_table.entity_type_id', $id); + if ($additionalTable) { + $this->join( + array('additional_table' => $additionalTable), + 'additional_table.attribute_id = main_table.attribute_id' + ); + } + + return $this; + } + + /** + * Specify attribute set filter + * + * @param int $setId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setAttributeSetFilter($setId) + { + if (is_array($setId)) { + if (!empty($setId)) { + $this->join( + 'entity_attribute', + 'entity_attribute.attribute_id = main_table.attribute_id', + 'attribute_id' + ); + $this->addFieldToFilter('entity_attribute.attribute_set_id', array('in' => $setId)); + $this->addAttributeGrouping(); + $this->_useAnalyticFunction = true; + } + } elseif ($setId) { + $this->join( + 'entity_attribute', + 'entity_attribute.attribute_id = main_table.attribute_id' + ); + $this->addFieldToFilter('entity_attribute.attribute_set_id', $setId); + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + } + + return $this; + } + + /** + * Specify multiple attribute sets filter + * Result will be ordered by sort_order + * + * @param array $setIds + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setAttributeSetsFilter(array $setIds) + { + $this->getSelect()->distinct(true); + $this->join( + array('entity_attribute' => $this->getTable('eav/entity_attribute')), + 'entity_attribute.attribute_id = main_table.attribute_id', + 'attribute_id' + ); + $this->addFieldToFilter('entity_attribute.attribute_set_id', array('in' => $setIds)); + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Filter for selecting of attributes that is in all sets + * + * @param array $setIds + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setInAllAttributeSetsFilter(array $setIds) + { + foreach ($setIds as $setId) { + $setId = (int) $setId; + if (!$setId) { + continue; + } + $alias = sprintf('entity_attribute_%d', $setId); + $joinCondition = $this->getConnection() + ->quoteInto("{$alias}.attribute_id = main_table.attribute_id AND {$alias}.attribute_set_id =?", $setId); + $this->join( + array($alias => 'eav/entity_attribute'), + $joinCondition, + 'attribute_id' + ); + } + + //$this->getSelect()->distinct(true); + $this->setOrder('is_user_defined', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Add filter which exclude attributes assigned to attribute set + * + * @param int $setId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setAttributeSetExcludeFilter($setId) + { + $this->join( + 'entity_attribute', + 'entity_attribute.attribute_id = main_table.attribute_id' + ); + $this->addFieldToFilter('entity_attribute.attribute_set_id', array('neq' => $setId)); + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Exclude attributes filter + * + * @param array $attributes + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setAttributesExcludeFilter($attributes) + { + return $this->addFieldToFilter('main_table.attribute_id', array('nin' => $attributes)); + } + + /** + * Filter by attribute group id + * + * @param int $groupId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setAttributeGroupFilter($groupId) + { + $this->join( + 'entity_attribute', + 'entity_attribute.attribute_id = main_table.attribute_id' + ); + $this->addFieldToFilter('entity_attribute.attribute_group_id', $groupId); + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Declare group by attribute id condition for collection select + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addAttributeGrouping() + { + $this->getSelect()->group('entity_attribute.attribute_id'); + return $this; + } + + /** + * Specify "is_unique" filter as true + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addIsUniqueFilter() + { + return $this->addFieldToFilter('is_unique', array('gt' => 0)); + } + + /** + * Specify "is_unique" filter as false + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addIsNotUniqueFilter() + { + return $this->addFieldToFilter('is_unique', 0); + } + + /** + * Specify filter to select just attributes with options + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addHasOptionsFilter() + { + $adapter = $this->getConnection(); + $orWhere = implode(' OR ', array( + $adapter->quoteInto('(main_table.frontend_input = ? AND ao.option_id > 0)', 'select'), + $adapter->quoteInto('(main_table.frontend_input <> ?)', 'select'), + '(main_table.is_user_defined = 0)' + )); + + $this->getSelect() + ->joinLeft( + array('ao' => $this->getTable('eav/attribute_option')), + 'ao.attribute_id = main_table.attribute_id', + 'option_id') + ->group('main_table.attribute_id') + ->where($orWhere); + + $this->_useAnalyticFunction = true; + + return $this; + } + + /** + * Apply filter by attribute frontend input type + * + * @param string $frontendInputType + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setFrontendInputTypeFilter($frontendInputType) + { + return $this->addFieldToFilter('frontend_input', $frontendInputType); + } + + /** + * Flag for adding information about attributes sets to result + * + * @param bool $flag + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addSetInfo($flag = true) + { + $this->_addSetInfoFlag = (bool)$flag; + return $this; + } + + /** + * Ad information about attribute sets to collection result data + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + protected function _addSetInfo() + { + if ($this->_addSetInfoFlag) { + $attributeIds = array(); + foreach ($this->_data as &$dataItem) { + $attributeIds[] = $dataItem['attribute_id']; + } + $attributeToSetInfo = array(); + + $adapter = $this->getConnection(); + if (count($attributeIds) > 0) { + $select = $adapter->select() + ->from( + array('entity' => $this->getTable('eav/entity_attribute')), + array('attribute_id', 'attribute_set_id', 'attribute_group_id', 'sort_order') + ) + ->joinLeft( + array('group' => $this->getTable('eav/attribute_group')), + 'entity.attribute_group_id = group.attribute_group_id', + array('group_sort_order' => 'sort_order') + ) + ->where('attribute_id IN (?)', $attributeIds); + $result = $adapter->fetchAll($select); + + foreach ($result as $row) { + $data = array( + 'group_id' => $row['attribute_group_id'], + 'group_sort' => $row['group_sort_order'], + 'sort' => $row['sort_order'] + ); + $attributeToSetInfo[$row['attribute_id']][$row['attribute_set_id']] = $data; + } + } + + foreach ($this->_data as &$attributeData) { + $setInfo = array(); + if (isset($attributeToSetInfo[$attributeData['attribute_id']])) { + $setInfo = $attributeToSetInfo[$attributeData['attribute_id']]; + } + + $attributeData['attribute_set_info'] = $setInfo; + } + + unset($attributeToSetInfo); + unset($attributeIds); + } + return $this; + } + + /** + * Ad information about attribute sets to collection result data + * + * @return Mage_Core_Model_Resource_Db_Collection_Abstract + */ + protected function _afterLoadData() + { + $this->_addSetInfo(); + + return parent::_afterLoadData(); + } + + /** + * Load is used in configurable products flag + * @deprecated + * + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function checkConfigurableProducts() + { + return $this; + } + + /** + * Specify collection attribute codes filter + * + * @param string || array $code + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function setCodeFilter($code) + { + if (empty($code)) { + return $this; + } + if (!is_array($code)) { + $code = array($code); + } + + return $this->addFieldToFilter('attribute_code', array('in' => $code)); + } + + /** + * Add store label to attribute by specified store id + * + * @param integer $storeId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Collection + */ + public function addStoreLabel($storeId) + { + $adapter = $this->getConnection(); + $joinExpression = $adapter + ->quoteInto('al.attribute_id = main_table.attribute_id AND al.store_id = ?', (int) $storeId); + $this->getSelect()->joinLeft( + array('al' => $this->getTable('eav/attribute_label')), + $joinExpression, + array('store_label' => $adapter->getIfNullSql('al.value', 'main_table.frontend_label')) + ); + + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group.php new file mode 100755 index 00000000..2a435b0e --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group.php @@ -0,0 +1,140 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Group extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/attribute_group', 'attribute_group_id'); + } + + /** + * Checks if attribute group exists + * + * @param Mage_Eav_Model_Entity_Attribute_Group $object + * @return boolean + */ + public function itemExists($object) + { + $adapter = $this->_getReadAdapter(); + $bind = array( + 'attribute_set_id' => $object->getAttributeSetId(), + 'attribute_group_name' => $object->getAttributeGroupName() + ); + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('attribute_set_id = :attribute_set_id') + ->where('attribute_group_name = :attribute_group_name'); + + return $adapter->fetchRow($select, $bind) > 0; + } + + /** + * Perform actions before object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + if (!$object->getSortOrder()) { + $object->setSortOrder($this->_getMaxSortOrder($object) + 1); + } + return parent::_beforeSave($object); + } + + /** + * Perform actions after object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Core_Model_Resource_Db_Abstract + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->getAttributes()) { + foreach ($object->getAttributes() as $attribute) { + $attribute->setAttributeGroupId($object->getId()); + $attribute->save(); + } + } + + return parent::_afterSave($object); + } + + /** + * Retreive max sort order + * + * @param Mage_Core_Model_Abstract $object + * @return int + */ + protected function _getMaxSortOrder($object) + { + $adapter = $this->_getReadAdapter(); + $bind = array(':attribute_set_id' => $object->getAttributeSetId()); + $select = $adapter->select() + ->from($this->getMainTable(), new Zend_Db_Expr("MAX(sort_order)")) + ->where('attribute_set_id = :attribute_set_id'); + + return $adapter->fetchOne($select, $bind); + } + + /** + * Set any group default if old one was removed + * + * @param integer $attributeSetId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Group + */ + public function updateDefaultGroup($attributeSetId) + { + $adapter = $this->_getWriteAdapter(); + $bind = array(':attribute_set_id' => $attributeSetId); + $select = $adapter->select() + ->from($this->getMainTable(), $this->getIdFieldName()) + ->where('attribute_set_id = :attribute_set_id') + ->order('default_id ' . Varien_Data_Collection::SORT_ORDER_DESC) + ->limit(1); + + $groupId = $adapter->fetchOne($select, $bind); + + if ($groupId) { + $data = array('default_id' => 1); + $where = array('attribute_group_id =?' => $groupId); + $adapter->update($this->getMainTable(), $data, $where); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group/Collection.php new file mode 100755 index 00000000..6c727512 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Group/Collection.php @@ -0,0 +1,69 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Group_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Init resource model for collection + * + */ + protected function _construct() + { + $this->_init('eav/entity_attribute_group'); + } + + /** + * Set Attribute Set Filter + * + * @param int $setId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Group_Collection + */ + public function setAttributeSetFilter($setId) + { + $this->addFieldToFilter('attribute_set_id', array('eq' => $setId)); + $this->setOrder('sort_order'); + return $this; + } + + /** + * Set sort order + * + * @param string $direction + * @return Mage_Eav_Model_Resource_Entity_Attribute_Group_Collection + */ + public function setSortOrder($direction = self::SORT_ORDER_ASC) + { + return $this->addOrder('sort_order', $direction); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option.php new file mode 100755 index 00000000..85e37e29 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option.php @@ -0,0 +1,130 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Option extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/attribute_option', 'option_id'); + } + + /** + * Add Join with option value for collection select + * + * @param Mage_Eav_Model_Entity_Collection_Abstract $collection + * @param Mage_Eav_Model_Entity_Attribute $attribute + * @param Zend_Db_Expr $valueExpr + * @return Mage_Eav_Model_Resource_Entity_Attribute_Option + */ + public function addOptionValueToCollection($collection, $attribute, $valueExpr) + { + $adapter = $this->_getReadAdapter(); + $attributeCode = $attribute->getAttributeCode(); + $optionTable1 = $attributeCode . '_option_value_t1'; + $optionTable2 = $attributeCode . '_option_value_t2'; + $tableJoinCond1 = "{$optionTable1}.option_id={$valueExpr} AND {$optionTable1}.store_id=0" + ; + $tableJoinCond2 = $adapter->quoteInto("{$optionTable2}.option_id={$valueExpr} AND {$optionTable2}.store_id=?", + $collection->getStoreId()); + $valueExpr = $adapter->getCheckSql("{$optionTable2}.value_id IS NULL", + "{$optionTable1}.value", + "{$optionTable2}.value"); + + $collection->getSelect() + ->joinLeft( + array($optionTable1 => $this->getTable('eav/attribute_option_value')), + $tableJoinCond1, + array()) + ->joinLeft( + array($optionTable2 => $this->getTable('eav/attribute_option_value')), + $tableJoinCond2, + array($attributeCode => $valueExpr) + ); + + return $this; + } + + /** + * Retrieve Select for update Flat data + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract $attribute + * @param int $store + * @param bool $hasValueField flag which require option value + * @return Varien_Db_Select + */ + public function getFlatUpdateSelect(Mage_Eav_Model_Entity_Attribute_Abstract $attribute, $store, + $hasValueField = true + ) { + $adapter = $this->_getReadAdapter(); + $attributeTable = $attribute->getBackend()->getTable(); + $attributeCode = $attribute->getAttributeCode(); + + $joinConditionTemplate = "%s.entity_id = %s.entity_id" + . " AND %s.entity_type_id = " . $attribute->getEntityTypeId() + . " AND %s.attribute_id = " . $attribute->getId() + . " AND %s.store_id = %d"; + $joinCondition = sprintf($joinConditionTemplate, 'e', 't1', 't1', 't1', 't1', + Mage_Core_Model_App::ADMIN_STORE_ID); + if ($attribute->getFlatAddChildData()) { + $joinCondition .= ' AND e.child_id = t1.entity_id'; + } + + $valueExpr = $adapter->getCheckSql('t2.value_id > 0', 't2.value', 't1.value'); + /** @var $select Varien_Db_Select */ + $select = $adapter->select() + ->joinLeft(array('t1' => $attributeTable), $joinCondition, array()) + ->joinLeft(array('t2' => $attributeTable), + sprintf($joinConditionTemplate, 't1', 't2', 't2', 't2', 't2', $store), + array($attributeCode => $valueExpr)); + + if (($attribute->getFrontend()->getInputType() != 'multiselect') && $hasValueField) { + $valueIdExpr = $adapter->getCheckSql('to2.value_id > 0', 'to2.value', 'to1.value'); + $select + ->joinLeft(array('to1' => $this->getTable('eav/attribute_option_value')), + "to1.option_id = {$valueExpr} AND to1.store_id = 0", array()) + ->joinLeft(array('to2' => $this->getTable('eav/attribute_option_value')), + $adapter->quoteInto("to2.option_id = {$valueExpr} AND to2.store_id = ?", $store), + array($attributeCode . '_value' => $valueIdExpr)); + } + + if ($attribute->getFlatAddChildData()) { + $select->where('e.is_child = 0'); + } + + return $select; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option/Collection.php new file mode 100755 index 00000000..70a9dc08 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Option/Collection.php @@ -0,0 +1,154 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Option value table + * + * @var string + */ + protected $_optionValueTable; + + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/entity_attribute_option'); + $this->_optionValueTable = Mage::getSingleton('core/resource')->getTableName('eav/attribute_option_value'); + } + + /** + * Set attribute filter + * + * @param int $setId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection + */ + public function setAttributeFilter($setId) + { + return $this->addFieldToFilter('attribute_id', $setId); + } + + + /** + * Add store filter to collection + * + * @param int $storeId + * @param bolean $useDefaultValue + * @return Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection + */ + public function setStoreFilter($storeId = null, $useDefaultValue = true) + { + if (is_null($storeId)) { + $storeId = Mage::app()->getStore()->getId(); + } + $adapter = $this->getConnection(); + + $joinCondition = $adapter->quoteInto('tsv.option_id = main_table.option_id AND tsv.store_id = ?', $storeId); + + if ($useDefaultValue) { + $this->getSelect() + ->join( + array('tdv' => $this->_optionValueTable), + 'tdv.option_id = main_table.option_id', + array('default_value' => 'value')) + ->joinLeft( + array('tsv' => $this->_optionValueTable), + $joinCondition, + array( + 'store_default_value' => 'value', + 'value' => $adapter->getCheckSql('tsv.value_id > 0', 'tsv.value', 'tdv.value') + )) + ->where('tdv.store_id = ?', 0); + } else { + $this->getSelect() + ->joinLeft( + array('tsv' => $this->_optionValueTable), + $joinCondition, + 'value') + ->where('tsv.store_id = ?', $storeId); + } + + $this->setOrder('value', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Add option id(s) frilter to collection + * + * @param int|array $optionId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection + */ + public function setIdFilter($optionId) + { + return $this->addFieldToFilter('option_id', array('in' => $optionId)); + } + + /** + * Convert collection items to select options array + * + * @param string $valueKey + * @return array + */ + public function toOptionArray($valueKey = 'value') + { + return $this->_toOptionArray('option_id', $valueKey); + } + + + /** + * Set order by position or alphabetically by values in admin + * + * @param string $dir direction + * @param boolean $sortAlpha sort alphabetically by values in admin + * @return Mage_Eav_Model_Resource_Entity_Attribute_Option_Collection + */ + public function setPositionOrder($dir = self::SORT_ORDER_ASC, $sortAlpha = false) + { + $this->setOrder('main_table.sort_order', $dir); + // sort alphabetically by values in admin + if ($sortAlpha) { + $this->getSelect() + ->joinLeft( + array('sort_alpha_value' => $this->_optionValueTable), + 'sort_alpha_value.option_id = main_table.option_id AND sort_alpha_value.store_id = 0', + array('value')); + $this->setOrder('sort_alpha_value.value', $dir); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set.php new file mode 100755 index 00000000..42c7367b --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set.php @@ -0,0 +1,177 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Set extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize connection + * + */ + protected function _construct() + { + $this->_init('eav/attribute_set', 'attribute_set_id'); + } + + /** + * Perform actions after object save + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Eav_Model_Resource_Entity_Attribute_Set + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->getGroups()) { + /* @var $group Mage_Eav_Model_Entity_Attribute_Group */ + foreach ($object->getGroups() as $group) { + $group->setAttributeSetId($object->getId()); + if ($group->itemExists() && !$group->getId()) { + continue; + } + $group->save(); + } + } + if ($object->getRemoveGroups()) { + foreach ($object->getRemoveGroups() as $group) { + /* @var $group Mage_Eav_Model_Entity_Attribute_Group */ + $group->delete(); + } + Mage::getResourceModel('eav/entity_attribute_group')->updateDefaultGroup($object->getId()); + } + if ($object->getRemoveAttributes()) { + foreach ($object->getRemoveAttributes() as $attribute) { + /* @var $attribute Mage_Eav_Model_Entity_Attribute */ + $attribute->deleteEntity(); + } + } + + return parent::_afterSave($object); + } + + /** + * Validate attribute set name + * + * @param Mage_Eav_Model_Entity_Attribute_Set $object + * @param string $attributeSetName + * @return bool + */ + public function validate($object, $attributeSetName) + { + + $adapter = $this->_getReadAdapter(); + $bind = array( + 'attribute_set_name' => trim($attributeSetName), + 'entity_type_id' => $object->getEntityTypeId() + ); + $select = $adapter->select() + ->from($this->getMainTable()) + ->where('attribute_set_name = :attribute_set_name') + ->where('entity_type_id = :entity_type_id'); + + if ($object->getId()) { + $bind['attribute_set_id'] = $object->getId(); + $select->where('attribute_set_id != :attribute_set_id'); + } + + return !$adapter->fetchOne($select, $bind) ? true : false; + } + + /** + * Retrieve Set info by attributes + * + * @param array $attributeIds + * @param int $setId + * @return array + */ + public function getSetInfo(array $attributeIds, $setId = null) + { + $adapter = $this->_getReadAdapter(); + $setInfo = array(); + $attributeToSetInfo = array(); + + if (count($attributeIds) > 0) { + $select = $adapter->select() + ->from( + array('entity' => $this->getTable('eav/entity_attribute')), + array('attribute_id', 'attribute_set_id', 'attribute_group_id', 'sort_order')) + ->joinLeft( + array('attribute_group' => $this->getTable('eav/attribute_group')), + 'entity.attribute_group_id = attribute_group.attribute_group_id', + array('group_sort_order' => 'sort_order')) + ->where('entity.attribute_id IN (?)', $attributeIds); + $bind = array(); + if (is_numeric($setId)) { + $bind[':attribute_set_id'] = $setId; + $select->where('entity.attribute_set_id = :attribute_set_id'); + } + $result = $adapter->fetchAll($select, $bind); + + foreach ($result as $row) { + $data = array( + 'group_id' => $row['attribute_group_id'], + 'group_sort' => $row['group_sort_order'], + 'sort' => $row['sort_order'] + ); + $attributeToSetInfo[$row['attribute_id']][$row['attribute_set_id']] = $data; + } + } + + foreach ($attributeIds as $atttibuteId) { + $setInfo[$atttibuteId] = isset($attributeToSetInfo[$atttibuteId]) + ? $attributeToSetInfo[$atttibuteId] + : array(); + } + + return $setInfo; + } + + /** + * Retrurn default attribute group id for attribute set id + * + * @param int $setId + * @return int|null + */ + public function getDefaultGroupId($setId) + { + $adapter = $this->_getReadAdapter(); + $bind = array( + 'attribute_set_id' => (int)$setId + ); + $select = $adapter->select() + ->from($this->getTable('eav/attribute_group'), 'attribute_group_id') + ->where('attribute_set_id = :attribute_set_id') + ->where('default_id = 1') + ->limit(1); + return $adapter->fetchOne($select, $bind); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set/Collection.php new file mode 100755 index 00000000..ffc940b6 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Attribute/Set/Collection.php @@ -0,0 +1,75 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Attribute_Set_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource initialization + * + */ + protected function _construct() + { + $this->_init('eav/entity_attribute_set'); + } + + /** + * Add filter by entity type id to collection + * + * @param int $typeId + * @return Mage_Eav_Model_Resource_Entity_Attribute_Set_Collection + */ + public function setEntityTypeFilter($typeId) + { + return $this->addFieldToFilter('entity_type_id', $typeId); + } + + /** + * Convert collection items to select options array + * + * @return array + */ + public function toOptionArray() + { + return parent::_toOptionArray('attribute_set_id', 'attribute_set_name'); + } + + /** + * Convert collection items to select options hash array + * + * @return array + */ + public function toOptionHash() + { + return parent::_toOptionHash('attribute_set_id', 'attribute_set_name'); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Store.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Store.php new file mode 100755 index 00000000..f00a1b08 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Store.php @@ -0,0 +1,77 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Store extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/entity_store', 'entity_store_id'); + } + + /** + * Load an object by entity type and store + * + * @param Varien_Object $object + * @param int $entityTypeId + * @param int $storeId + * @return boolean + */ + public function loadByEntityStore(Mage_Core_Model_Abstract $object, $entityTypeId, $storeId) + { + $adapter = $this->_getWriteAdapter(); + $bind = array( + ':entity_type_id' => $entityTypeId, + ':store_id' => $storeId + ); + $select = $adapter->select() + ->from($this->getMainTable()) + ->forUpdate(true) + ->where('entity_type_id = :entity_type_id') + ->where('store_id = :store_id'); + $data = $adapter->fetchRow($select, $bind); + + if (!$data) { + return false; + } + + $object->setData($data); + + $this->_afterLoad($object); + + return true; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Type.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Type.php new file mode 100755 index 00000000..c0994d5b --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Type.php @@ -0,0 +1,73 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Type extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/entity_type', 'entity_type_id'); + } + + /** + * Load Entity Type by Code + * + * @param Mage_Core_Model_Abstract $object + * @param string $code + * @return Mage_Eav_Model_Resource_Entity_Type + */ + public function loadByCode($object, $code) + { + return $this->load($object, $code, 'entity_type_code'); + } + + /** + * Retrieve additional attribute table name for specified entity type + * + * @param integer $entityTypeId + * @return string + */ + public function getAdditionalAttributeTable($entityTypeId) + { + $adapter = $this->_getReadAdapter(); + $bind = array('entity_type_id' => $entityTypeId); + $select = $adapter->select() + ->from($this->getMainTable(), array('additional_attribute_table')) + ->where('entity_type_id = :entity_type_id'); + + return $adapter->fetchOne($select, $bind); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Entity/Type/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Entity/Type/Collection.php new file mode 100755 index 00000000..4dad84e5 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Entity/Type/Collection.php @@ -0,0 +1,44 @@ + + */ +class Mage_Eav_Model_Resource_Entity_Type_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Resource initialization + */ + protected function _construct() + { + $this->_init('eav/entity_type'); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Attribute.php b/app/code/core/Mage/Eav/Model/Resource/Form/Attribute.php new file mode 100755 index 00000000..2f15ba09 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Attribute.php @@ -0,0 +1,52 @@ + + */ +abstract class Mage_Eav_Model_Resource_Form_Attribute extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Return form attribute IDs by form code + * + * @param string $formCode + * @return array + */ + public function getFormAttributeIds($formCode) + { + $bind = array('form_code' => $formCode); + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable(), 'attribute_id') + ->where('form_code = :form_code'); + + return $this->_getReadAdapter()->fetchCol($select, $bind); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Attribute/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Form/Attribute/Collection.php new file mode 100755 index 00000000..0ba68834 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Attribute/Collection.php @@ -0,0 +1,264 @@ + + */ +class Mage_Eav_Model_Resource_Form_Attribute_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Current module pathname + * + * @var string + */ + protected $_moduleName = ''; + + /** + * Current EAV entity type code + * + * @var string + */ + protected $_entityTypeCode = ''; + + /** + * Current store instance + * + * @var Mage_Core_Model_Store + */ + protected $_store; + + /** + * Eav Entity Type instance + * + * @var Mage_Eav_Model_Entity_Type + */ + protected $_entityType; + + /** + * Resource initialization + * + * @throws Mage_Core_Exception + */ + protected function _construct() + { + if (empty($this->_moduleName)) { + Mage::throwException(Mage::helper('eav')->__('Current module pathname is undefined')); + } + if (empty($this->_entityTypeCode)) { + Mage::throwException(Mage::helper('eav')->__('Current module EAV entity is undefined')); + } + } + + /** + * Get EAV website table + * + * Get table, where website-dependent attribute parameters are stored + * If realization doesn't demand this functionality, let this function just return null + * + * @return string|null + */ + protected function _getEavWebsiteTable() + { + return null; + } + + /** + * Set current store to collection + * + * @param Mage_Core_Model_Store|string|int $store + * @return Mage_Eav_Model_Resource_Form_Attribute_Collection + */ + public function setStore($store) + { + $this->_store = Mage::app()->getStore($store); + return $this; + } + + /** + * Return current store instance + * + * @return Mage_Core_Model_Store + */ + public function getStore() + { + if ($this->_store === null) { + $this->_store = Mage::app()->getStore(); + } + return $this->_store; + } + + /** + * Set entity type instance to collection + * + * @param Mage_Eav_Model_Entity_Type|string|int $entityType + * @return Mage_Eav_Model_Resource_Form_Attribute_Collection + */ + public function setEntityType($entityType) + { + $this->_entityType = Mage::getSingleton('eav/config')->getEntityType($entityType); + return $this; + } + + /** + * Return current entity type instance + * + * @return Mage_Eav_Model_Entity_Type + */ + public function getEntityType() + { + if ($this->_entityType === null) { + $this->setEntityType($this->_entityTypeCode); + } + return $this->_entityType; + } + + /** + * Add Form Code filter to collection + * + * @param string $code + * @return Mage_Eav_Model_Resource_Form_Attribute_Collection + */ + public function addFormCodeFilter($code) + { + return $this->addFieldToFilter('main_table.form_code', $code); + } + + /** + * Set order by attribute sort order + * + * @param string $direction + * @return Mage_Eav_Model_Resource_Form_Attribute_Collection + */ + public function setSortOrder($direction = self::SORT_ORDER_ASC) + { + $this->setOrder('ea.is_user_defined', self::SORT_ORDER_ASC); + return $this->setOrder('ca.sort_order', $direction); + } + + /** + * Add joins to select + * + * @return Mage_Eav_Model_Resource_Form_Attribute_Collection + */ + protected function _beforeLoad() + { + $select = $this->getSelect(); + $connection = $this->getConnection(); + $entityType = $this->getEntityType(); + $this->setItemObjectClass($entityType->getAttributeModel()); + + $eaColumns = array(); + $caColumns = array(); + $saColumns = array(); + + $eaDescribe = $connection->describeTable($this->getTable('eav/attribute')); + unset($eaDescribe['attribute_id']); + foreach (array_keys($eaDescribe) as $columnName) { + $eaColumns[$columnName] = $columnName; + } + + $select->join( + array('ea' => $this->getTable('eav/attribute')), + 'main_table.attribute_id = ea.attribute_id', + $eaColumns + ); + + // join additional attribute data table + $additionalTable = $entityType->getAdditionalAttributeTable(); + if ($additionalTable) { + $caDescribe = $connection->describeTable($this->getTable($additionalTable)); + unset($caDescribe['attribute_id']); + foreach (array_keys($caDescribe) as $columnName) { + $caColumns[$columnName] = $columnName; + } + + $select->join( + array('ca' => $this->getTable($additionalTable)), + 'main_table.attribute_id = ca.attribute_id', + $caColumns + ); + } + + // add scope values + if ($this->_getEavWebsiteTable()) { + $saDescribe = $connection->describeTable($this->_getEavWebsiteTable()); + unset($saDescribe['attribute_id']); + foreach (array_keys($saDescribe) as $columnName) { + if ($columnName == 'website_id') { + $saColumns['scope_website_id'] = $columnName; + } else { + if (isset($eaColumns[$columnName])) { + $code = sprintf('scope_%s', $columnName); + $expression = $connection->getCheckSql('sa.%s IS NULL', 'ea.%s', 'sa.%s'); + $saColumns[$code] = new Zend_Db_Expr(sprintf($expression, + $columnName, $columnName, $columnName)); + } elseif (isset($caColumns[$columnName])) { + $code = sprintf('scope_%s', $columnName); + $expression = $connection->getCheckSql('sa.%s IS NULL', 'ca.%s', 'sa.%s'); + $saColumns[$code] = new Zend_Db_Expr(sprintf($expression, + $columnName, $columnName, $columnName)); + } + } + } + + $store = $this->getStore(); + $joinWebsiteExpression = $connection + ->quoteInto( + 'sa.attribute_id = main_table.attribute_id AND sa.website_id = ?', (int)$store->getWebsiteId() + ); + $select->joinLeft( + array('sa' => $this->_getEavWebsiteTable()), + $joinWebsiteExpression, + $saColumns + ); + } + + + // add store attribute label + if ($store->isAdmin()) { + $select->columns(array('store_label' => 'ea.frontend_label')); + } else { + $storeLabelExpr = $connection->getCheckSql('al.value IS NULL', 'ea.frontend_label', 'al.value'); + $joinExpression = $connection + ->quoteInto('al.attribute_id = main_table.attribute_id AND al.store_id = ?', (int)$store->getId()); + $select->joinLeft( + array('al' => $this->getTable('eav/attribute_label')), + $joinExpression, + array('store_label' => $storeLabelExpr) + ); + } + + // add entity type filter + $select->where('ea.entity_type_id = ?', (int)$entityType->getId()); + + return parent::_beforeLoad(); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Element.php b/app/code/core/Mage/Eav/Model/Resource/Form/Element.php new file mode 100755 index 00000000..807e36aa --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Element.php @@ -0,0 +1,68 @@ + + */ +class Mage_Eav_Model_Resource_Form_Element extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize connection and define main table + */ + protected function _construct() + { + $this->_init('eav/form_element', 'element_id'); + $this->addUniqueField(array( + 'field' => array('type_id', 'attribute_id'), + 'title' => Mage::helper('eav')->__('Form Element with the same attribute') + )); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Eav_Model_Form_Element $object + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + $select->join( + $this->getTable('eav/attribute'), + $this->getTable('eav/attribute') . '.attribute_id = ' . $this->getMainTable() . '.attribute_id', + array('attribute_code', 'entity_type_id') + ); + + return $select; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Element/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Form/Element/Collection.php new file mode 100755 index 00000000..e985ea0b --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Element/Collection.php @@ -0,0 +1,133 @@ + + */ +class Mage_Eav_Model_Resource_Form_Element_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + */ + protected function _construct() + { + $this->_init('eav/form_element'); + } + + /** + * Add Form Type filter to collection + * + * @param Mage_Eav_Model_Form_Type|int $type + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + public function addTypeFilter($type) + { + if ($type instanceof Mage_Eav_Model_Form_Type) { + $type = $type->getId(); + } + + return $this->addFieldToFilter('type_id', $type); + } + + /** + * Add Form Fieldset filter to collection + * + * @param Mage_Eav_Model_Form_Fieldset|int $fieldset + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + public function addFieldsetFilter($fieldset) + { + if ($fieldset instanceof Mage_Eav_Model_Form_Fieldset) { + $fieldset = $fieldset->getId(); + } + + return $this->addFieldToFilter('fieldset_id', $fieldset); + } + + /** + * Add Attribute filter to collection + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract|int $attribute + * + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + public function addAttributeFilter($attribute) + { + if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { + $attribute = $attribute->getId(); + } + + return $this->addFieldToFilter('attribute_id', $attribute); + } + + /** + * Set order by element sort order + * + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + public function setSortOrder() + { + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + + return $this; + } + + /** + * Join attribute data + * + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + protected function _joinAttributeData() + { + $this->getSelect()->join( + array('eav_attribute' => $this->getTable('eav/attribute')), + 'main_table.attribute_id = eav_attribute.attribute_id', + array('attribute_code', 'entity_type_id') + ); + + return $this; + } + + /** + * Load data (join attribute data) + * + * @param boolean $printQuery + * @param boolean $logQuery + * @return Mage_Eav_Model_Resource_Form_Element_Collection + */ + public function load($printQuery = false, $logQuery = false) + { + if (!$this->isLoaded()) { + $this->_joinAttributeData(); + } + return parent::load($printQuery, $logQuery); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset.php b/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset.php new file mode 100755 index 00000000..1d00eda5 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset.php @@ -0,0 +1,165 @@ + + */ +class Mage_Eav_Model_Resource_Form_Fieldset extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize connection and define main table + */ + protected function _construct() + { + $this->_init('eav/form_fieldset', 'fieldset_id'); + $this->addUniqueField(array( + 'field' => array('type_id', 'code'), + 'title' => Mage::helper('eav')->__('Form Fieldset with the same code') + )); + } + + /** + * After save (save labels) + * + * @param Mage_Eav_Model_Form_Fieldset $object + * @return Mage_Eav_Model_Resource_Form_Fieldset + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->hasLabels()) { + $new = $object->getLabels(); + $old = $this->getLabels($object); + + $adapter = $this->_getWriteAdapter(); + + $insert = array_diff(array_keys($new), array_keys($old)); + $delete = array_diff(array_keys($old), array_keys($new)); + $update = array(); + + foreach ($new as $storeId => $label) { + if (isset($old[$storeId]) && $old[$storeId] != $label) { + $update[$storeId] = $label; + } elseif (isset($old[$storeId]) && empty($label)) { + $delete[] = $storeId; + } + } + + if (!empty($insert)) { + $data = array(); + foreach ($insert as $storeId) { + $label = $new[$storeId]; + if (empty($label)) { + continue; + } + $data[] = array( + 'fieldset_id' => (int)$object->getId(), + 'store_id' => (int)$storeId, + 'label' => $label + ); + } + if ($data) { + $adapter->insertMultiple($this->getTable('eav/form_fieldset_label'), $data); + } + } + + if (!empty($delete)) { + $where = array( + 'fieldset_id = ?' => $object->getId(), + 'store_id IN(?)' => $delete + ); + $adapter->delete($this->getTable('eav/form_fieldset_label'), $where); + } + + if (!empty($update)) { + foreach ($update as $storeId => $label) { + $bind = array('label' => $label); + $where = array( + 'fieldset_id =?' => $object->getId(), + 'store_id =?' => $storeId + ); + $adapter->update($this->getTable('eav/form_fieldset_label'), $bind, $where); + } + } + } + + return parent::_afterSave($object); + } + + /** + * Retrieve fieldset labels for stores + * + * @param Mage_Eav_Model_Form_Fieldset $object + * @return array + */ + public function getLabels($object) + { + $objectId = $object->getId(); + if (!$objectId) { + return array(); + } + $adapter = $this->_getReadAdapter(); + $bind = array(':fieldset_id' => $objectId); + $select = $adapter->select() + ->from($this->getTable('eav/form_fieldset_label'), array('store_id', 'label')) + ->where('fieldset_id = :fieldset_id'); + + return $adapter->fetchPairs($select, $bind); + } + + /** + * Retrieve select object for load object data + * + * @param string $field + * @param mixed $value + * @param Mage_Eav_Model_Form_Fieldset $object + * @return Varien_Db_Select + */ + protected function _getLoadSelect($field, $value, $object) + { + $select = parent::_getLoadSelect($field, $value, $object); + + $labelExpr = $select->getAdapter()->getIfNullSql('store_label.label', 'default_label.label'); + + $select + ->joinLeft( + array('default_label' => $this->getTable('eav/form_fieldset_label')), + $this->getMainTable() . '.fieldset_id = default_label.fieldset_id AND default_label.store_id=0', + array()) + ->joinLeft( + array('store_label' => $this->getTable('eav/form_fieldset_label')), + $this->getMainTable() . '.fieldset_id = store_label.fieldset_id AND default_label.store_id=' + . (int)$object->getStoreId(), + array('label' => $labelExpr) + ); + + return $select; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset/Collection.php new file mode 100755 index 00000000..f2643b5d --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Fieldset/Collection.php @@ -0,0 +1,135 @@ + + */ +class Mage_Eav_Model_Resource_Form_Fieldset_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Store scope ID + * + * @var int + */ + protected $_storeId; + + /** + * Initialize collection model + * + */ + protected function _construct() + { + $this->_init('eav/form_fieldset'); + } + + /** + * Add Form Type filter to collection + * + * @param Mage_Eav_Model_Form_Type|int $type + * @return Mage_Eav_Model_Resource_Form_Fieldset_Collection + */ + public function addTypeFilter($type) + { + if ($type instanceof Mage_Eav_Model_Form_Type) { + $type = $type->getId(); + } + + return $this->addFieldToFilter('type_id', $type); + } + + /** + * Set order by fieldset sort order + * + * @return Mage_Eav_Model_Resource_Form_Fieldset_Collection + */ + public function setSortOrder() + { + $this->setOrder('sort_order', self::SORT_ORDER_ASC); + return $this; + } + + /** + * Retrieve label store scope + * + * @return int + */ + public function getStoreId() + { + if (is_null($this->_storeId)) { + return Mage::app()->getStore()->getId(); + } + return $this->_storeId; + } + + /** + * Set store scope ID + * + * @param int $storeId + * @return Mage_Eav_Model_Resource_Form_Fieldset_Collection + */ + public function setStoreId($storeId) + { + $this->_storeId = $storeId; + return $this; + } + + /** + * Initialize select object + * + * @return Mage_Eav_Model_Resource_Form_Fieldset_Collection + */ + protected function _initSelect() + { + parent::_initSelect(); + $select = $this->getSelect(); + $select->join( + array('default_label' => $this->getTable('eav/form_fieldset_label')), + 'main_table.fieldset_id = default_label.fieldset_id AND default_label.store_id = 0', + array()); + if ($this->getStoreId() == 0) { + $select->columns('label', 'default_label'); + } else { + $labelExpr = $select->getAdapter() + ->getIfNullSql('store_label.label', 'default_label.label'); + $joinCondition = $this->getConnection() + ->quoteInto( + 'main_table.fieldset_id = store_label.fieldset_id AND store_label.store_id = ?', + (int)$this->getStoreId()); + $select->joinLeft( + array('store_label' => $this->getTable('eav/form_fieldset_label')), + $joinCondition, + array('label' => $labelExpr) + ); + } + + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Type.php b/app/code/core/Mage/Eav/Model/Resource/Form/Type.php new file mode 100755 index 00000000..23e42fa9 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Type.php @@ -0,0 +1,155 @@ + + */ +class Mage_Eav_Model_Resource_Form_Type extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize connection and define main table + * + */ + protected function _construct() + { + $this->_init('eav/form_type', 'type_id'); + $this->addUniqueField(array( + 'field' => array('code', 'theme', 'store_id'), + 'title' => Mage::helper('eav')->__('Form Type with the same code') + )); + } + + /** + * Load an object + * + * @param Mage_Eav_Model_Form_Type $object + * @param mixed $value + * @param string $field field to load by (defaults to model id) + * @return Mage_Eav_Model_Resource_Form_Type + */ + public function load(Mage_Core_Model_Abstract $object, $value, $field = null) + { + if (is_null($field) && !is_numeric($value)) { + $field = 'code'; + } + return parent::load($object, $value, $field); + } + + /** + * Retrieve form type entity types + * + * @param Mage_Eav_Model_Form_Type $object + * @return array + */ + public function getEntityTypes($object) + { + $objectId = $object->getId(); + if (!$objectId) { + return array(); + } + $adapter = $this->_getReadAdapter(); + $bind = array(':type_id' => $objectId); + $select = $adapter->select() + ->from($this->getTable('eav/form_type_entity'), 'entity_type_id') + ->where('type_id = :type_id'); + + return $adapter->fetchCol($select, $bind); + } + + /** + * Save entity types after save form type + * + * @see Mage_Core_Model_Resource_Db_Abstract#_afterSave($object) + * + * @param Mage_Eav_Model_Form_Type $object + * @return Mage_Eav_Model_Resource_Form_Type + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + if ($object->hasEntityTypes()) { + $new = $object->getEntityTypes(); + $old = $this->getEntityTypes($object); + + $insert = array_diff($new, $old); + $delete = array_diff($old, $new); + + $adapter = $this->_getWriteAdapter(); + + if (!empty($insert)) { + $data = array(); + foreach ($insert as $entityId) { + if (empty($entityId)) { + continue; + } + $data[] = array( + 'entity_type_id' => (int)$entityId, + 'type_id' => $object->getId() + ); + } + if ($data) { + $adapter->insertMultiple($this->getTable('eav/form_type_entity'), $data); + } + } + + if (!empty($delete)) { + $where = array( + 'entity_type_id IN (?)' => $delete, + 'type_id = ?' => $object->getId() + ); + $adapter->delete($this->getTable('eav/form_type_entity'), $where); + } + } + + return parent::_afterSave($object); + } + + /** + * Retrieve form type filtered by given attribute + * + * @param Mage_Eav_Model_Entity_Attribute_Abstract|int $attribute + * @return array + */ + public function getFormTypesByAttribute($attribute) + { + if ($attribute instanceof Mage_Eav_Model_Entity_Attribute_Abstract) { + $attribute = $attribute->getId(); + } + if (!$attribute) { + return array(); + } + $bind = array(':attribute_id' => $attribute); + $select = $this->_getReadAdapter()->select() + ->from($this->getTable('eav/form_element')) + ->where('attribute_id = :attribute_id'); + + return $this->_getReadAdapter()->fetchAll($select, $bind); + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Form/Type/Collection.php b/app/code/core/Mage/Eav/Model/Resource/Form/Type/Collection.php new file mode 100755 index 00000000..68dfe244 --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Form/Type/Collection.php @@ -0,0 +1,77 @@ + + */ +class Mage_Eav_Model_Resource_Form_Type_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize collection model + * + */ + protected function _construct() + { + $this->_init('eav/form_type'); + } + + /** + * Convert items array to array for select options + * + * @return array + */ + public function toOptionArray() + { + return $this->_toOptionArray('type_id', 'label'); + } + + /** + * Add Entity type filter to collection + * + * @param Mage_Eav_Model_Entity_Type|int $entity + * @return Mage_Eav_Model_Resource_Form_Type_Collection + */ + public function addEntityTypeFilter($entity) + { + if ($entity instanceof Mage_Eav_Model_Entity_Type) { + $entity = $entity->getId(); + } + + $this->getSelect() + ->join( + array('form_type_entity' => $this->getTable('eav/form_type_entity')), + 'main_table.type_id = form_type_entity.type_id', + array()) + ->where('form_type_entity.entity_type_id = ?', $entity); + + return $this; + } +} diff --git a/app/code/core/Mage/Eav/Model/Resource/Helper/Mysql4.php b/app/code/core/Mage/Eav/Model/Resource/Helper/Mysql4.php new file mode 100644 index 00000000..e328656a --- /dev/null +++ b/app/code/core/Mage/Eav/Model/Resource/Helper/Mysql4.php @@ -0,0 +1,128 @@ + + */ +class Mage_Eav_Model_Resource_Helper_Mysql4 extends Mage_Core_Model_Resource_Helper_Mysql4 +{ + /** + * Mysql column - Table DDL type pairs + * + * @var array + */ + protected $_ddlColumnTypes = array( + Varien_Db_Ddl_Table::TYPE_BOOLEAN => 'bool', + Varien_Db_Ddl_Table::TYPE_SMALLINT => 'smallint', + Varien_Db_Ddl_Table::TYPE_INTEGER => 'int', + Varien_Db_Ddl_Table::TYPE_BIGINT => 'bigint', + Varien_Db_Ddl_Table::TYPE_FLOAT => 'float', + Varien_Db_Ddl_Table::TYPE_DECIMAL => 'decimal', + Varien_Db_Ddl_Table::TYPE_NUMERIC => 'decimal', + Varien_Db_Ddl_Table::TYPE_DATE => 'date', + Varien_Db_Ddl_Table::TYPE_TIMESTAMP => 'timestamp', + Varien_Db_Ddl_Table::TYPE_DATETIME => 'datetime', + Varien_Db_Ddl_Table::TYPE_TEXT => 'text', + Varien_Db_Ddl_Table::TYPE_BLOB => 'blob', + Varien_Db_Ddl_Table::TYPE_VARBINARY => 'blob' + ); + + /** + * Returns columns for select + * + * @param string $tableAlias + * @param string $eavType + * @return string|array + */ + public function attributeSelectFields($tableAlias, $eavType) + { + return '*'; + } + + /** + * Returns DDL type by column type in database + * + * @param string $columnType + * @return string + */ + public function getDdlTypeByColumnType($columnType) + { + switch ($columnType) { + case 'char': + case 'varchar': + $columnType = 'text'; + break; + case 'tinyint': + $columnType = 'smallint'; + break; + } + + return array_search($columnType, $this->_ddlColumnTypes); + } + + /** + * Prepares value fields for unions depend on type + * + * @param string $value + * @param string $eavType + * @return Zend_Db_Expr + */ + public function prepareEavAttributeValue($value, $eavType) + { + return $value; + } + + /** + * Groups selects to separate unions depend on type + * + * @param array $selects + * @return array + */ + public function getLoadAttributesSelectGroups($selects) + { + $mainGroup = array(); + foreach ($selects as $eavType => $selectGroup) { + $mainGroup = array_merge($mainGroup, $selectGroup); + } + return array($mainGroup); + } + + /** + * Retrieve 'cast to int' expression + * + * @param string|Zend_Db_Expr $expression + * @return Zend_Db_Expr + */ + public function getCastToIntExpression($expression) + { + return new Zend_Db_Expr("CAST($expression AS SIGNED)"); + } +} diff --git a/app/code/core/Mage/Eav/etc/config.xml b/app/code/core/Mage/Eav/etc/config.xml index 127c0f3e..1a0e3fcb 100644 --- a/app/code/core/Mage/Eav/etc/config.xml +++ b/app/code/core/Mage/Eav/etc/config.xml @@ -21,46 +21,77 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.15 + 1.6.0.0 Mage_Eav_Model - eav_mysql4 + eav_resource - - - Mage_Eav_Model_Mysql4 + + Mage_Eav_Model_Resource + eav_mysql4 - eav_entity
    - eav_entity
    - eav_entity_type
    - eav_entity_store
    - eav_entity_attribute
    - eav_attribute
    - eav_attribute_set
    - eav_attribute_group
    - eav_attribute_option
    - eav_attribute_option_value
    - eav_attribute_label
    - eav_form_type
    - eav_form_type_entity
    - eav_form_fieldset
    - eav_form_fieldset_label
    - eav_form_element
    + + eav_entity
    +
    + + eav_entity
    +
    + + eav_entity_type
    +
    + + eav_entity_store
    +
    + + eav_entity_attribute
    +
    + + eav_attribute
    +
    + + eav_attribute_set
    +
    + + eav_attribute_group
    +
    + + eav_attribute_option
    +
    + + eav_attribute_option_value
    +
    + + eav_attribute_label
    +
    + + eav_form_type
    +
    + + eav_form_type_entity
    +
    + + eav_form_fieldset
    +
    + + eav_form_fieldset_label
    +
    + + eav_form_element
    +
    -
    +
    - @@ -81,20 +112,8 @@ -
    - @@ -106,7 +125,6 @@ - @@ -118,4 +136,18 @@ + + + + + text + + date + boolean + multiselect + + + + +
    diff --git a/app/code/core/Mage/Eav/sql/eav_setup/install-1.6.0.0.php b/app/code/core/Mage/Eav/sql/eav_setup/install-1.6.0.0.php new file mode 100644 index 00000000..ac615af3 --- /dev/null +++ b/app/code/core/Mage/Eav/sql/eav_setup/install-1.6.0.0.php @@ -0,0 +1,1158 @@ +startSetup(); + +/** + * Create table 'eav/entity_type' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/entity_type')) + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Type Id') + ->addColumn('entity_type_code', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => false, + ), 'Entity Type Code') + ->addColumn('entity_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Entity Model') + ->addColumn('attribute_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + ), 'Attribute Model') + ->addColumn('entity_table', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Entity Table') + ->addColumn('value_table_prefix', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Value Table Prefix') + ->addColumn('entity_id_field', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Entity Id Field') + ->addColumn('is_data_sharing', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '1', + ), 'Defines Is Data Sharing') + ->addColumn('data_sharing_key', Varien_Db_Ddl_Table::TYPE_TEXT, 100, array( + 'default' => 'default', + ), 'Data Sharing Key') + ->addColumn('default_attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Default Attribute Set Id') + ->addColumn('increment_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => '', + ), 'Increment Model') + ->addColumn('increment_per_store', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Increment Per Store') + ->addColumn('increment_pad_length', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '8', + ), 'Increment Pad Length') + ->addColumn('increment_pad_char', Varien_Db_Ddl_Table::TYPE_TEXT, 1, array( + 'nullable' => false, + 'default' => '0', + ), 'Increment Pad Char') + ->addColumn('additional_attribute_table', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => '', + ), 'Additional Attribute Table') + ->addColumn('entity_attribute_collection', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Entity Attribute Collection') + ->addIndex($installer->getIdxName('eav/entity_type', array('entity_type_code')), + array('entity_type_code')) + ->setComment('Eav Entity Type'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/entity' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/entity')) + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Set Id') + ->addColumn('increment_id', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => true, + 'default' => null, + ), 'Increment Id') + ->addColumn('parent_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Parent Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Created At') + ->addColumn('updated_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Updated At') + ->addColumn('is_active', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '1', + ), 'Defines Is Entity Active') + ->addIndex($installer->getIdxName('eav/entity', array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName('eav/entity', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('eav/entity', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/entity', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity'); +$installer->getConnection()->createTable($table); + +/** + * Create table array('eav/entity_value_prefix', 'datetime') + */ +$table = $installer->getConnection() + ->newTable($installer->getTable(array('eav/entity_value_prefix', 'datetime'))) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DATETIME, null, array( + 'nullable' => false, + 'default' => $installer->getConnection()->getSuggestedZeroDate() + ), 'Attribute Value') + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_id')), + array('entity_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('attribute_id', 'value')), + array('attribute_id', 'value')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_type_id', 'value')), + array('entity_type_id', 'value')) + ->addIndex( + $installer->getIdxName( + array('eav/entity_value_prefix', 'datetime'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'datetime'), + 'entity_id', + 'eav/entity', + 'entity_id' + ), + 'entity_id', $installer->getTable('eav/entity'), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'datetime'), + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'datetime'), + 'store_id', + 'core/store', + 'store_id' + ), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Prefix'); +$installer->getConnection()->createTable($table); + +/** + * Create table array('eav/entity_value_prefix', 'decimal') + */ +$table = $installer->getConnection() + ->newTable($installer->getTable(array('eav/entity_value_prefix', 'decimal'))) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_DECIMAL, '12,4', array( + 'nullable' => false, + 'default' => '0.0000', + ), 'Attribute Value') + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_id')), + array('entity_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('attribute_id', 'value')), + array('attribute_id', 'value')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_type_id', 'value')), + array('entity_type_id', 'value')) + ->addIndex( + $installer->getIdxName( + array('eav/entity_value_prefix', 'decimal'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'decimal'), + 'entity_id', + 'eav/entity', + 'entity_id' + ), + 'entity_id', $installer->getTable('eav/entity'), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'decimal'), + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'decimal'), + 'store_id', + 'core/store', + 'store_id' + ), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Prefix'); +$installer->getConnection()->createTable($table); + +/** + * Create table array('eav/entity_value_prefix', 'int') + */ +$table = $installer->getConnection() + ->newTable($installer->getTable(array('eav/entity_value_prefix', 'int'))) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Attribute Value') + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_id')), + array('entity_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('attribute_id', 'value')), + array('attribute_id', 'value')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_type_id', 'value')), + array('entity_type_id', 'value')) + ->addIndex( + $installer->getIdxName( + array('eav/entity_value_prefix', 'int'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'int'), + 'entity_id', + 'eav/entity', + 'entity_id' + ), + 'entity_id', $installer->getTable('eav/entity'), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'int'), + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'int'), + 'store_id', + 'core/store', + 'store_id' + ), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Prefix'); +$installer->getConnection()->createTable($table); + +/** + * Create table array('eav/entity_value_prefix', 'text') + */ +$table = $installer->getConnection() + ->newTable($installer->getTable(array('eav/entity_value_prefix', 'text'))) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => false, + ), 'Attribute Value') + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('entity_id')), + array('entity_id')) + ->addIndex( + $installer->getIdxName( + array('eav/entity_value_prefix', 'text'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'text'), + 'entity_id', + 'eav/entity', + 'entity_id' + ), + 'entity_id', $installer->getTable('eav/entity'), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'text'), + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'text'), + 'store_id', + 'core/store', + 'store_id' + ), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Prefix'); +$installer->getConnection()->createTable($table); + +/** + * Create table array('eav/entity_value_prefix', 'varchar') + */ +$table = $installer->getConnection() + ->newTable($installer->getTable(array('eav/entity_value_prefix', 'varchar'))) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Attribute Value') + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_id')), + array('entity_id')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('attribute_id', 'value')), + array('attribute_id', 'value')) + ->addIndex($installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_type_id', 'value')), + array('entity_type_id', 'value')) + ->addIndex( + $installer->getIdxName( + array('eav/entity_value_prefix', 'varchar'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'varchar'), + 'entity_id', + 'eav/entity', + 'entity_id' + ), + 'entity_id', $installer->getTable('eav/entity'), 'entity_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName( + array('eav/entity_value_prefix', 'varchar'), + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + array('eav/entity_value_prefix', 'varchar'), + 'store_id', + 'core/store', + 'store_id' + ), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Value Prefix'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute')) + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Attribute Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_code', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Attribute Code') + ->addColumn('attribute_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Attribute Model') + ->addColumn('backend_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Backend Model') + ->addColumn('backend_type', Varien_Db_Ddl_Table::TYPE_TEXT, 8, array( + 'nullable' => false, + 'default' => 'static', + ), 'Backend Type') + ->addColumn('backend_table', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Backend Table') + ->addColumn('frontend_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Frontend Model') + ->addColumn('frontend_input', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + ), 'Frontend Input') + ->addColumn('frontend_label', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Frontend Label') + ->addColumn('frontend_class', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Frontend Class') + ->addColumn('source_model', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Source Model') + ->addColumn('is_required', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Defines Is Required') + ->addColumn('is_user_defined', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Defines Is User Defined') + ->addColumn('default_value', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Default Value') + ->addColumn('is_unique', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Defines Is Unique') + ->addColumn('note', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Note') + ->addIndex( + $installer->getIdxName( + 'eav/attribute', + array('entity_type_id', 'attribute_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_type_id', 'attribute_code'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/attribute', array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName('eav/attribute', array('entity_type_id')), + array('entity_type_id')) + ->addForeignKey($installer->getFkName('eav/attribute', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/entity_store' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/entity_store')) + ->addColumn('entity_store_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Store Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('increment_prefix', Varien_Db_Ddl_Table::TYPE_TEXT, 20, array( + 'nullable' => true, + ), 'Increment Prefix') + ->addColumn('increment_last_id', Varien_Db_Ddl_Table::TYPE_TEXT, 50, array( + 'nullable' => true, + ), 'Last Incremented Id') + ->addIndex($installer->getIdxName('eav/entity_store', array('entity_type_id')), + array('entity_type_id')) + ->addIndex($installer->getIdxName('eav/entity_store', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('eav/entity_store', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/entity_store', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Store'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute_set' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute_set')) + ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Attribute Set Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_set_name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Attribute Set Name') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex( + $installer->getIdxName( + 'eav/attribute_set', + array('entity_type_id', 'attribute_set_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_type_id', 'attribute_set_name'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/attribute_set', array('entity_type_id', 'sort_order')), + array('entity_type_id', 'sort_order')) + ->addForeignKey($installer->getFkName('eav/attribute_set', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute Set'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute_group' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute_group')) + ->addColumn('attribute_group_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Attribute Group Id') + ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Set Id') + ->addColumn('attribute_group_name', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Attribute Group Name') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addColumn('default_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'default' => '0', + ), 'Default Id') + ->addIndex( + $installer->getIdxName( + 'eav/attribute_group', + array('attribute_set_id', 'attribute_group_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_set_id', 'attribute_group_name'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/attribute_group', array('attribute_set_id', 'sort_order')), + array('attribute_set_id', 'sort_order')) + ->addForeignKey( + $installer->getFkName( + 'eav/attribute_group', + 'attribute_set_id', + 'eav/attribute_set', + 'attribute_set_id' + ), + 'attribute_set_id', $installer->getTable('eav/attribute_set'), 'attribute_set_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute Group'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/entity_attribute' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/entity_attribute')) + ->addColumn('entity_attribute_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Attribute Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Entity Type Id') + ->addColumn('attribute_set_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Set Id') + ->addColumn('attribute_group_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Group Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex( + $installer->getIdxName( + 'eav/entity_attribute', + array('attribute_set_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_set_id', 'attribute_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex( + $installer->getIdxName( + 'eav/entity_attribute', + array('attribute_group_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_group_id', 'attribute_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/entity_attribute', array('attribute_set_id', 'sort_order')), + array('attribute_set_id', 'sort_order')) + ->addIndex($installer->getIdxName('eav/entity_attribute', array('attribute_id')), + array('attribute_id')) + ->addForeignKey($installer->getFkName('eav/entity_attribute', 'attribute_id', 'eav/attribute', 'attribute_id'), + 'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName( + 'eav/entity_attribute', + 'attribute_group_id', + 'eav/attribute_group', + 'attribute_group_id' + ), + 'attribute_group_id', $installer->getTable('eav/attribute_group'), 'attribute_group_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Entity Attributes'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute_option' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute_option')) + ->addColumn('option_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Option Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex($installer->getIdxName('eav/attribute_option', array('attribute_id')), + array('attribute_id')) + ->addForeignKey($installer->getFkName('eav/attribute_option', 'attribute_id', 'eav/attribute', 'attribute_id'), + 'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute Option'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute_option_value' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute_option_value')) + ->addColumn('value_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Value Id') + ->addColumn('option_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Option Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Value') + ->addIndex($installer->getIdxName('eav/attribute_option_value', array('option_id')), + array('option_id')) + ->addIndex($installer->getIdxName('eav/attribute_option_value', array('store_id')), + array('store_id')) + ->addForeignKey( + $installer->getFkName('eav/attribute_option_value', 'option_id', 'eav/attribute_option', 'option_id'), + 'option_id', $installer->getTable('eav/attribute_option'), 'option_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('eav/attribute_option_value', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute Option Value'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/attribute_label' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/attribute_label')) + ->addColumn('attribute_label_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Attribute Label Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Attribute Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Store Id') + ->addColumn('value', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => true, + 'default' => null, + ), 'Value') + ->addIndex($installer->getIdxName('eav/attribute_label', array('attribute_id')), + array('attribute_id')) + ->addIndex($installer->getIdxName('eav/attribute_label', array('store_id')), + array('store_id')) + ->addIndex($installer->getIdxName('eav/attribute_label', array('attribute_id', 'store_id')), + array('attribute_id', 'store_id')) + ->addForeignKey($installer->getFkName('eav/attribute_label', 'attribute_id', 'eav/attribute', 'attribute_id'), + 'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/attribute_label', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Attribute Label'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/form_type' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/form_type')) + ->addColumn('type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Type Id') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + 'nullable' => false, + ), 'Code') + ->addColumn('label', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Label') + ->addColumn('is_system', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Is System') + ->addColumn('theme', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + 'nullable' => true, + ), 'Theme') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Store Id') + ->addIndex( + $installer->getIdxName( + 'eav/form_type', + array('code', 'theme', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('code', 'theme', 'store_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/form_type', array('store_id')), + array('store_id')) + ->addForeignKey($installer->getFkName('eav/form_type', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Form Type'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/form_type_entity' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/form_type_entity')) + ->addColumn('type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Type Id') + ->addColumn('entity_type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Entity Type Id') + ->addIndex($installer->getIdxName('eav/form_type_entity', array('entity_type_id')), + array('entity_type_id')) + ->addForeignKey( + $installer->getFkName( + 'eav/form_type_entity', + 'entity_type_id', + 'eav/entity_type', + 'entity_type_id' + ), + 'entity_type_id', $installer->getTable('eav/entity_type'), 'entity_type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/form_type_entity', 'type_id', 'eav/form_type', 'type_id'), + 'type_id', $installer->getTable('eav/form_type'), 'type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Form Type Entity'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/form_fieldset' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/form_fieldset')) + ->addColumn('fieldset_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Fieldset Id') + ->addColumn('type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Type Id') + ->addColumn('code', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + 'nullable' => false, + ), 'Code') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex( + $installer->getIdxName( + 'eav/form_fieldset', + array('type_id', 'code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('type_id', 'code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/form_fieldset', array('type_id')), + array('type_id')) + ->addForeignKey($installer->getFkName('eav/form_fieldset', 'type_id', 'eav/form_type', 'type_id'), + 'type_id', $installer->getTable('eav/form_type'), 'type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Form Fieldset'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/form_fieldset_label' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/form_fieldset_label')) + ->addColumn('fieldset_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Fieldset Id') + ->addColumn('store_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Store Id') + ->addColumn('label', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + ), 'Label') + ->addIndex($installer->getIdxName('eav/form_fieldset_label', array('fieldset_id')), + array('fieldset_id')) + ->addIndex($installer->getIdxName('eav/form_fieldset_label', array('store_id')), + array('store_id')) + ->addForeignKey( + $installer->getFkName('eav/form_fieldset_label', 'fieldset_id', 'eav/form_fieldset', 'fieldset_id'), + 'fieldset_id', $installer->getTable('eav/form_fieldset'), 'fieldset_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey( + $installer->getFkName('eav/form_fieldset_label', 'store_id', 'core/store', 'store_id'), + 'store_id', $installer->getTable('core/store'), 'store_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Form Fieldset Label'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'eav/form_element' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('eav/form_element')) + ->addColumn('element_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Element Id') + ->addColumn('type_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Type Id') + ->addColumn('fieldset_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + ), 'Fieldset Id') + ->addColumn('attribute_id', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + ), 'Attribute Id') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'nullable' => false, + 'default' => '0', + ), 'Sort Order') + ->addIndex( + $installer->getIdxName( + 'eav/form_element', + array('type_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('type_id', 'attribute_id'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addIndex($installer->getIdxName('eav/form_element', array('type_id')), + array('type_id')) + ->addIndex($installer->getIdxName('eav/form_element', array('fieldset_id')), + array('fieldset_id')) + ->addIndex($installer->getIdxName('eav/form_element', array('attribute_id')), + array('attribute_id')) + ->addForeignKey($installer->getFkName('eav/form_element', 'attribute_id', 'eav/attribute', 'attribute_id'), + 'attribute_id', $installer->getTable('eav/attribute'), 'attribute_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/form_element', 'fieldset_id', 'eav/form_fieldset', 'fieldset_id'), + 'fieldset_id', $installer->getTable('eav/form_fieldset'), 'fieldset_id', + Varien_Db_Ddl_Table::ACTION_SET_NULL, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('eav/form_element', 'type_id', 'eav/form_type', 'type_id'), + 'type_id', $installer->getTable('eav/form_type'), 'type_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Eav Form Element'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-install-0.7.0.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-install-0.7.0.php index 34af6a6c..4b413bf0 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-install-0.7.0.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-install-0.7.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.0-0.7.1.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.0-0.7.1.php index 96575be6..cdd77a93 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.0-0.7.1.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.0-0.7.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.1-0.7.2.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.1-0.7.2.php index 0c728629..e31ea89a 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.1-0.7.2.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.1-0.7.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.10-0.7.11.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.10-0.7.11.php index c4a1bfee..363c3b44 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.10-0.7.11.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.10-0.7.11.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.11-0.7.12.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.11-0.7.12.php index 72814381..990d5873 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.11-0.7.12.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.11-0.7.12.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.12-0.7.13.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.12-0.7.13.php index a8873613..1ef7f404 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.12-0.7.13.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.12-0.7.13.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php index 532d32ac..8cb5fcb4 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.13-0.7.14.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php index 4ad3ad54..2ddd41e2 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.14-0.7.15.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.15-0.7.16.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.15-0.7.16.php new file mode 100644 index 00000000..b2a3fd98 --- /dev/null +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.15-0.7.16.php @@ -0,0 +1,37 @@ +startSetup(); +$conn = $installer->getConnection(); +foreach (array('datetime', 'decimal', 'int', 'text', 'varchar') as $type) { + $tableName = $installer->getTable('eav_entity_' . $type); + $conn->addKey($tableName, 'UNQ_ATTRIBUTE_VALUE', array('entity_id','attribute_id','store_id'), 'unique'); +} + +$installer->endSetup(); diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.2-0.7.3.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.2-0.7.3.php index 173305dd..ee7d38c6 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.2-0.7.3.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.2-0.7.3.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.3-0.7.4.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.3-0.7.4.php index 2d09f040..cca69d98 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.3-0.7.4.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.3-0.7.4.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.4-0.7.5.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.4-0.7.5.php index 86726da2..bdc6fb2b 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.4-0.7.5.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.4-0.7.5.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.5-0.7.6.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.5-0.7.6.php index 27ce5869..46ba070b 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.5-0.7.6.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.5-0.7.6.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.6-0.7.7.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.6-0.7.7.php index 13923654..7250bb83 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.6-0.7.7.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.6-0.7.7.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.7-0.7.8.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.7-0.7.8.php index ab9a23fd..6672e658 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.7-0.7.8.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.7-0.7.8.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.8-0.7.9.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.8-0.7.9.php index 409d0a60..aa813bbd 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.8-0.7.9.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.8-0.7.9.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.9-0.7.10.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.9-0.7.10.php index 5041677a..ecd2be94 100644 --- a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.9-0.7.10.php +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-0.7.9-0.7.10.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Eav - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..46721c52 --- /dev/null +++ b/app/code/core/Mage/Eav/sql/eav_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,2238 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute'), + 'FK_EAV_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_group'), + 'FK_EAV_ATTRIBUTE_GROUP' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_label'), + 'FK_ATTRIBUTE_LABEL_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_label'), + 'FK_ATTRIBUTE_LABEL_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_option'), + 'FK_ATTRIBUTE_OPTION_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_option_value'), + 'FK_ATTRIBUTE_OPTION_VALUE_OPTION' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_option_value'), + 'FK_ATTRIBUTE_OPTION_VALUE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/attribute_set'), + 'FK_EAV_ATTRIBUTE_SET' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity'), + 'FK_EAV_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity'), + 'FK_EAV_ENTITY_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIBUTE_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIBUTE_GROUP' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIVUTE_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIVUTE_GROUP' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_store'), + 'FK_EAV_ENTITY_STORE_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/entity_store'), + 'FK_EAV_ENTITY_STORE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_element'), + 'FK_EAV_FORM_ELEMENT_ATTRIBUTE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_element'), + 'FK_EAV_FORM_ELEMENT_FORM_FIELDSET' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_element'), + 'FK_EAV_FORM_ELEMENT_FORM_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_fieldset_label'), + 'FK_EAV_FORM_FIELDSET_LABEL_FORM_FIELDSET' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_fieldset_label'), + 'FK_EAV_FORM_FIELDSET_LABEL_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_type'), + 'FK_EAV_FORM_TYPE_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_type_entity'), + 'FK_EAV_FORM_TYPE_ENTITY_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_type_entity'), + 'FK_EAV_FORM_TYPE_ENTITY_FORM_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('eav/form_fieldset'), + 'FK_EAV_FORM_FIELDSET_FORM_TYPE' +); + + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_EAV_ENTITY_DATETIME_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_EAV_ENTITY_DATETIME_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_EAV_ENTITY_DATETIME_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_EAV_ENTITY_DECIMAL_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_EAV_ENTITY_DECIMAL_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_EAV_ENTITY_DECIMAL_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_EAV_ENTITY_INT_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_EAV_ENTITY_INT_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_EAV_ENTITY_INT_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_EAV_ENTITY_TEXT_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_EAV_ENTITY_TEXT_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_EAV_ENTITY_TEXT_STORE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_EAV_ENTITY_VARCHAR_ENTITY' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_EAV_ENTITY_VARCHAR_ENTITY_TYPE' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_EAV_ENTITY_VARCHAR_STORE' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute'), + 'ENTITY_TYPE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_group'), + 'ATTRIBUTE_SET_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_group'), + 'ATTRIBUTE_SET_ID_2' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_label'), + 'IDX_ATTRIBUTE_LABEL_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_label'), + 'IDX_ATTRIBUTE_LABEL_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_label'), + 'IDX_ATTRIBUTE_LABEL_ATTRIBUTE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_option'), + 'FK_ATTRIBUTE_OPTION_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_option_value'), + 'FK_ATTRIBUTE_OPTION_VALUE_OPTION' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_option_value'), + 'FK_ATTRIBUTE_OPTION_VALUE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_set'), + 'ENTITY_TYPE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/attribute_set'), + 'ENTITY_TYPE_ID_2' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity'), + 'FK_ENTITY_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity'), + 'FK_ENTITY_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_attribute'), + 'ATTRIBUTE_SET_ID_2' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_attribute'), + 'ATTRIBUTE_GROUP_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_attribute'), + 'ATTRIBUTE_SET_ID_3' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_attribute'), + 'FK_EAV_ENTITY_ATTRIVUTE_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_store'), + 'FK_EAV_ENTITY_STORE_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_store'), + 'FK_EAV_ENTITY_STORE_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/entity_type'), + 'ENTITY_NAME' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_element'), + 'UNQ_FORM_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_element'), + 'IDX_FORM_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_element'), + 'IDX_FORM_FIELDSET' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_element'), + 'IDX_FORM_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_fieldset'), + 'UNQ_FORM_FIELDSET_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_fieldset'), + 'IDX_FORM_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_fieldset_label'), + 'IDX_FORM_FIELDSET' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_fieldset_label'), + 'IDX_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_type'), + 'UNQ_FORM_TYPE_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_type'), + 'IDX_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('eav/form_type_entity'), + 'IDX_EAV_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'UNQ_ATTRIBUTE_VALUE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_ATTRIBUTE_DATETIME_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_ATTRIBUTE_DATETIME_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_ATTRIBUTE_DATETIME_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'FK_ATTRIBUTE_DATETIME_ENTITY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'VALUE_BY_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'VALUE_BY_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'UNQ_ATTRIBUTE_VALUE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_ATTRIBUTE_DECIMAL_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_ATTRIBUTE_DECIMAL_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_ATTRIBUTE_DECIMAL_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'FK_ATTRIBUTE_DECIMAL_ENTITY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'VALUE_BY_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'VALUE_BY_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'UNQ_ATTRIBUTE_VALUE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_ATTRIBUTE_INT_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_ATTRIBUTE_INT_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_ATTRIBUTE_INT_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'FK_ATTRIBUTE_INT_ENTITY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'VALUE_BY_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'VALUE_BY_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'UNQ_ATTRIBUTE_VALUE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_ATTRIBUTE_TEXT_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_ATTRIBUTE_TEXT_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_ATTRIBUTE_TEXT_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'FK_ATTRIBUTE_TEXT_ENTITY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'UNQ_ATTRIBUTE_VALUE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_ATTRIBUTE_VARCHAR_ENTITY_TYPE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_ATTRIBUTE_VARCHAR_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_ATTRIBUTE_VARCHAR_STORE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'FK_ATTRIBUTE_VARCHAR_ENTITY' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'VALUE_BY_ATTRIBUTE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'VALUE_BY_ENTITY_TYPE' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('eav/entity') => array( + 'columns' => array( + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Entity Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Entity Type Id' + ), + 'attribute_set_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Set Id' + ), + 'increment_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'nullable' => false, + 'comment' => 'Increment Id' + ), + 'parent_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Parent Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'created_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Created At' + ), + 'updated_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Updated At' + ), + 'is_active' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '1', + 'comment' => 'Defines Is Entity Active' + ) + ), + 'comment' => 'Eav Entity' + ), + $installer->getTable('eav/entity_type') => array( + 'columns' => array( + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Entity Type Id' + ), + 'entity_type_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'nullable' => false, + 'comment' => 'Entity Type Code' + ), + 'entity_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Entity Model' + ), + 'attribute_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Attribute Model' + ), + 'entity_table' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Entity Table' + ), + 'value_table_prefix' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Value Table Prefix' + ), + 'entity_id_field' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Entity Id Field' + ), + 'is_data_sharing' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '1', + 'comment' => 'Defines Is Data Sharing' + ), + 'data_sharing_key' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 100, + 'default' => 'default', + 'comment' => 'Data Sharing Key' + ), + 'default_attribute_set_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Default Attribute Set Id' + ), + 'increment_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => true, + 'default' => '', + 'comment' => 'Increment Model' + ), + 'increment_per_store' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Increment Per Store' + ), + 'increment_pad_length' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '8', + 'comment' => 'Increment Pad Length' + ), + 'increment_pad_char' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 1, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Increment Pad Char' + ), + 'additional_attribute_table' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => true, + 'default' => '', + 'comment' => 'Additional Attribute Table' + ), + 'entity_attribute_collection' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => true, + 'default' => '', + 'comment' => 'Entity Attribute Collection' + ) + ), + 'comment' => 'Eav Entity Type' + ), + $installer->getTable('eav/entity_store') => array( + 'columns' => array( + 'entity_store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Entity Store Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Entity Type Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'increment_prefix' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 20, + 'comment' => 'Increment Prefix' + ), + 'increment_last_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'comment' => 'Last Incremented Id' + ) + ), + 'comment' => 'Eav Entity Store' + ), + $installer->getTable('eav/entity_attribute') => array( + 'columns' => array( + 'entity_attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Entity Attribute Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Entity Type Id' + ), + 'attribute_set_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Set Id' + ), + 'attribute_group_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Group Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Id' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Eav Entity Attributes' + ), + $installer->getTable('eav/attribute') => array( + 'columns' => array( + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Attribute Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Entity Type Id' + ), + 'attribute_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Attribute Code' + ), + 'attribute_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Attribute Model' + ), + 'backend_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Backend Model' + ), + 'backend_type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 8, + 'nullable' => false, + 'default' => 'static', + 'comment' => 'Backend Type' + ), + 'backend_table' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Backend Table' + ), + 'frontend_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Frontend Model' + ), + 'frontend_input' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 50, + 'comment' => 'Frontend Input' + ), + 'frontend_label' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Frontend Label' + ), + 'frontend_class' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Frontend Class' + ), + 'source_model' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Source Model' + ), + 'is_required' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Defines Is Required' + ), + 'is_user_defined' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Defines Is User Defined' + ), + 'default_value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Default Value' + ), + 'is_unique' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Defines Is Unique' + ), + 'note' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Note' + ) + ), + 'comment' => 'Eav Attribute' + ), + $installer->getTable('eav/attribute_set') => array( + 'columns' => array( + 'attribute_set_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Attribute Set Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Entity Type Id' + ), + 'attribute_set_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Attribute Set Name' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Eav Attribute Set' + ), + $installer->getTable('eav/attribute_group') => array( + 'columns' => array( + 'attribute_group_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Attribute Group Id' + ), + 'attribute_set_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Set Id' + ), + 'attribute_group_name' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Attribute Group Name' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ), + 'default_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'default' => '0', + 'comment' => 'Default Id' + ) + ), + 'comment' => 'Eav Attribute Group' + ), + $installer->getTable('eav/attribute_option') => array( + 'columns' => array( + 'option_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Option Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Id' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Eav Attribute Option' + ), + $installer->getTable('eav/attribute_option_value') => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'option_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Option Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Value' + ) + ), + 'comment' => 'Eav Attribute Option Value' + ), + $installer->getTable('eav/attribute_label') => array( + 'columns' => array( + 'attribute_label_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Attribute Label Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Value' + ) + ), + 'comment' => 'Eav Attribute Label' + ), + $installer->getTable('eav/form_type') => array( + 'columns' => array( + 'type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Type Id' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'nullable' => false, + 'comment' => 'Code' + ), + 'label' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Label' + ), + 'is_system' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Is System' + ), + 'theme' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'comment' => 'Theme' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store Id' + ) + ), + 'comment' => 'Eav Form Type' + ), + $installer->getTable('eav/form_type_entity') => array( + 'columns' => array( + 'type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Type Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Entity Type Id' + ) + ), + 'comment' => 'Eav Form Type Entity' + ), + $installer->getTable('eav/form_fieldset') => array( + 'columns' => array( + 'fieldset_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Fieldset Id' + ), + 'type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Type Id' + ), + 'code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'nullable' => false, + 'comment' => 'Code' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Eav Form Fieldset' + ), + $installer->getTable('eav/form_fieldset_label') => array( + 'columns' => array( + 'fieldset_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Fieldset Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Store Id' + ), + 'label' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Label' + ) + ), + 'comment' => 'Eav Form Fieldset Label' + ), + $installer->getTable('eav/form_element') => array( + 'columns' => array( + 'element_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Element Id' + ), + 'type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Type Id' + ), + 'fieldset_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'comment' => 'Fieldset Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Attribute Id' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort Order' + ) + ), + 'comment' => 'Eav Form Element' + ) + , + $installer->getTable(array('eav/entity_value_prefix', 'datetime')) => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Type Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Store Id' + ), + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DATETIME, + 'nullable' => false, + 'default' => '0000-00-00 00:00:00', + 'comment' => 'Attribute Value' + ) + ), + 'comment' => 'Eav Entity Value Prefix' + ), + $installer->getTable(array('eav/entity_value_prefix', 'decimal')) => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Type Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Store Id' + ), + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_DECIMAL, + 'scale' => 4, + 'precision' => 12, + 'nullable' => false, + 'default' => '0.0000', + 'comment' => 'Attribute Value' + ) + ), + 'comment' => 'Eav Entity Value Prefix' + ), + $installer->getTable(array('eav/entity_value_prefix', 'int')) => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Type Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Store Id' + ), + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'nullable' => false, + 'comment' => 'Attribute Value' + ) + ), + 'comment' => 'Eav Entity Value Prefix' + ), + $installer->getTable(array('eav/entity_value_prefix', 'text')) => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Type Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Store Id' + ), + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'nullable' => false, + 'comment' => 'Attribute Value' + ) + ), + 'comment' => 'Eav Entity Value Prefix' + ), + $installer->getTable(array('eav/entity_value_prefix', 'varchar')) => array( + 'columns' => array( + 'value_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Value Id' + ), + 'entity_type_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Type Id' + ), + 'attribute_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Attribute Id' + ), + 'store_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Store Id' + ), + 'entity_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'comment' => 'Entity Id' + ), + 'value' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'comment' => 'Attribute Value' + ) + ), + 'comment' => 'Eav Entity Value Prefix' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute'), + $installer->getIdxName( + 'eav/attribute', + array('entity_type_id', 'attribute_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_type_id', 'attribute_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute'), + $installer->getIdxName('eav/attribute', array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_group'), + $installer->getIdxName( + 'eav/attribute_group', + array('attribute_set_id', 'attribute_group_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_set_id', 'attribute_group_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_group'), + $installer->getIdxName('eav/attribute_group', array('attribute_set_id', 'sort_order')), + array('attribute_set_id', 'sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_label'), + $installer->getIdxName('eav/attribute_label', array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_label'), + $installer->getIdxName('eav/attribute_label', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_label'), + $installer->getIdxName('eav/attribute_label', array('attribute_id', 'store_id')), + array('attribute_id', 'store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_option'), + $installer->getIdxName('eav/attribute_option', array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_option_value'), + $installer->getIdxName('eav/attribute_option_value', array('option_id')), + array('option_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_option_value'), + $installer->getIdxName('eav/attribute_option_value', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_set'), + $installer->getIdxName( + 'eav/attribute_set', + array('entity_type_id', 'attribute_set_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_type_id', 'attribute_set_name'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/attribute_set'), + $installer->getIdxName('eav/attribute_set', array('entity_type_id', 'sort_order')), + array('entity_type_id', 'sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity'), + $installer->getIdxName('eav/entity', array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity'), + $installer->getIdxName('eav/entity', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_attribute'), + $installer->getIdxName( + 'eav/entity_attribute', + array('attribute_set_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_set_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_attribute'), + $installer->getIdxName( + 'eav/entity_attribute', + array('attribute_group_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('attribute_group_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_attribute'), + $installer->getIdxName('eav/entity_attribute', array('attribute_set_id', 'sort_order')), + array('attribute_set_id', 'sort_order') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_attribute'), + $installer->getIdxName('eav/entity_attribute', array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_store'), + $installer->getIdxName('eav/entity_store', array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_store'), + $installer->getIdxName('eav/entity_store', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/entity_type'), + $installer->getIdxName('eav/entity_type', array('entity_type_code')), + array('entity_type_code') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_element'), + $installer->getIdxName( + 'eav/form_element', + array('type_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('type_id', 'attribute_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_element'), + $installer->getIdxName('eav/form_element', array('type_id')), + array('type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_element'), + $installer->getIdxName('eav/form_element', array('fieldset_id')), + array('fieldset_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_element'), + $installer->getIdxName('eav/form_element', array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_fieldset'), + $installer->getIdxName( + 'eav/form_fieldset', + array('type_id', 'code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('type_id', 'code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_fieldset'), + $installer->getIdxName('eav/form_fieldset', array('type_id')), + array('type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_fieldset_label'), + $installer->getIdxName('eav/form_fieldset_label', array('fieldset_id')), + array('fieldset_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_fieldset_label'), + $installer->getIdxName('eav/form_fieldset_label', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_type'), + $installer->getIdxName( + 'eav/form_type', + array('code', 'theme', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('code', 'theme', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_type'), + $installer->getIdxName('eav/form_type', array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('eav/form_type_entity'), + $installer->getIdxName('eav/form_type_entity', array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName( + array('eav/entity_value_prefix', 'datetime'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_id')), + array('entity_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('attribute_id', 'value')), + array('attribute_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + $installer->getIdxName(array('eav/entity_value_prefix', 'datetime'), array('entity_type_id', 'value')), + array('entity_type_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName( + array('eav/entity_value_prefix', 'decimal'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_id')), + array('entity_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('attribute_id', 'value')), + array('attribute_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + $installer->getIdxName(array('eav/entity_value_prefix', 'decimal'), array('entity_type_id', 'value')), + array('entity_type_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName( + array('eav/entity_value_prefix', 'int'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_id')), + array('entity_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('attribute_id', 'value')), + array('attribute_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'int')), + $installer->getIdxName(array('eav/entity_value_prefix', 'int'), array('entity_type_id', 'value')), + array('entity_type_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + $installer->getIdxName( + array('eav/entity_value_prefix', 'text'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + $installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + $installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + $installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'text')), + $installer->getIdxName(array('eav/entity_value_prefix', 'text'), array('entity_id')), + array('entity_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName( + array('eav/entity_value_prefix', 'varchar'), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('entity_id', 'attribute_id', 'store_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_type_id')), + array('entity_type_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('attribute_id')), + array('attribute_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('store_id')), + array('store_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_id')), + array('entity_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('attribute_id', 'value')), + array('attribute_id', 'value') +); + +$installer->getConnection()->addIndex( + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + $installer->getIdxName(array('eav/entity_value_prefix', 'varchar'), array('entity_type_id', 'value')), + array('entity_type_id', 'value') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable('eav/attribute'), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_group', 'attribute_set_id', 'eav/attribute_set', 'attribute_set_id'), + $installer->getTable('eav/attribute_group'), + 'attribute_set_id', + $installer->getTable('eav/attribute_set'), + 'attribute_set_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_label', 'attribute_id', 'eav/attribute', 'attribute_id'), + $installer->getTable('eav/attribute_label'), + 'attribute_id', + $installer->getTable('eav/attribute'), + 'attribute_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_label', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/attribute_label'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_option', 'attribute_id', 'eav/attribute', 'attribute_id'), + $installer->getTable('eav/attribute_option'), + 'attribute_id', + $installer->getTable('eav/attribute'), + 'attribute_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_option_value', 'option_id', 'eav/attribute_option', 'option_id'), + $installer->getTable('eav/attribute_option_value'), + 'option_id', + $installer->getTable('eav/attribute_option'), + 'option_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_option_value', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/attribute_option_value'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/attribute_set', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable('eav/attribute_set'), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable('eav/entity'), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/entity'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity_attribute', 'attribute_id', 'eav/attribute', 'attribute_id'), + $installer->getTable('eav/entity_attribute'), + 'attribute_id', + $installer->getTable('eav/attribute'), + 'attribute_id' +); + + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity_attribute', 'attribute_group_id', 'eav/attribute_group', 'attribute_group_id'), + $installer->getTable('eav/entity_attribute'), + 'attribute_group_id', + $installer->getTable('eav/attribute_group'), + 'attribute_group_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity_store', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable('eav/entity_store'), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/entity_store', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/entity_store'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_element', 'attribute_id', 'eav/attribute', 'attribute_id'), + $installer->getTable('eav/form_element'), + 'attribute_id', + $installer->getTable('eav/attribute'), + 'attribute_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_element', 'fieldset_id', 'eav/form_fieldset', 'fieldset_id'), + $installer->getTable('eav/form_element'), + 'fieldset_id', + $installer->getTable('eav/form_fieldset'), + 'fieldset_id', + Varien_Db_Ddl_Table::ACTION_SET_NULL +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_element', 'type_id', 'eav/form_type', 'type_id'), + $installer->getTable('eav/form_element'), + 'type_id', + $installer->getTable('eav/form_type'), + 'type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_fieldset', 'type_id', 'eav/form_type', 'type_id'), + $installer->getTable('eav/form_fieldset'), + 'type_id', + $installer->getTable('eav/form_type'), + 'type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_fieldset_label', 'fieldset_id', 'eav/form_fieldset', 'fieldset_id'), + $installer->getTable('eav/form_fieldset_label'), + 'fieldset_id', + $installer->getTable('eav/form_fieldset'), + 'fieldset_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_fieldset_label', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/form_fieldset_label'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_type', 'store_id', 'core/store', 'store_id'), + $installer->getTable('eav/form_type'), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_type_entity', 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable('eav/form_type_entity'), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('eav/form_type_entity', 'type_id', 'eav/form_type', 'type_id'), + $installer->getTable('eav/form_type_entity'), + 'type_id', + $installer->getTable('eav/form_type'), + 'type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'datetime'), 'entity_id', 'eav/entity', 'entity_id'), + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'entity_id', + $installer->getTable('eav/entity'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'datetime'), 'store_id', 'core/store', 'store_id'), + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'datetime'), 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable(array('eav/entity_value_prefix', 'datetime')), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'decimal'), 'entity_id', 'eav/entity', 'entity_id'), + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'entity_id', + $installer->getTable('eav/entity'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'decimal'), 'store_id', 'core/store', 'store_id'), + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'decimal'), 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable(array('eav/entity_value_prefix', 'decimal')), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'int'), 'entity_id', 'eav/entity', 'entity_id'), + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'entity_id', + $installer->getTable('eav/entity'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'int'), 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'int'), 'store_id', 'core/store', 'store_id'), + $installer->getTable(array('eav/entity_value_prefix', 'int')), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'text'), 'entity_id', 'eav/entity', 'entity_id'), + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'entity_id', + $installer->getTable('eav/entity'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'text'), 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'text'), 'store_id', 'core/store', 'store_id'), + $installer->getTable(array('eav/entity_value_prefix', 'text')), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'varchar'), 'entity_id', 'eav/entity', 'entity_id'), + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'entity_id', + $installer->getTable('eav/entity'), + 'entity_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'varchar'), 'store_id', 'core/store', 'store_id'), + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'store_id', + $installer->getTable('core/store'), + 'store_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName(array('eav/entity_value_prefix', 'varchar'), 'entity_type_id', 'eav/entity_type', 'entity_type_id'), + $installer->getTable(array('eav/entity_value_prefix', 'varchar')), + 'entity_type_id', + $installer->getTable('eav/entity_type'), + 'entity_type_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php b/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php index 6db26b05..509c0c9d 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Notifications.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,9 +34,12 @@ class Mage_Index_Block_Adminhtml_Notifications extends Mage_Adminhtml_Block_Temp public function getProcessesForReindex() { $res = array(); - $processes = Mage::getSingleton('index/indexer')->getProcessesCollection(); + $processes = Mage::getSingleton('index/indexer')->getProcessesCollection()->addEventsStats(); + /** @var $process Mage_Index_Model_Process */ foreach ($processes as $process) { - if ($process->getStatus() == Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX) { + if (($process->getStatus() == Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX + || $process->getEvents() > 0) && $process->getIndexer()->isVisible() + ) { $res[] = $process->getIndexer()->getName(); } } diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process.php b/app/code/core/Mage/Index/Block/Adminhtml/Process.php index 911ed393..06782cad 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php index 250840df..98d02d2a 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php index 21a358c4..0dde07ca 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php index 4668e9e1..a450c27b 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php index 1b259a48..eb70adc8 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php index 66e33446..b9ee489a 100644 --- a/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid.php @@ -20,21 +20,33 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Index_Block_Adminhtml_Process_Grid extends Mage_Adminhtml_Block_Widget_Grid { + /** + * Process model + * + * @var Mage_Index_Model_Process + */ protected $_processModel; + /** + * Mass-action block + * + * @var string + */ + protected $_massactionBlockName = 'index/adminhtml_process_grid_massaction'; + /** * Class constructor */ public function __construct() { parent::__construct(); - $this->_processModel = Mage::getModel('index/process'); + $this->_processModel = Mage::getSingleton('index/process'); $this->setId('indexer_processes_grid'); $this->_filterVisibility = false; $this->_pagerVisibility = false; @@ -55,9 +67,18 @@ protected function _prepareCollection() */ protected function _afterLoadCollection() { - foreach ($this->_collection as $item) { + /** @var $item Mage_Index_Model_Process */ + foreach ($this->_collection as $key => $item) { + if (!$item->getIndexer()->isVisible()) { + $this->_collection->removeItemByKey($key); + continue; + } $item->setName($item->getIndexer()->getName()); $item->setDescription($item->getIndexer()->getDescription()); + $item->setUpdateRequired($item->getUnprocessedEventsCollection()->count() > 0 ? 1 : 0); + if ($item->isLocked()) { + $item->setStatus(Mage_Index_Model_Process::STATUS_RUNNING); + } } return $this; } @@ -102,8 +123,19 @@ protected function _prepareColumns() 'frame_callback' => array($this, 'decorateStatus') )); + $this->addColumn('update_required', array( + 'header' => Mage::helper('index')->__('Update Required'), + 'sortable' => false, + 'width' => '120', + 'align' => 'left', + 'index' => 'update_required', + 'type' => 'options', + 'options' => $this->_processModel->getUpdateRequiredOptions(), + 'frame_callback' => array($this, 'decorateUpdateRequired') + )); + $this->addColumn('ended_at', array( - 'header' => Mage::helper('index')->__('Last Run'), + 'header' => Mage::helper('index')->__('Updated At'), 'type' => 'datetime', 'width' => '180', 'align' => 'left', @@ -123,11 +155,6 @@ protected function _prepareColumns() 'url' => array('base'=> '*/*/reindexProcess'), 'field' => 'process' ), -// array( -// 'caption' => Mage::helper('index')->__('Pending Events'), -// 'url' => array('base'=> '*/*/reindexEvents'), -// 'field' => 'process' -// ) ), 'filter' => false, 'sortable' => false, @@ -140,6 +167,10 @@ protected function _prepareColumns() /** * Decorate status column values * + * @param string $value + * @param Mage_Index_Model_Process $row + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @param bool $isExport * @return string */ public function decorateStatus($value, $row, $column, $isExport) @@ -159,6 +190,29 @@ public function decorateStatus($value, $row, $column, $isExport) return ''.$value.''; } + /** + * Decorate "Update Required" column values + * + * @param string $value + * @param Mage_Index_Model_Process $row + * @param Mage_Adminhtml_Block_Widget_Grid_Column $column + * @param bool $isExport + * @return string + */ + public function decorateUpdateRequired($value, $row, $column, $isExport) + { + $class = ''; + switch ($row->getUpdateRequired()) { + case 0: + $class = 'grid-severity-notice'; + break; + case 1: + $class = 'grid-severity-critical'; + break; + } + return ''.$value.''; + } + /** * Decorate last run date coumn * diff --git a/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid/Massaction.php b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid/Massaction.php new file mode 100644 index 00000000..c712c91a --- /dev/null +++ b/app/code/core/Mage/Index/Block/Adminhtml/Process/Grid/Massaction.php @@ -0,0 +1,54 @@ + + */ +class Mage_Index_Block_Adminhtml_Process_Grid_Massaction extends Mage_Adminhtml_Block_Widget_Grid_Massaction_Abstract +{ + /** + * Get ids for only visible indexers + * + * @return string + */ + public function getGridIdsJson() + { + if (!$this->getUseSelectAll()) { + return ''; + } + + $ids = array(); + foreach ($this->getParentBlock()->getCollection() as $process) { + $ids[] = $process->getId(); + } + + return implode(',', $ids); + } +} diff --git a/app/code/core/Mage/Index/Helper/Data.php b/app/code/core/Mage/Index/Helper/Data.php index 5e4b0a13..45b90c80 100644 --- a/app/code/core/Mage/Index/Helper/Data.php +++ b/app/code/core/Mage/Index/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/Model/Event.php b/app/code/core/Mage/Index/Model/Event.php index 40bc39fc..2f605e24 100644 --- a/app/code/core/Mage/Index/Model/Event.php +++ b/app/code/core/Mage/Index/Model/Event.php @@ -20,10 +20,29 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Index_Model_Resource_Event _getResource() + * @method Mage_Index_Model_Resource_Event getResource() + * @method Mage_Index_Model_Event setType(string $value) + * @method Mage_Index_Model_Event setEntity(string $value) + * @method int getEntityPk() + * @method Mage_Index_Model_Event setEntityPk(int $value) + * @method string getCreatedAt() + * @method Mage_Index_Model_Event setCreatedAt(string $value) + * @method Mage_Index_Model_Event setOldData(string $value) + * @method Mage_Index_Model_Event setNewData(string $value) + * @method Varien_Object getDataObject() + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ class Mage_Index_Model_Event extends Mage_Core_Model_Abstract { /** @@ -98,14 +117,10 @@ public function setDataNamespace($namespace) public function resetData() { if ($this->_dataNamespace) { - $data = $this->getOldData(false); - $data[$this->_dataNamespace] = null; - $this->setOldData($data); $data = $this->getNewData(false); $data[$this->_dataNamespace] = null; $this->setNewData($data); } else { - $this->setOldData(array()); $this->setNewData(array()); } return $this; @@ -133,6 +148,37 @@ public function getProcessIds() return $this->_processIds; } + /** + * Merge new data + * + * @param array $previous + * @param mixed $current + * @return array + */ + protected function _mergeNewDataRecursive($previous, $current) + { + if (!is_array($current)) { + if (!is_null($current)) { + $previous[] = $current; + } + return $previous; + } + + foreach ($previous as $key => $value) { + if (array_key_exists($key, $current) && !is_null($current[$key]) && is_array($previous[$key])) { + if (!is_string($key) || is_array($current[$key])) { + $current[$key] = $this->_mergeNewDataRecursive($previous[$key], $current[$key]); + } + } elseif (!array_key_exists($key, $current) || is_null($current[$key])) { + $current[$key] = $previous[$key]; + } elseif (!is_array($previous[$key]) && !is_string($key)) { + $current[] = $previous[$key]; + } + } + + return $current; + } + /** * Merge previous event data to object. * Used for events duplicated protection @@ -146,40 +192,61 @@ public function mergePreviousData($data) $this->setId($data['event_id']); $this->setCreatedAt($data['created_at']); } - if (!empty($data['old_data'])) { - $this->setOldData($data['old_data']); - } + if (!empty($data['new_data'])) { $previousNewData = unserialize($data['new_data']); $currentNewData = $this->getNewData(false); - $currentNewData = array_merge($previousNewData, $currentNewData); + $currentNewData = $this->_mergeNewDataRecursive($previousNewData, $currentNewData); $this->setNewData(serialize($currentNewData)); } return $this; } + /** + * Clean new data, unset data for done processes + * + * @return Mage_Index_Model_Event + */ + public function cleanNewData() + { + $processIds = $this->getProcessIds(); + if (!is_array($processIds) || empty($processIds)) { + return $this; + } + + $newData = $this->getNewData(false); + foreach ($processIds as $processId => $processStatus) { + if ($processStatus == Mage_Index_Model_Process::EVENT_STATUS_DONE) { + $process = Mage::getSingleton('index/indexer')->getProcessById($processId); + if ($process) { + $namespace = get_class($process->getIndexer()); + if (array_key_exists($namespace, $newData)) { + unset($newData[$namespace]); + } + } + } + } + $this->setNewData(serialize($newData)); + + return $this; + } + /** * Get event old data array * + * @deprecated since 1.6.2.0 + * @param bool $useNamespace * @return array */ public function getOldData($useNamespace = true) { - $data = $this->_getData('old_data'); - if (is_string($data)) { - $data = unserialize($data); - } elseif (empty($data) || !is_array($data)) { - $data = array(); - } - if ($useNamespace && $this->_dataNamespace) { - return isset($data[$this->_dataNamespace]) ? $data[$this->_dataNamespace] : array(); - } - return $data; + return array(); } /** * Get event new data array * + * @param bool $useNamespace * @return array */ public function getNewData($useNamespace = true) @@ -199,26 +266,13 @@ public function getNewData($useNamespace = true) /** * Add new values to old data array (overwrite if value with same key exist) * + * @deprecated since 1.6.2.0 * @param array | string $data * @param null | mixed $value * @return Mage_Index_Model_Event */ public function addOldData($key, $value=null) { - $oldData = $this->getOldData(false); - if (!is_array($key)) { - $key = array($key => $value); - } - - if ($this->_dataNamespace) { - if (!isset($oldData[$this->_dataNamespace])) { - $oldData[$this->_dataNamespace] = array(); - } - $oldData[$this->_dataNamespace] = array_merge($oldData[$this->_dataNamespace], $key); - } else { - $oldData = array_merge($oldData, $key); - } - $this->setOldData($oldData); return $this; } @@ -276,9 +330,7 @@ public function getType() */ protected function _beforeSave() { - $oldData = $this->getOldData(false); $newData = $this->getNewData(false); - $this->setOldData(serialize($oldData)); $this->setNewData(serialize($newData)); if (!$this->hasCreatedAt()) { $this->setCreatedAt($this->_getResource()->formatDate(time(), true)); diff --git a/app/code/core/Mage/Index/Model/Indexer.php b/app/code/core/Mage/Index/Model/Indexer.php index 0cefcd09..651d2d5d 100644 --- a/app/code/core/Mage/Index/Model/Indexer.php +++ b/app/code/core/Mage/Index/Model/Indexer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,17 +32,33 @@ class Mage_Index_Model_Indexer /** * Collection of available processes * - * @var Mage_Index_Model_Mysql4_Process_Collection + * @var Mage_Index_Model_Resource_Process_Collection */ protected $_processesCollection; /** * Indexer processes lock flag * + * @deprecated after 1.6.1.0 * @var bool */ protected $_lockFlag = false; + /** + * Whether table changes are allowed + * + * @var bool + */ + protected $_allowTableChanges = true; + + /** + * Current processing event(s) + * In array case it should be array(Entity type, Event type) + * + * @var null|Mage_Index_Model_Event|array + */ + protected $_currentEvent = null; + /** * Class constructor. Initialize index processes based on configuration */ @@ -54,7 +70,7 @@ public function __construct() /** * Get collection of all available processes * - * @return Mage_Index_Model_Mysql4_Process_Collection + * @return Mage_Index_Model_Resource_Process_Collection */ public function getProcessesCollection() { @@ -95,6 +111,9 @@ public function getProcessByCode($code) /** * Lock indexer actions + * @deprecated after 1.6.1.0 + * + * @return Mage_Index_Model_Indexer */ public function lockIndexer() { @@ -104,6 +123,9 @@ public function lockIndexer() /** * Unlock indexer actions + * @deprecated after 1.6.1.0 + * + * @return Mage_Index_Model_Indexer */ public function unlockIndexer() { @@ -114,6 +136,7 @@ public function unlockIndexer() /** * Check if onject actions are locked * + * @deprecated after 1.6.1.0 * @return bool */ public function isLocked() @@ -131,11 +154,32 @@ public function isLocked() */ public function indexEvents($entity=null, $type=null) { - if ($this->isLocked()) { - return $this; + Mage::dispatchEvent('start_index_events' . $this->_getEventTypeName($entity, $type)); + + /** @var $resourceModel Mage_Index_Model_Resource_Process */ + $resourceModel = Mage::getResourceSingleton('index/process'); + + $allowTableChanges = $this->_allowTableChanges && !$resourceModel->isInTransaction(); + if ($allowTableChanges) { + $this->_currentEvent = array($entity, $type); + $this->_changeKeyStatus(false); } - $this->_runAll('indexEvents', array($entity, $type)); + $resourceModel->beginTransaction(); + $this->_allowTableChanges = false; + try { + $this->_runAll('indexEvents', array($entity, $type)); + $resourceModel->commit(); + } catch (Exception $e) { + $resourceModel->rollBack(); + throw $e; + } + if ($allowTableChanges) { + $this->_allowTableChanges = true; + $this->_changeKeyStatus(true); + $this->_currentEvent = null; + } + Mage::dispatchEvent('end_index_events' . $this->_getEventTypeName($entity, $type)); return $this; } @@ -147,11 +191,7 @@ public function indexEvents($entity=null, $type=null) */ public function indexEvent(Mage_Index_Model_Event $event) { - if ($this->isLocked()) { - return $this; - } - - $this->_runAll('processEvent', array($event)); + $this->_runAll('safeProcessEvent', array($event)); return $this; } @@ -162,10 +202,6 @@ public function indexEvent(Mage_Index_Model_Event $event) */ public function registerEvent(Mage_Index_Model_Event $event) { - if ($this->isLocked()) { - return $this; - } - $this->_runAll('register', array($event)); return $this; } @@ -181,9 +217,6 @@ public function registerEvent(Mage_Index_Model_Event $event) */ public function logEvent(Varien_Object $entity, $entityType, $eventType, $doSave=true) { - if ($this->isLocked()) { - return $this; - } $event = Mage::getModel('index/event') ->setEntity($entityType) ->setType($eventType) @@ -208,16 +241,43 @@ public function logEvent(Varien_Object $entity, $entityType, $eventType, $doSave */ public function processEntityAction(Varien_Object $entity, $entityType, $eventType) { - if ($this->isLocked()) { - return $this; - } $event = $this->logEvent($entity, $entityType, $eventType, false); /** - * Index and save event just in case if some process mutched it + * Index and save event just in case if some process matched it */ if ($event->getProcessIds()) { - $this->indexEvent($event); + Mage::dispatchEvent('start_process_event' . $this->_getEventTypeName($entityType, $eventType)); + + /** @var $resourceModel Mage_Index_Model_Resource_Process */ + $resourceModel = Mage::getResourceSingleton('index/process'); + + $allowTableChanges = $this->_allowTableChanges && !$resourceModel->isInTransaction(); + if ($allowTableChanges) { + $this->_currentEvent = $event; + $this->_changeKeyStatus(false); + } + + $resourceModel->beginTransaction(); + $this->_allowTableChanges = false; + try { + $this->indexEvent($event); + $resourceModel->commit(); + } catch (Exception $e) { + $resourceModel->rollBack(); + if ($allowTableChanges) { + $this->_allowTableChanges = true; + $this->_changeKeyStatus(true); + $this->_currentEvent = null; + } + throw $e; + } + if ($allowTableChanges) { + $this->_allowTableChanges = true; + $this->_changeKeyStatus(true); + $this->_currentEvent = null; + } $event->save(); + Mage::dispatchEvent('end_process_event' . $this->_getEventTypeName($entityType, $eventType)); } return $this; } @@ -233,24 +293,137 @@ public function processEntityAction(Varien_Object $entity, $entityType, $eventTy */ protected function _runAll($method, $args) { + $checkLocks = $method != 'register'; $processed = array(); foreach ($this->_processesCollection as $process) { $code = $process->getIndexerCode(); if (in_array($code, $processed)) { continue; } + $hasLocks = false; + if ($process->getDepends()) { foreach ($process->getDepends() as $processCode) { $dependProcess = $this->getProcessByCode($processCode); if ($dependProcess && !in_array($processCode, $processed)) { - call_user_func_array(array($dependProcess, $method), $args); - $processed[] = $processCode; + if ($checkLocks && $dependProcess->isLocked()) { + $hasLocks = true; + } else { + call_user_func_array(array($dependProcess, $method), $args); + if ($checkLocks && $dependProcess->getMode() == Mage_Index_Model_Process::MODE_MANUAL) { + $hasLocks = true; + } else { + $processed[] = $processCode; + } + } } } } - call_user_func_array(array($process, $method), $args); - $processed[] = $code; + if (!$hasLocks) { + call_user_func_array(array($process, $method), $args); + $processed[] = $code; + } + } + } + + /** + * Enable/Disable keys in index tables + * + * @param bool $enable + * @return Mage_Index_Model_Indexer + */ + protected function _changeKeyStatus($enable = true) + { + $processed = array(); + foreach ($this->_processesCollection as $process) { + $code = $process->getIndexerCode(); + if (in_array($code, $processed)) { + continue; + } + + if ($process->getDepends()) { + foreach ($process->getDepends() as $processCode) { + $dependProcess = $this->getProcessByCode($processCode); + if ($dependProcess && !in_array($processCode, $processed)) { + if ($this->_changeProcessKeyStatus($dependProcess, $enable)) { + $processed[] = $processCode; + } + } + } + } + + if ($this->_changeProcessKeyStatus($process, $enable)) { + $processed[] = $code; + } + } + + return $this; + } + + /** + * Check if the event will be processed and disable/enable keys in index tables + * + * @param mixed|Mage_Index_Model_Process $process + * @param bool $enable + * @return bool + */ + protected function _changeProcessKeyStatus($process, $enable = true) + { + $event = $this->_currentEvent; + if ($process instanceof Mage_Index_Model_Process + && $process->getMode() !== Mage_Index_Model_Process::MODE_MANUAL + && !$process->isLocked() + && (is_null($event) + || ($event instanceof Mage_Index_Model_Event && $process->matchEvent($event)) + || (is_array($event) && $process->matchEntityAndType($event[0], $event[1])) + )) { + if ($enable) { + $process->enableIndexerKeys(); + } else { + $process->disableIndexerKeys(); + } + return true; + } + return false; + } + + /** + * Allow DDL operations while indexing + * + * @return Mage_Index_Model_Indexer + */ + public function allowTableChanges() + { + $this->_allowTableChanges = true; + return $this; + } + + /** + * Disallow DDL operations while indexing + * + * @return Mage_Index_Model_Indexer + */ + public function disallowTableChanges() + { + $this->_allowTableChanges = false; + return $this; + } + + /** + * Get event type name + * + * @param null|string $entityType + * @param null|string $eventType + * @return string + */ + protected function _getEventTypeName($entityType = null, $eventType = null) + { + $eventName = $entityType . '_' . $eventType; + $eventName = trim($eventName, '_'); + if (!empty($eventName)) { + $eventName = '_' . $eventName; } + return $eventName; } } diff --git a/app/code/core/Mage/Index/Model/Indexer/Abstract.php b/app/code/core/Mage/Index/Model/Indexer/Abstract.php index dc35c173..99f8a1d5 100644 --- a/app/code/core/Mage/Index/Model/Indexer/Abstract.php +++ b/app/code/core/Mage/Index/Model/Indexer/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -32,6 +32,21 @@ abstract class Mage_Index_Model_Indexer_Abstract extends Mage_Core_Model_Abstrac { protected $_matchedEntities = array(); + /** + * Whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @var bool + */ + protected $_allowTableChanges = true; + + /** + * Whether the indexer should be displayed on process/list page + * + * @var bool + */ + protected $_isVisible = true; + /** * Get Indexer name * @@ -44,7 +59,10 @@ abstract public function getName(); * * @return string */ - abstract public function getDescription(); + public function getDescription() + { + return ''; + } /** * Register indexer required data inside event object @@ -140,9 +158,73 @@ public function callEventHandler(Mage_Index_Model_Event $event) $method = $this->_camelize($event->getType()); } - if (method_exists($this->_getResource(), $method)) { - $this->_getResource()->$method($event); + $resourceModel = $this->_getResource(); + if (method_exists($resourceModel, $method)) { + $resourceModel->$method($event); + } + return $this; + } + + /** + * Set whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @param bool $value + * @return Mage_Index_Model_Indexer_Abstract + */ + public function setAllowTableChanges($value = true) + { + $this->_allowTableChanges = $value; + return $this; + } + + /** + * Disable resource table keys + * + * @return Mage_Index_Model_Indexer_Abstract + */ + public function disableKeys() + { + if (empty($this->_resourceName)) { + return $this; + } + + $resourceModel = $this->getResource(); + if ($resourceModel instanceof Mage_Index_Model_Resource_Abstract) { + $resourceModel->useDisableKeys(true); + $resourceModel->disableTableKeys(); } + return $this; } + + /** + * Enable resource table keys + * + * @return Mage_Index_Model_Indexer_Abstract + */ + public function enableKeys() + { + if (empty($this->_resourceName)) { + return $this; + } + + $resourceModel = $this->getResource(); + if ($resourceModel instanceof Mage_Index_Model_Resource_Abstract) { + $resourceModel->useDisableKeys(true); + $resourceModel->enableTableKeys(); + } + + return $this; + } + + /** + * Whether the indexer should be displayed on process/list page + * + * @return bool + */ + public function isVisible() + { + return $this->_isVisible; + } } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Abstract.php b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php index 205d3990..54ce51fa 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Abstract.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Abstract.php @@ -20,183 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Abstract resource model. Can be used as base for indexer resources + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team */ -abstract class Mage_Index_Model_Mysql4_Abstract extends Mage_Core_Model_Mysql4_Abstract +abstract class Mage_Index_Model_Mysql4_Abstract extends Mage_Index_Model_Resource_Abstract { - const IDX_SUFFIX = '_idx'; - const TMP_SUFFIX = '_tmp'; - - /** - * Flag that defines if need to use "_idx" index table suffix instead of "_tmp" - * - * @var bool - */ - protected $_isNeedUseIdxTable = false; - - public function reindexAll() - { - $this->useIdxTable(true); - return $this; - } - - /** - * Get DB adapter for index data processing - * - * @return Varien_Db_Adapter_Pdo_Mysql - */ - protected function _getIndexAdapter() - { - return $this->_getWriteAdapter(); - } - - /** - * Get index table name with additional suffix - * - * @param string $table - * @return string - */ - public function getIdxTable($table = null) - { - $suffix = self::TMP_SUFFIX; - if ($this->_isNeedUseIdxTable) { - $suffix = self::IDX_SUFFIX; - } - if ($table) { - return $table . $suffix; - } - return $this->getMainTable() . $suffix; - } - - /** - * Synchronize data between index storage and original storage - * - * @return Mage_Index_Model_Mysql4_Abstract - */ - public function syncData() - { - $this->beginTransaction(); - /** - * Can't use truncate because of transaction - */ - $this->_getWriteAdapter()->delete($this->getMainTable()); - $this->insertFromTable($this->getIdxTable(), $this->getMainTable(), false); - $this->commit(); - return $this; - } - - /** - * Create temporary table for index data pregeneration - * - * @return Mage_Index_Model_Mysql4_Abstract - */ - public function cloneIndexTable($asOriginal = false) - { - $mainTable = $this->getMainTable(); - $idxTable = $this->getIdxTable(); - $idxAdapter = $this->_getIndexAdapter(); - - $sql = 'DROP TABLE IF EXISTS ' . $idxAdapter->quoteIdentifier($idxTable); - $idxAdapter->query($sql); - if ($asOriginal) { - $sql = 'CREATE TABLE ' . $idxAdapter->quoteIdentifier($idxTable) - . ' LIKE ' . $idxAdapter->quoteIdentifier($this->getMainTable()); - } else { - $sql = 'CREATE TABLE ' . $idxAdapter->quoteIdentifier($idxTable) - . ' SELECT * FROM ' . $idxAdapter->quoteIdentifier($mainTable) . ' LIMIT 0'; - } - $idxAdapter->query($sql); - return $this; - } - - /** - * Copy data from source table of read adapter to destination table of index adapter - * - * @param string $sourceTable - * @param string $destTable - * @param bool $readToIndex data migration direction (true - read=>index, false - index=>read) - * @return Mage_Index_Model_Mysql4_Abstract - */ - public function insertFromTable($sourceTable, $destTable, $readToIndex=true) - { - if ($readToIndex) { - $columns = $this->_getWriteAdapter()->describeTable($sourceTable); - } else { - $columns = $this->_getIndexAdapter()->describeTable($sourceTable); - } - $columns = array_keys($columns); - $select = 'SELECT * FROM ' . $sourceTable; - return $this->insertFromSelect($select, $destTable, $columns, $readToIndex); - } - - /** - * Insert data from select statement of read adapter to - * destination table related with index adapter - * - * @param string $select - * @param string $destTable - * @param array $columns - * @param bool $readToIndex data migration direction (true - read=>index, false - index=>read) - * @return Mage_Index_Model_Mysql4_Abstract - * */ - public function insertFromSelect($select, $destTable, array $columns, $readToIndex=true) - { - if ($readToIndex) { - $from = $this->_getWriteAdapter(); - $to = $this->_getIndexAdapter(); - } else { - $from = $this->_getIndexAdapter(); - $to = $this->_getWriteAdapter(); - } - $to->query("ALTER TABLE {$destTable} DISABLE KEYS"); - if ($from === $to) { - $sql = 'INSERT INTO ' . $destTable . ' ' . $select; - $to->query($sql); - } else { - $stmt = $from->query($select); - $data = array(); - $counter = 0; - while ($row = $stmt->fetch(PDO::FETCH_NUM)) { - $data[] = $row; - $counter++; - if ($counter>2000) { - $to->insertArray($destTable, $columns, $data); - $data = array(); - $counter = 0; - } - } - if (!empty($data)) { - $to->insertArray($destTable, $columns, $data); - } - } - $to->query("ALTER TABLE {$destTable} ENABLE KEYS"); - return $this; - } - - /** - * Set or get what either "_idx" or "_tmp" suffixed temporary index table need to use - * - * @param bool $value - * @return Mage_Index_Model_Mysql4_Abstract - */ - public function useIdxTable($value = null) - { - if (!is_null($value)) { - $this->_isNeedUseIdxTable = (bool)$value; - } - return $this->_isNeedUseIdxTable; - } - - /** - * Clean up temporary index table - */ - public function clearTemporaryIndexTable() - { - $this->_getWriteAdapter()->delete($this->getIdxTable()); - } } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Event.php b/app/code/core/Mage/Index/Model/Mysql4/Event.php index 0159a3de..dda5fcb4 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Event.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Event.php @@ -20,68 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Index_Model_Mysql4_Event extends Mage_Core_Model_Mysql4_Abstract -{ - protected function _construct() - { - $this->_init('index/event', 'event_id'); - } - - /** - * Check if semilar event exist before start saving data - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Index_Model_Mysql4_Event - */ - protected function _beforeSave(Mage_Core_Model_Abstract $object) - { - /** - * Check if event already exist and merge previous data - */ - if (!$object->getId()) { - $select = $this->_getReadAdapter()->select() - ->from($this->getMainTable()) - ->where('type=?', $object->getType()) - ->where('entity=?', $object->getEntity()); - if ($object->hasEntityPk()) { - $select->where('entity_pk=?', $object->getEntityPk()); - } - $data = $this->_getWriteAdapter()->fetchRow($select); - if ($data) { - $object->mergePreviousData($data); - } - } - return parent::_beforeSave($object); - } - /** - * Save assigned processes - * - * @param Mage_Core_Model_Abstract $object - * @return Mage_Index_Model_Mysql4_Event - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $processIds = $object->getProcessIds(); - if (is_array($processIds)) { - $processTable = $this->getTable('index/process_event'); - if (empty($processIds)) { - $this->_getWriteAdapter()->delete($processTable); - } else { - foreach ($processIds as $processId => $processStatus) { - $data = array( - 'process_id'=> $processId, - 'event_id' => $object->getId(), - 'status' => $processStatus - ); - $this->_getWriteAdapter()->insertOnDuplicate($processTable, $data, array('status')); - } - } - } - return parent::_afterSave($object); - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ +class Mage_Index_Model_Mysql4_Event extends Mage_Index_Model_Resource_Event +{ } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php b/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php index 5f6bb125..707fc48b 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Event/Collection.php @@ -20,103 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Index_Model_Mysql4_Event_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract -{ - /** - * Initialize resource - */ - protected function _construct() - { - $this->_init('index/event'); - } - - /** - * Add filter by entity - * - * @param string | array $entity - * @return Mage_Index_Model_Mysql4_Event_Collection - */ - public function addEntityFilter($entity) - { - if (is_array($entity) && !empty($entity)) { - $this->getSelect()->where('entity IN (?)', $entity); - } else { - $this->getSelect()->where('entity = ?', $entity); - } - return $this; - } - - /** - * Add filter by type - * - * @param string | array $type - * @return Mage_Index_Model_Mysql4_Event_Collection - */ - public function addTypeFilter($type) - { - if (is_array($type) && !empty($type)) { - $this->getSelect()->where('type IN (?)', $type); - } else { - $this->getSelect()->where('type = ?', $type); - } - return $this; - } - - /** - * Add filter by process and status to events collection - * - * @param $process - * @param $status - * @return Mage_Index_Model_Mysql4_Event_Collection - */ - public function addProcessFilter($process, $status=null) - { - $this->_joinProcessEventTable(); - if ($process instanceof Mage_Index_Model_Process) { - $this->getSelect()->where('process_event.process_id = ?', $process->getId()); - } elseif (is_array($process) && !empty($process)) { - $this->getSelect()->where('process_event.process_id IN (?)', $process); - } else { - $this->getSelect()->where('process_event.process_id = ?', $process); - } - - if ($status !== null) { - $this->getSelect()->where('process_event.status = ?', $status); - } - return $this; - } - - /** - * Join index_process_event table to event table - * - * @return Mage_Index_Model_Mysql4_Event_Collection - */ - protected function _joinProcessEventTable() - { - if (!$this->getFlag('process_event_table_joined')) { - $this->getSelect()->join(array('process_event' => $this->getTable('index/process_event')), - 'process_event.event_id=main_table.event_id', - array('process_event_status' => 'status') - ); - $this->setFlag('process_event_table_joined', true); - } - return $this; - } - /** - * Reset collection state - * - * @return Mage_Index_Model_Mysql4_Event_Collection - */ - public function reset() - { - $this->_totalRecords = null; - $this->_data = null; - $this->_isCollectionLoaded = false; - return $this; - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ +class Mage_Index_Model_Mysql4_Event_Collection extends Mage_Index_Model_Resource_Event_Collection +{ } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Process.php b/app/code/core/Mage/Index/Model/Mysql4/Process.php index 4628b4e1..b38c5e17 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Process.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Process.php @@ -20,91 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Index_Model_Mysql4_Process extends Mage_Core_Model_Mysql4_Abstract -{ - /** - * Initialize table and table pk - */ - protected function _construct() - { - $this->_init('index/process', 'process_id'); - } - - /** - * Update process/event association row status - * - * @param int $processId - * @param int $eventId - * @param string $status - * @return Mage_Index_Model_Mysql4_Process - */ - public function updateEventStatus($processId, $eventId, $status) - { - $adapter = $this->_getWriteAdapter(); - $condition = $adapter->quoteInto('process_id = ? AND ', $processId) - . $adapter->quoteInto('event_id = ?', $eventId); - $adapter->update($this->getTable('index/process_event'), array('status' => $status), $condition); - return $this; - } - - /** - * Register process end - * - * @param Mage_Index_Model_Process $process - * @return Mage_Index_Model_Mysql4_Process - */ - public function endProcess(Mage_Index_Model_Process $process) - { - $data = array( - 'status' => Mage_Index_Model_Process::STATUS_PENDING, - 'ended_at' =>$this->formatDate(time()), - ); - $this->_getWriteAdapter()->update( - $this->getMainTable(), - $data, - $this->_getWriteAdapter()->quoteInto('process_id=?', $process->getId()) - ); - return $this; - } - /** - * Register process start - * - * @param Mage_Index_Model_Process $process - * @return Mage_Index_Model_Mysql4_Process - */ - public function startProcess(Mage_Index_Model_Process $process) - { - $data = array( - 'status' => Mage_Index_Model_Process::STATUS_RUNNING, - 'started_at'=>$this->formatDate(time()), - ); - $this->_getWriteAdapter()->update( - $this->getMainTable(), - $data, - $this->_getWriteAdapter()->quoteInto('process_id=?', $process->getId()) - ); - return $this; - } - /** - * Update process status field - * - * @param Mage_Index_Model_Process - * @param string status - * @return Mage_Index_Model_Mysql4_Process - */ - public function updateStatus($process, $status) - { - $data = array('status' => $status); - $this->_getWriteAdapter()->update( - $this->getMainTable(), - $data, - $this->_getWriteAdapter()->quoteInto('process_id=?', $process->getId()) - ); - return $this; - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ +class Mage_Index_Model_Mysql4_Process extends Mage_Index_Model_Resource_Process +{ } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php b/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php index 953c47b2..4841a8b8 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Process/Collection.php @@ -20,13 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Index_Model_Mysql4_Process_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract + + +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ +class Mage_Index_Model_Mysql4_Process_Collection extends Mage_Index_Model_Resource_Process_Collection { - protected function _construct() - { - $this->_init('index/process'); - } } diff --git a/app/code/core/Mage/Index/Model/Mysql4/Setup.php b/app/code/core/Mage/Index/Model/Mysql4/Setup.php index 603ea87b..03a780a5 100644 --- a/app/code/core/Mage/Index/Model/Mysql4/Setup.php +++ b/app/code/core/Mage/Index/Model/Mysql4/Setup.php @@ -20,54 +20,18 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ -class Mage_Index_Model_Mysql4_Setup extends Mage_Core_Model_Resource_Setup -{ - /** - * Apply Index moduke DB updates and sync indexes declaration - */ - public function applyUpdates() - { - parent::applyUpdates(); - $this->_syncIndexes(); - } - - /** - * Sync indexes declarations in config and in DB - */ - protected function _syncIndexes() - { - $connection = $this->getConnection(); - if (!$connection) { - return $this; - } - $indexes = Mage::getConfig()->getNode(Mage_Index_Model_Process::XML_PATH_INDEXER_DATA); - $indexCodes = array(); - foreach ($indexes->children() as $code => $index) { - $indexCodes[] = $code; - } - $table = $this->getTable('index/process'); - $existingIndexes = $connection->fetchCol('SELECT indexer_code FROM '.$table); - $delete = array_diff($existingIndexes, $indexCodes); - $insert = array_diff($indexCodes, $existingIndexes); - if (!empty($delete)) { - $connection->delete($table, $connection->quoteInto('indexer_code IN (?)', $delete)); - } - if (!empty($insert)) { - $inserData = array(); - foreach ($insert as $code) { - $inserData[] = array( - 'indexer_code' => $code, - 'status' => Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX - ); - } - if (method_exists($connection, 'insertArray')) { - $connection->insertArray($table, array('indexer_code', 'status'), $inserData); - } - } - } +/** + * Enter description here ... + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ +class Mage_Index_Model_Mysql4_Setup extends Mage_Index_Model_Resource_Setup +{ } diff --git a/app/code/core/Mage/Index/Model/Observer.php b/app/code/core/Mage/Index/Model/Observer.php index a4184a7a..f373903d 100644 --- a/app/code/core/Mage/Index/Model/Observer.php +++ b/app/code/core/Mage/Index/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -35,7 +35,7 @@ class Mage_Index_Model_Observer public function __construct() { - $this->_indexer = Mage::getModel('index/indexer'); + $this->_indexer = Mage::getSingleton('index/indexer'); } /** diff --git a/app/code/core/Mage/Index/Model/Process.php b/app/code/core/Mage/Index/Model/Process.php index 8f66e600..014270af 100644 --- a/app/code/core/Mage/Index/Model/Process.php +++ b/app/code/core/Mage/Index/Model/Process.php @@ -20,13 +20,33 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ +/** + * Enter description here ... + * + * @method Mage_Index_Model_Resource_Process _getResource() + * @method Mage_Index_Model_Resource_Process getResource() + * @method string getIndexerCode() + * @method Mage_Index_Model_Process setIndexerCode(string $value) + * @method string getStatus() + * @method Mage_Index_Model_Process setStatus(string $value) + * @method string getStartedAt() + * @method Mage_Index_Model_Process setStartedAt(string $value) + * @method string getEndedAt() + * @method Mage_Index_Model_Process setEndedAt(string $value) + * @method string getMode() + * @method Mage_Index_Model_Process setMode(string $value) + * + * @category Mage + * @package Mage_Index + * @author Magento Core Team + */ class Mage_Index_Model_Process extends Mage_Core_Model_Abstract { - const XML_PATH_INDEXER_DATA = 'global/index/indexer'; + const XML_PATH_INDEXER_DATA = 'global/index/indexer'; /** * Process statuses */ @@ -37,17 +57,17 @@ class Mage_Index_Model_Process extends Mage_Core_Model_Abstract /** * Process event statuses */ - const EVENT_STATUS_NEW = 'new'; - const EVENT_STATUS_DONE = 'done'; - const EVENT_STATUS_ERROR = 'error'; - const EVENT_STATUS_WORKING = 'working'; + const EVENT_STATUS_NEW = 'new'; + const EVENT_STATUS_DONE = 'done'; + const EVENT_STATUS_ERROR = 'error'; + const EVENT_STATUS_WORKING = 'working'; /** * Process modes * Process mode allow disable automatic process events processing */ - const MODE_MANUAL = 'manual'; - const MODE_REAL_TIME= 'real_time'; + const MODE_MANUAL = 'manual'; + const MODE_REAL_TIME = 'real_time'; /** * Indexer stategy object @@ -62,6 +82,14 @@ class Mage_Index_Model_Process extends Mage_Core_Model_Abstract protected $_isLocked = null; protected $_lockFile = null; + /** + * Whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @var bool + */ + protected $_allowTableChanges = true; + /** * Initialize resource */ @@ -87,7 +115,8 @@ protected function _setEventNamespace(Mage_Index_Model_Event $event) /** * Remove indexer namespace from event * - * @return Mage_Index_Model_Process + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Process */ protected function _resetEventNamespace($event) { @@ -100,6 +129,7 @@ protected function _resetEventNamespace($event) * Register data required by process in event object * * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Process */ public function register(Mage_Index_Model_Event $event) { @@ -108,6 +138,9 @@ public function register(Mage_Index_Model_Event $event) $this->getIndexer()->register($event); $event->addProcessId($this->getId()); $this->_resetEventNamespace($event); + if ($this->getMode() == self::MODE_MANUAL) { + $this->_getResource()->updateStatus($this, self::STATUS_REQUIRE_REINDEX); + } } return $this; @@ -124,21 +157,71 @@ public function matchEvent(Mage_Index_Model_Event $event) return $this->getIndexer()->matchEvent($event); } + /** + * Check if specific entity and action type is matched + * + * @param string $entity + * @param string $type + * @return bool + */ + public function matchEntityAndType($entity, $type) + { + if ($entity !== null && $type !== null) { + return $this->getIndexer()->matchEntityAndType($entity, $type); + } + return true; + } + /** * Reindex all data what this process responsible is * - * @return unknown_type */ public function reindexAll() { if ($this->isLocked()) { Mage::throwException(Mage::helper('index')->__('%s Index process is working now. Please try run this process later.', $this->getIndexer()->getName())); } + + $processStatus = $this->getStatus(); + $this->_getResource()->startProcess($this); $this->lock(); - $this->getIndexer()->reindexAll(); - $this->unlock(); - $this->_getResource()->endProcess($this); + try { + $eventsCollection = $this->getUnprocessedEventsCollection(); + + /** @var $eventResource Mage_Index_Model_Resource_Event */ + $eventResource = Mage::getResourceSingleton('index/event'); + + if ($eventsCollection->count() > 0 && $processStatus == self::STATUS_PENDING + || $this->getForcePartialReindex() + ) { + $this->_getResource()->beginTransaction(); + try { + $this->_processEventsCollection($eventsCollection, false); + $this->_getResource()->commit(); + } catch (Exception $e) { + $this->_getResource()->rollBack(); + throw $e; + } + } else { + //Update existing events since we'll do reindexAll + $eventResource->updateProcessEvents($this); + $this->getIndexer()->reindexAll(); + } + $this->unlock(); + + $unprocessedEvents = $eventResource->getUnprocessedEvents($this); + if ($this->getMode() == self::MODE_MANUAL && (count($unprocessedEvents) > 0)) { + $this->_getResource()->updateStatus($this, self::STATUS_REQUIRE_REINDEX); + } else { + $this->_getResource()->endProcess($this); + } + } catch (Exception $e) { + $this->unlock(); + $this->_getResource()->failProcess($this); + throw $e; + } + Mage::dispatchEvent('after_reindex_process_' . $this->getIndexerCode()); } /** @@ -153,6 +236,11 @@ public function reindexEverything() return $this; } + /** @var $eventResource Mage_Index_Model_Resource_Event */ + $eventResource = Mage::getResourceSingleton('index/event'); + $unprocessedEvents = $eventResource->getUnprocessedEvents($this); + $this->setForcePartialReindex(count($unprocessedEvents) > 0 && $this->getStatus() == self::STATUS_PENDING); + if ($this->getDepends()) { $indexer = Mage::getSingleton('index/indexer'); foreach ($this->getDepends() as $code) { @@ -175,17 +263,28 @@ public function reindexEverything() */ public function processEvent(Mage_Index_Model_Event $event) { - if ($this->getMode() == self::MODE_MANUAL) { + if (!$this->matchEvent($event)) { return $this; } - if (!$this->getIndexer()->matchEvent($event)) { + if ($this->getMode() == self::MODE_MANUAL) { + $this->changeStatus(self::STATUS_REQUIRE_REINDEX); return $this; } + + $this->_getResource()->updateProcessStartDate($this); $this->_setEventNamespace($event); - $this->getIndexer()->processEvent($event); + $isError = false; + + try { + $this->getIndexer()->processEvent($event); + } catch (Exception $e) { + $isError = true; + } $event->resetData(); $this->_resetEventNamespace($event); - $event->addProcessId($this->getId(), self::EVENT_STATUS_DONE); + $this->_getResource()->updateProcessEndDate($this); + $event->addProcessId($this->getId(), $isError ? self::EVENT_STATUS_ERROR : self::EVENT_STATUS_DONE); + return $this; } @@ -241,36 +340,56 @@ public function indexEvents($entity=null, $type=null) if ($this->isLocked()) { return $this; } + $this->lock(); + try { + /** + * Prepare events collection + */ + $eventsCollection = $this->getUnprocessedEventsCollection(); + if ($entity !== null) { + $eventsCollection->addEntityFilter($entity); + } + if ($type !== null) { + $eventsCollection->addTypeFilter($type); + } - /** - * Prepare events collection - */ - $eventsCollection = Mage::getResourceModel('index/event_collection') - ->addProcessFilter($this, self::EVENT_STATUS_NEW); - if ($entity !== null) { - $eventsCollection->addEntityFilter($entity); - } - if ($type !== null) { - $eventsCollection->addTypeFilter($type); + $this->_processEventsCollection($eventsCollection); + $this->unlock(); + } catch (Exception $e) { + $this->unlock(); + throw $e; } + return $this; + } - /** - * Process all new events - */ - while ($eventsCollection->getSize()) { - foreach ($eventsCollection as $event) { - try { - $this->processEvent($event); - } catch (Exception $e) { - $event->addProcessId($this->getId(), self::EVENT_STATUS_ERROR); + /** + * Process all events of the collection + * + * @param Mage_Index_Model_Resource_Event_Collection $eventsCollection + * @param bool $skipUnmatched + * @return Mage_Index_Model_Process + */ + protected function _processEventsCollection( + Mage_Index_Model_Resource_Event_Collection $eventsCollection, + $skipUnmatched = true + ) { + // We can't reload the collection because of transaction + /** @var $event Mage_Index_Model_Event */ + while ($event = $eventsCollection->fetchItem()) { + try { + $this->processEvent($event); + if (!$skipUnmatched) { + $eventProcessIds = $event->getProcessIds(); + if (!isset($eventProcessIds[$this->getId()])) { + $event->addProcessId($this->getId(), null); + } } - $event->save(); + } catch (Exception $e) { + $event->addProcessId($this->getId(), self::EVENT_STATUS_ERROR); } - $eventsCollection->reset(); + $event->save(); } - - $this->unlock(); return $this; } @@ -383,6 +502,10 @@ public function __destruct() */ public function changeStatus($status) { + Mage::dispatchEvent('index_process_change_status', array( + 'process' => $this, + 'status' => $status + )); $this->_getResource()->updateStatus($this, $status); return $this; } @@ -414,6 +537,19 @@ public function getStatusesOptions() ); } + /** + * Get list of "Update Required" options + * + * @return array + */ + public function getUpdateRequiredOptions() + { + return array( + 0 => Mage::helper('index')->__('No'), + 1 => Mage::helper('index')->__('Yes'), + ); + } + /** * Retrieve depend indexer codes * @@ -438,4 +574,83 @@ public function getDepends() return $depends; } + + /** + * Set whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @param bool $value + * @return Mage_Index_Model_Process + */ + public function setAllowTableChanges($value = true) + { + $this->_allowTableChanges = $value; + return $this; + } + + /** + * Disable keys in index table + * + * @return Mage_Index_Model_Process + */ + public function disableIndexerKeys() + { + $indexer = $this->getIndexer(); + if ($indexer) { + $indexer->disableKeys(); + } + return $this; + } + + /** + * Enable keys in index table + * + * @return Mage_Index_Model_Process + */ + public function enableIndexerKeys() + { + $indexer = $this->getIndexer(); + if ($indexer) { + $indexer->enableKeys(); + } + return $this; + } + + /** + * Process event with locks checking + * + * @param Mage_Index_Model_Event $event + * @return Mage_Index_Model_Process + */ + public function safeProcessEvent(Mage_Index_Model_Event $event) + { + if ($this->isLocked()) { + return $this; + } + if (!$this->matchEvent($event)) { + return $this; + } + $this->lock(); + try { + $this->processEvent($event); + $this->unlock(); + } catch (Exception $e) { + $this->unlock(); + throw $e; + } + return $this; + } + + /** + * Get unprocessed events collection + * + * @return Mage_Index_Model_Resource_Event_Collection + */ + public function getUnprocessedEventsCollection() + { + /** @var $eventsCollection Mage_Index_Model_Resource_Event_Collection */ + $eventsCollection = Mage::getResourceModel('index/event_collection'); + $eventsCollection->addProcessFilter($this, self::EVENT_STATUS_NEW); + return $eventsCollection; + } } diff --git a/app/code/core/Mage/Index/Model/Resource/Abstract.php b/app/code/core/Mage/Index/Model/Resource/Abstract.php new file mode 100755 index 00000000..e9b02951 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Abstract.php @@ -0,0 +1,276 @@ + + */ +abstract class Mage_Index_Model_Resource_Abstract extends Mage_Core_Model_Resource_Db_Abstract +{ + const IDX_SUFFIX= '_idx'; + const TMP_SUFFIX= '_tmp'; + + /** + * Flag that defines if need to use "_idx" index table suffix instead of "_tmp" + * + * @var bool + */ + protected $_isNeedUseIdxTable = false; + + /** + * Flag that defines if need to disable keys during data inserting + * + * @var bool + */ + protected $_isDisableKeys = true; + + /** + * Whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @var bool + */ + protected $_allowTableChanges = true; + + /** + * Reindex all + * + * @return Mage_Index_Model_Resource_Abstract + */ + public function reindexAll() + { + $this->useIdxTable(true); + return $this; + } + + /** + * Get DB adapter for index data processing + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getIndexAdapter() + { + return $this->_getWriteAdapter(); + } + + /** + * Get index table name with additional suffix + * + * @param string $table + * @return string + */ + public function getIdxTable($table = null) + { + $suffix = self::TMP_SUFFIX; + if ($this->_isNeedUseIdxTable) { + $suffix = self::IDX_SUFFIX; + } + if ($table) { + return $table . $suffix; + } + return $this->getMainTable() . $suffix; + } + + /** + * Synchronize data between index storage and original storage + * + * @return Mage_Index_Model_Resource_Abstract + */ + public function syncData() + { + $this->beginTransaction(); + try { + /** + * Can't use truncate because of transaction + */ + $this->_getWriteAdapter()->delete($this->getMainTable()); + $this->insertFromTable($this->getIdxTable(), $this->getMainTable(), false); + $this->commit(); + } catch (Exception $e) { + $this->rollBack(); + throw $e; + } + return $this; + } + + /** + * Create temporary table for index data pregeneration + * + * @deprecated since 1.5.0.0 + * @param bool $asOriginal + * @return Mage_Index_Model_Resource_Abstract + */ + public function cloneIndexTable($asOriginal = false) + { + return $this; + } + + /** + * Copy data from source table of read adapter to destination table of index adapter + * + * @param string $sourceTable + * @param string $destTable + * @param bool $readToIndex data migration direction (true - read=>index, false - index=>read) + * @return Mage_Index_Model_Resource_Abstract + */ + public function insertFromTable($sourceTable, $destTable, $readToIndex = true) + { + if ($readToIndex) { + $sourceColumns = array_keys($this->_getWriteAdapter()->describeTable($sourceTable)); + $targetColumns = array_keys($this->_getWriteAdapter()->describeTable($destTable)); + } else { + $sourceColumns = array_keys($this->_getIndexAdapter()->describeTable($sourceTable)); + $targetColumns = array_keys($this->_getWriteAdapter()->describeTable($destTable)); + } + $select = $this->_getIndexAdapter()->select()->from($sourceTable, $sourceColumns); + + Mage::getResourceHelper('index')->insertData($this, $select, $destTable, $targetColumns, $readToIndex); + return $this; + } + + /** + * Insert data from select statement of read adapter to + * destination table related with index adapter + * + * @param Varien_Db_Select $select + * @param string $destTable + * @param array $columns + * @param bool $readToIndex data migration direction (true - read=>index, false - index=>read) + * @return Mage_Index_Model_Resource_Abstract + */ + public function insertFromSelect($select, $destTable, array $columns, $readToIndex = true) + { + if ($readToIndex) { + $from = $this->_getWriteAdapter(); + $to = $this->_getIndexAdapter(); + } else { + $from = $this->_getIndexAdapter(); + $to = $this->_getWriteAdapter(); + } + + if ($from === $to) { + $query = $select->insertFromSelect($destTable, $columns); + $to->query($query); + } else { + $stmt = $from->query($select); + $data = array(); + $counter = 0; + while ($row = $stmt->fetch(PDO::FETCH_NUM)) { + $data[] = $row; + $counter++; + if ($counter>2000) { + $to->insertArray($destTable, $columns, $data); + $data = array(); + $counter = 0; + } + } + if (!empty($data)) { + $to->insertArray($destTable, $columns, $data); + } + } + + return $this; + } + + /** + * Set or get what either "_idx" or "_tmp" suffixed temporary index table need to use + * + * @param bool $value + * @return bool + */ + public function useIdxTable($value = null) + { + if (!is_null($value)) { + $this->_isNeedUseIdxTable = (bool)$value; + } + return $this->_isNeedUseIdxTable; + } + + /** + * Set or get flag that defines if need to disable keys during data inserting + * + * @param bool $value + * @return bool + */ + public function useDisableKeys($value = null) + { + if (!is_null($value)) { + $this->_isDisableKeys = (bool)$value; + } + return $this->_isDisableKeys; + } + + /** + * Clean up temporary index table + * + */ + public function clearTemporaryIndexTable() + { + $this->_getWriteAdapter()->delete($this->getIdxTable()); + } + + /** + * Set whether table changes are allowed + * + * @deprecated after 1.6.1.0 + * @param bool $value + * @return Mage_Index_Model_Resource_Abstract + */ + public function setAllowTableChanges($value = true) + { + $this->_allowTableChanges = $value; + return $this; + } + + /** + * Disable Main Table keys + * + * @return Mage_Index_Model_Resource_Abstract + */ + public function disableTableKeys() + { + if ($this->useDisableKeys()) { + $this->_getWriteAdapter()->disableTableKeys($this->getMainTable()); + } + return $this; + } + + /** + * Enable Main Table keys + * + * @return Mage_Index_Model_Resource_Abstract + */ + public function enableTableKeys() + { + if ($this->useDisableKeys()) { + $this->_getWriteAdapter()->enableTableKeys($this->getMainTable()); + } + return $this; + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Event.php b/app/code/core/Mage/Index/Model/Resource/Event.php new file mode 100755 index 00000000..1f8da415 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Event.php @@ -0,0 +1,148 @@ + + */ +class Mage_Index_Model_Resource_Event extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Enter description here ... + * + */ + protected function _construct() + { + $this->_init('index/event', 'event_id'); + } + + /** + * Check if semilar event exist before start saving data + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Index_Model_Resource_Event + */ + protected function _beforeSave(Mage_Core_Model_Abstract $object) + { + /** + * Check if event already exist and merge previous data + */ + if (!$object->getId()) { + $select = $this->_getReadAdapter()->select() + ->from($this->getMainTable()) + ->where('type=?', $object->getType()) + ->where('entity=?', $object->getEntity()); + if ($object->hasEntityPk()) { + $select->where('entity_pk=?', $object->getEntityPk()); + } + $data = $this->_getWriteAdapter()->fetchRow($select); + if ($data) { + $object->mergePreviousData($data); + } + } + $object->cleanNewData(); + return parent::_beforeSave($object); + } + + /** + * Save assigned processes + * + * @param Mage_Core_Model_Abstract $object + * @return Mage_Index_Model_Resource_Event + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $processIds = $object->getProcessIds(); + if (is_array($processIds)) { + $processTable = $this->getTable('index/process_event'); + if (empty($processIds)) { + $this->_getWriteAdapter()->delete($processTable); + } else { + foreach ($processIds as $processId => $processStatus) { + if (is_null($processStatus) || $processStatus == Mage_Index_Model_Process::EVENT_STATUS_DONE) { + $this->_getWriteAdapter()->delete($processTable, array( + 'process_id = ?' => $processId, + 'event_id = ?' => $object->getId(), + )); + continue; + } + $data = array( + 'process_id' => $processId, + 'event_id' => $object->getId(), + 'status' => $processStatus + ); + $this->_getWriteAdapter()->insertOnDuplicate($processTable, $data, array('status')); + } + } + } + return parent::_afterSave($object); + } + + /** + * Update status for events of process + * + * @param int|array|Mage_Index_Model_Process $process + * @param string $status + * @return Mage_Index_Model_Resource_Event + */ + public function updateProcessEvents($process, $status = Mage_Index_Model_Process::EVENT_STATUS_DONE) + { + $whereCondition = ''; + if ($process instanceof Mage_Index_Model_Process) { + $whereCondition = array('process_id = ?' => $process->getId()); + } elseif (is_array($process) && !empty($process)) { + $whereCondition = array('process_id IN (?)' => $process); + } elseif (!is_array($whereCondition)) { + $whereCondition = array('process_id = ?' => $process); + } + $this->_getWriteAdapter()->update( + $this->getTable('index/process_event'), + array('status' => $status), + $whereCondition + ); + return $this; + } + + /** + * Retrieve unprocessed events list by specified process + * + * @param Mage_Index_Model_Process $process + * @return array + */ + public function getUnprocessedEvents($process) + { + $select = $this->_getReadAdapter()->select() + ->from($this->getTable('index/process_event')) + ->where('process_id = ?', $process->getId()) + ->where('status = ?', Mage_Index_Model_Process::EVENT_STATUS_NEW); + + return $this->_getReadAdapter()->fetchAll($select); + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Event/Collection.php b/app/code/core/Mage/Index/Model/Resource/Event/Collection.php new file mode 100755 index 00000000..39996a92 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Event/Collection.php @@ -0,0 +1,136 @@ + + */ +class Mage_Index_Model_Resource_Event_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource + * + */ + protected function _construct() + { + $this->_init('index/event'); + } + + /** + * Add filter by entity + * + * @param string | array $entity + * @return Mage_Index_Model_Resource_Event_Collection + */ + public function addEntityFilter($entity) + { + if (is_array($entity) && !empty($entity)) { + $this->addFieldToFilter('entity', array('in'=>$entity)); + } else { + $this->addFieldToFilter('entity', $entity); + } + return $this; + } + + /** + * Add filter by type + * + * @param string | array $type + * @return Mage_Index_Model_Resource_Event_Collection + */ + public function addTypeFilter($type) + { + if (is_array($type) && !empty($type)) { + $this->addFieldToFilter('type', array('in'=>$type)); + } else { + $this->addFieldToFilter('type', $type); + } + return $this; + } + + /** + * Add filter by process and status to events collection + * + * @param int|array|Mage_Index_Model_Process $process + * @param string $status + * @return Mage_Index_Model_Resource_Event_Collection + */ + public function addProcessFilter($process, $status = null) + { + $this->_joinProcessEventTable(); + if ($process instanceof Mage_Index_Model_Process) { + $this->addFieldToFilter('process_event.process_id', $process->getId()); + } elseif (is_array($process) && !empty($process)) { + $this->addFieldToFilter('process_event.process_id', array('in' => $process)); + } else { + $this->addFieldToFilter('process_event.process_id', $process); + } + + if ($status !== null) { + if (is_array($status) && !empty($status)) { + $this->addFieldToFilter('process_event.status', array('in' => $status)); + } else { + $this->addFieldToFilter('process_event.status', $status); + } + } + return $this; + } + + /** + * Join index_process_event table to event table + * + * @return Mage_Index_Model_Resource_Event_Collection + */ + protected function _joinProcessEventTable() + { + if (!$this->getFlag('process_event_table_joined')) { + $this->getSelect()->join(array('process_event' => $this->getTable('index/process_event')), + 'process_event.event_id=main_table.event_id', + array('process_event_status' => 'status') + ); + $this->setFlag('process_event_table_joined', true); + } + return $this; + } + + /** + * Reset collection state + * + * @return Mage_Index_Model_Resource_Event_Collection + */ + public function reset() + { + $this->_totalRecords = null; + $this->_data = null; + $this->_isCollectionLoaded = false; + $this->_items = array(); + return $this; + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Helper/Mysql4.php b/app/code/core/Mage/Index/Model/Resource/Helper/Mysql4.php new file mode 100644 index 00000000..de7a0f6c --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Helper/Mysql4.php @@ -0,0 +1,50 @@ + + */ +class Mage_Index_Model_Resource_Helper_Mysql4 extends Mage_Core_Model_Resource_Helper_Mysql4 +{ + /** + * Insert data from select statement + * + * @param Mage_Index_Model_Resource_Abstract $object + * @param Varien_Db_Select $select + * @param string $destTable + * @param array $columns + * @param bool $readToIndex + * @return Mage_Index_Model_Resource_Helper_Mysql4 + */ + public function insertData($object, $select, $destTable, $columns, $readToIndex) + { + return $object->insertFromSelect($select, $destTable, $columns, $readToIndex); + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Process.php b/app/code/core/Mage/Index/Model/Resource/Process.php new file mode 100755 index 00000000..5506eaa2 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Process.php @@ -0,0 +1,175 @@ + + */ +class Mage_Index_Model_Resource_Process extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Initialize table and table pk + * + */ + protected function _construct() + { + $this->_init('index/process', 'process_id'); + } + + /** + * Update process/event association row status + * + * @param int $processId + * @param int $eventId + * @param string $status + * @return Mage_Index_Model_Resource_Process + */ + public function updateEventStatus($processId, $eventId, $status) + { + $adapter = $this->_getWriteAdapter(); + $condition = array( + 'process_id = ?' => $processId, + 'event_id = ?' => $eventId + ); + $adapter->update($this->getTable('index/process_event'), array('status' => $status), $condition); + return $this; + } + + /** + * Register process end + * + * @param Mage_Index_Model_Process $process + * @return Mage_Index_Model_Resource_Process + */ + public function endProcess(Mage_Index_Model_Process $process) + { + $data = array( + 'status' => Mage_Index_Model_Process::STATUS_PENDING, + 'ended_at' => $this->formatDate(time()), + ); + $this->_updateProcessData($process->getId(), $data); + return $this; + } + + /** + * Register process start + * + * @param Mage_Index_Model_Process $process + * @return Mage_Index_Model_Resource_Process + */ + public function startProcess(Mage_Index_Model_Process $process) + { + $data = array( + 'status' => Mage_Index_Model_Process::STATUS_RUNNING, + 'started_at' => $this->formatDate(time()), + ); + $this->_updateProcessData($process->getId(), $data); + return $this; + } + + /** + * Register process fail + * + * @param Mage_Index_Model_Process $process + * @return Mage_Index_Model_Resource_Process + */ + public function failProcess(Mage_Index_Model_Process $process) + { + $data = array( + 'status' => Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX, + 'ended_at' => $this->formatDate(time()), + ); + $this->_updateProcessData($process->getId(), $data); + return $this; + } + + /** + * Update process status field + * + * + * @param Mage_Index_Model_Process $process + * @param string $status + * @return Mage_Index_Model_Resource_Process + */ + public function updateStatus($process, $status) + { + $data = array('status' => $status); + $this->_updateProcessData($process->getId(), $data); + return $this; + } + + /** + * Updates process data + * @param int $processId + * @param array $data + * @return Mage_Index_Model_Resource_Process + */ + protected function _updateProcessData($processId, $data) + { + $bind = array('process_id=?' => $processId); + $this->_getWriteAdapter()->update($this->getMainTable(), $data, $bind); + + return $this; + } + + /** + * Update process start date + * + * @param Mage_Index_Model_Process $process + * @return Mage_Index_Model_Resource_Process + */ + public function updateProcessStartDate(Mage_Index_Model_Process $process) + { + $this->_updateProcessData($process->getId(), array('started_at' => $this->formatDate(time()))); + return $this; + } + + /** + * Update process end date + * + * @param Mage_Index_Model_Process $process + * @return Mage_Index_Model_Resource_Process + */ + public function updateProcessEndDate(Mage_Index_Model_Process $process) + { + $this->_updateProcessData($process->getId(), array('ended_at' => $this->formatDate(time()))); + return $this; + } + + /** + * Whether transaction is already started + * + * @return bool + */ + public function isInTransaction() + { + return $this->_getWriteAdapter()->getTransactionLevel() > 0; + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Process/Collection.php b/app/code/core/Mage/Index/Model/Resource/Process/Collection.php new file mode 100755 index 00000000..ad8fff67 --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Process/Collection.php @@ -0,0 +1,68 @@ + + */ +class Mage_Index_Model_Resource_Process_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Initialize resource + * + */ + protected function _construct() + { + $this->_init('index/process'); + } + + /** + * Add count of unprocessed events to process collection + * + * @return Mage_Index_Model_Resource_Process_Collection + */ + public function addEventsStats() + { + $countsSelect = $this->getConnection() + ->select() + ->from($this->getTable('index/process_event'), array('process_id', 'events' => 'COUNT(*)')) + ->where('status=?', Mage_Index_Model_Process::EVENT_STATUS_NEW) + ->group('process_id'); + $this->getSelect() + ->joinLeft( + array('e' => $countsSelect), + 'e.process_id=main_table.process_id', + array('events' => $this->getConnection()->getCheckSql( + $this->getConnection()->prepareSqlCondition('e.events', array('null' => null)), 0, 'e.events' + )) + ); + return $this; + } +} diff --git a/app/code/core/Mage/Index/Model/Resource/Setup.php b/app/code/core/Mage/Index/Model/Resource/Setup.php new file mode 100755 index 00000000..7b4fd82c --- /dev/null +++ b/app/code/core/Mage/Index/Model/Resource/Setup.php @@ -0,0 +1,86 @@ + + */ +class Mage_Index_Model_Resource_Setup extends Mage_Core_Model_Resource_Setup +{ + /** + * Apply Index module DB updates and sync indexes declaration + * + * @return void + */ + public function applyUpdates() + { + parent::applyUpdates(); + $this->_syncIndexes(); + } + + /** + * Sync indexes declarations in config and in DB + * + * @return Mage_Index_Model_Resource_Setup + */ + protected function _syncIndexes() + { + $connection = $this->getConnection(); + if (!$connection) { + return $this; + } + $indexes = Mage::getConfig()->getNode(Mage_Index_Model_Process::XML_PATH_INDEXER_DATA); + $indexCodes = array(); + foreach ($indexes->children() as $code => $index) { + $indexCodes[] = $code; + } + $table = $this->getTable('index/process'); + $select = $connection->select()->from($table, 'indexer_code'); + $existingIndexes = $connection->fetchCol($select); + $delete = array_diff($existingIndexes, $indexCodes); + $insert = array_diff($indexCodes, $existingIndexes); + + if (!empty($delete)) { + $connection->delete($table, $connection->quoteInto('indexer_code IN (?)', $delete)); + } + if (!empty($insert)) { + $insertData = array(); + foreach ($insert as $code) { + $insertData[] = array( + 'indexer_code' => $code, + 'status' => Mage_Index_Model_Process::STATUS_REQUIRE_REINDEX + ); + } + if (method_exists($connection, 'insertArray')) { + $connection->insertArray($table, array('indexer_code', 'status'), $insertData); + } + } + } +} diff --git a/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php b/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php index e609b544..c01f309a 100644 --- a/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php +++ b/app/code/core/Mage/Index/controllers/Adminhtml/ProcessController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ class Mage_Index_Adminhtml_ProcessController extends Mage_Adminhtml_Controller_Action diff --git a/app/code/core/Mage/Index/etc/adminhtml.xml b/app/code/core/Mage/Index/etc/adminhtml.xml index b88464bf..0eae7375 100644 --- a/app/code/core/Mage/Index/etc/adminhtml.xml +++ b/app/code/core/Mage/Index/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/Index/etc/config.xml b/app/code/core/Mage/Index/etc/config.xml index af5ff41c..ddc1e884 100644 --- a/app/code/core/Mage/Index/etc/config.xml +++ b/app/code/core/Mage/Index/etc/config.xml @@ -21,46 +21,49 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 1.4.0.2 + 1.6.0.0 Mage_Index_Model - index_mysql4 + index_resource - - Mage_Index_Model_Mysql4 + + Mage_Index_Model_Resource + index_mysql4 - index_event
    - index_process
    - index_process_event
    + + index_event
    +
    + + index_process
    +
    + + index_process_event
    +
    -
    +
    Mage_Index - Mage_Index_Model_Mysql4_Setup + Mage_Index_Model_Resource_Setup - + @@ -141,5 +144,14 @@ + + + + + Mage_Index.csv + + + +
    diff --git a/app/code/core/Mage/Index/sql/index_setup/install-1.6.0.0.php b/app/code/core/Mage/Index/sql/index_setup/install-1.6.0.0.php new file mode 100644 index 00000000..f67946d7 --- /dev/null +++ b/app/code/core/Mage/Index/sql/index_setup/install-1.6.0.0.php @@ -0,0 +1,124 @@ +startSetup(); + +/** + * Create table 'index/event' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('index/event')) + ->addColumn('event_id', Varien_Db_Ddl_Table::TYPE_BIGINT, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Event Id') + ->addColumn('type', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + 'nullable' => false, + ), 'Type') + ->addColumn('entity', Varien_Db_Ddl_Table::TYPE_TEXT, 64, array( + 'nullable' => false, + ), 'Entity') + ->addColumn('entity_pk', Varien_Db_Ddl_Table::TYPE_BIGINT, null, array( + ), 'Entity Primary Key') + ->addColumn('created_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + 'nullable' => false, + ), 'Creation Time') + ->addColumn('old_data', Varien_Db_Ddl_Table::TYPE_TEXT, '2M', array( + ), 'Old Data') + ->addColumn('new_data', Varien_Db_Ddl_Table::TYPE_TEXT, '2M', array( + ), 'New Data') + ->addIndex($installer->getIdxName('index/event', array('type', 'entity', 'entity_pk'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('type', 'entity', 'entity_pk'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Index Event'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'index/process' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('index/process')) + ->addColumn('process_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Process Id') + ->addColumn('indexer_code', Varien_Db_Ddl_Table::TYPE_TEXT, 32, array( + 'nullable' => false, + ), 'Indexer Code') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_TEXT, 15, array( + 'nullable' => false, + 'default' => 'pending', + ), 'Status') + ->addColumn('started_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Started At') + ->addColumn('ended_at', Varien_Db_Ddl_Table::TYPE_TIMESTAMP, null, array( + ), 'Ended At') + ->addColumn('mode', Varien_Db_Ddl_Table::TYPE_TEXT, 9, array( + 'nullable' => false, + 'default' => 'real_time', + ), 'Mode') + ->addIndex($installer->getIdxName('index/process', array('indexer_code'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('indexer_code'), array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->setComment('Index Process'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'index/process_event' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('index/process_event')) + ->addColumn('process_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Process Id') + ->addColumn('event_id', Varien_Db_Ddl_Table::TYPE_BIGINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Event Id') + ->addColumn('status', Varien_Db_Ddl_Table::TYPE_TEXT, 7, array( + 'nullable' => false, + 'default' => 'new', + ), 'Status') + ->addIndex($installer->getIdxName('index/process_event', array('event_id')), + array('event_id')) + ->addForeignKey($installer->getFkName('index/process_event', 'event_id', 'index/event', 'event_id'), + 'event_id', $installer->getTable('index/event'), 'event_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('index/process_event', 'process_id', 'index/process', 'process_id'), + 'process_id', $installer->getTable('index/process'), 'process_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Index Process Event'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php b/app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php index 0b38d6bb..e2aea76b 100644 --- a/app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-install-1.4.0.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php index 785a0f00..ae7e33b1 100644 --- a/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.0-1.4.0.1.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php index 086d0f24..c88b0169 100644 --- a/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.4.0.1-1.4.0.2.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Index - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..2938cc37 --- /dev/null +++ b/app/code/core/Mage/Index/sql/index_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,235 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('index_process_event'), + 'FK_INDEX_EVNT_PROCESS' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('index/process_event'), + 'FK_INDEX_PROCESS_EVENT' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('index/event'), + 'IDX_UNIQUE_EVENT' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('index/process'), + 'IDX_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('index/process_event'), + 'FK_INDEX_EVNT_PROCESS' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('index/event') => array( + 'columns' => array( + 'event_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BIGINT, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Event Id' + ), + 'type' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'nullable' => false, + 'comment' => 'Type' + ), + 'entity' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 64, + 'nullable' => false, + 'comment' => 'Entity' + ), + 'entity_pk' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BIGINT, + 'comment' => 'Entity Primary Key' + ), + 'created_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'nullable' => false, + 'comment' => 'Creation Time' + ), + 'old_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2M', + 'comment' => 'Old Data' + ), + 'new_data' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '2M', + 'comment' => 'New Data' + ) + ), + 'comment' => 'Index Event' + ), + $installer->getTable('index/process') => array( + 'columns' => array( + 'process_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Process Id' + ), + 'indexer_code' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 32, + 'nullable' => false, + 'comment' => 'Indexer Code' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 15, + 'nullable' => false, + 'default' => 'pending', + 'comment' => 'Status' + ), + 'started_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Started At' + ), + 'ended_at' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TIMESTAMP, + 'comment' => 'Ended At' + ), + 'mode' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 9, + 'nullable' => false, + 'default' => 'real_time', + 'comment' => 'Mode' + ) + ), + 'comment' => 'Index Process' + ), + $installer->getTable('index/process_event') => array( + 'columns' => array( + 'process_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Process Id' + ), + 'event_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_BIGINT, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Event Id' + ), + 'status' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 7, + 'nullable' => false, + 'default' => 'new', + 'comment' => 'Status' + ) + ), + 'comment' => 'Index Process Event' + ) +); + +$installer->getConnection()->modifyTables($tables); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('index/event'), + $installer->getIdxName( + 'index/event', + array('type', 'entity', 'entity_pk'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('type', 'entity', 'entity_pk'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('index/process'), + $installer->getIdxName( + 'index/process', + array('indexer_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('indexer_code'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('index/process_event'), + $installer->getIdxName('index/process_event', array('event_id')), + array('event_id') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName('index/process_event', 'event_id', 'index/event', 'event_id'), + $installer->getTable('index/process_event'), + 'event_id', + $installer->getTable('index/event'), + 'event_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName('index/process_event', 'process_id', 'index/process', 'process_id'), + $installer->getTable('index/process_event'), + 'process_id', + $installer->getTable('index/process'), + 'process_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Install/Block/Abstract.php b/app/code/core/Mage/Install/Block/Abstract.php index 488ea683..31ae59a4 100644 --- a/app/code/core/Mage/Install/Block/Abstract.php +++ b/app/code/core/Mage/Install/Block/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/Admin.php b/app/code/core/Mage/Install/Block/Admin.php index 44a8e8ab..f9476898 100644 --- a/app/code/core/Mage/Install/Block/Admin.php +++ b/app/code/core/Mage/Install/Block/Admin.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/Begin.php b/app/code/core/Mage/Install/Block/Begin.php index 8d954baf..0580d3e8 100644 --- a/app/code/core/Mage/Install/Block/Begin.php +++ b/app/code/core/Mage/Install/Block/Begin.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/Config.php b/app/code/core/Mage/Install/Block/Config.php index 420db9a5..47b19886 100644 --- a/app/code/core/Mage/Install/Block/Config.php +++ b/app/code/core/Mage/Install/Block/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/Db/Main.php b/app/code/core/Mage/Install/Block/Db/Main.php new file mode 100644 index 00000000..6403e29e --- /dev/null +++ b/app/code/core/Mage/Install/Block/Db/Main.php @@ -0,0 +1,117 @@ + + */ +class Mage_Install_Block_Db_Main extends Mage_Core_Block_Template +{ + /** + * Array of Database blocks keyed by name + * + * @var array + */ + protected $_databases = array(); + + /** + * Adding customized database block template for database model type + * + * @param string $type database type + * @param string $block database block type + * @param string $template + * @return Mage_Install_Block_Db_Main + */ + public function addDatabaseBlock($type, $block, $template) + { + $this->_databases[$type] = array( + 'block' => $block, + 'template' => $template, + 'instance' => null + ); + + return $this; + } + + /** + * Retrieve database block by type + * + * @param string $type database model type + * @return bool | Mage_Core_Block_Template + */ + public function getDatabaseBlock($type) + { + $block = false; + if (isset($this->_databases[$type])) { + if ($this->_databases[$type]['instance']) { + $block = $this->_databases[$type]['instance']; + } else { + $block = $this->getLayout()->createBlock($this->_databases[$type]['block']) + ->setTemplate($this->_databases[$type]['template']) + ->setIdPrefix($type); + $this->_databases[$type]['instance'] = $block; + } + } + return $block; + } + + /** + * Retrieve database blocks + * + * @return array + */ + public function getDatabaseBlocks() + { + $databases = array(); + foreach ($this->_databases as $type => $blockData) { + $databases[] = $this->getDatabaseBlock($type); + } + return $databases; + } + + /** + * Retrieve configuration form data object + * + * @return Varien_Object + */ + public function getFormData() + { + $data = $this->getData('form_data'); + if (is_null($data)) { + $data = Mage::getSingleton('install/session')->getConfigData(true); + if (empty($data)) { + $data = Mage::getModel('install/installer_config')->getFormData(); + } + else { + $data = new Varien_Object($data); + } + $this->setFormData($data); + } + return $data; + } + +} diff --git a/app/code/core/Mage/Install/Block/Db/Type.php b/app/code/core/Mage/Install/Block/Db/Type.php new file mode 100644 index 00000000..8be115d7 --- /dev/null +++ b/app/code/core/Mage/Install/Block/Db/Type.php @@ -0,0 +1,70 @@ + + */ +class Mage_Install_Block_Db_Type extends Mage_Core_Block_Template +{ + /** + * Db title + * + * @var string + */ + protected $_title = null; + + /** + * Return Db title + * + * @return string + */ + public function getTitle() + { + return $this->_title; + } + + /** + * Retrieve configuration form data object + * + * @return Varien_Object + */ + public function getFormData() + { + $data = $this->getData('form_data'); + if (is_null($data)) { + $data = Mage::getSingleton('install/session')->getConfigData(true); + if (empty($data)) { + $data = Mage::getModel('install/installer_config')->getFormData(); + } else { + $data = new Varien_Object($data); + } + $this->setFormData($data); + } + return $data; + } +} diff --git a/app/code/core/Mage/Install/Block/Db/Type/Mysql4.php b/app/code/core/Mage/Install/Block/Db/Type/Mysql4.php new file mode 100644 index 00000000..0690dc55 --- /dev/null +++ b/app/code/core/Mage/Install/Block/Db/Type/Mysql4.php @@ -0,0 +1,40 @@ + + */ +class Mage_Install_Block_Db_Type_Mysql4 extends Mage_Install_Block_Db_Type +{ + /** + * Db title + * + * @var string + */ + protected $_title = 'MySQL'; +} diff --git a/app/code/core/Mage/Install/Block/Download.php b/app/code/core/Mage/Install/Block/Download.php index 729cf442..146ec667 100644 --- a/app/code/core/Mage/Install/Block/Download.php +++ b/app/code/core/Mage/Install/Block/Download.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/End.php b/app/code/core/Mage/Install/Block/End.php index b2dbed7d..5dbbb448 100644 --- a/app/code/core/Mage/Install/Block/End.php +++ b/app/code/core/Mage/Install/Block/End.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -55,8 +55,7 @@ public function getEncryptionKey() public function getIframeSourceUrl() { if (!Mage_AdminNotification_Model_Survey::isSurveyUrlValid() - || Mage::getSingleton('install/installer')->getHideIframe()) - { + || Mage::getSingleton('install/installer')->getHideIframe()) { return null; } return Mage_AdminNotification_Model_Survey::getSurveyUrl(); diff --git a/app/code/core/Mage/Install/Block/Locale.php b/app/code/core/Mage/Install/Block/Locale.php index bd83de9d..afed4d99 100644 --- a/app/code/core/Mage/Install/Block/Locale.php +++ b/app/code/core/Mage/Install/Block/Locale.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Block/State.php b/app/code/core/Mage/Install/Block/State.php index 4508daa5..672af3ff 100644 --- a/app/code/core/Mage/Install/Block/State.php +++ b/app/code/core/Mage/Install/Block/State.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -38,4 +38,34 @@ public function __construct() $this->setTemplate('install/state.phtml'); $this->assign('steps', Mage::getSingleton('install/wizard')->getSteps()); } + + /** + * Get previous downloader steps + * + * @return array + */ + public function getDownloaderSteps() + { + if ($this->isDownloaderInstall()) { + $steps = array( + Mage::helper('install')->__('Welcome'), + Mage::helper('install')->__('Validation'), + Mage::helper('install')->__('Magento Connect Manager Deployment'), + ); + return $steps; + } else { + return array(); + } + } + + /** + * Checks for Magento Connect Manager installation method + * + * @return bool + */ + public function isDownloaderInstall() + { + $session = Mage::app()->getCookie()->get('magento_downloader_session'); + return $session ? true : false; + } } diff --git a/app/code/core/Mage/Install/Controller/Action.php b/app/code/core/Mage/Install/Controller/Action.php index 8e503674..4d924b62 100644 --- a/app/code/core/Mage/Install/Controller/Action.php +++ b/app/code/core/Mage/Install/Controller/Action.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Helper/Data.php b/app/code/core/Mage/Install/Helper/Data.php index 465cf2ef..b70f8a37 100644 --- a/app/code/core/Mage/Install/Helper/Data.php +++ b/app/code/core/Mage/Install/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Model/Config.php b/app/code/core/Mage/Install/Model/Config.php index 6ccc3600..4ac9d76c 100644 --- a/app/code/core/Mage/Install/Model/Config.php +++ b/app/code/core/Mage/Install/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -56,7 +56,7 @@ public function __construct() public function getWizardSteps() { $steps = array(); - foreach ((array)$this->getNode(self::XML_PATH_WIZARD_STEPS) as $stepName=>$step) { + foreach ((array)$this->getNode(self::XML_PATH_WIZARD_STEPS) as $stepName => $step) { $stepObject = new Varien_Object((array)$step); $stepObject->setName($stepName); $steps[] = $stepObject; @@ -101,10 +101,10 @@ public function getExtensionsForCheck() $res = array(); $items = (array) $this->getNode(self::XML_PATH_CHECK_EXTENSIONS); - foreach ($items as $name=>$value) { + foreach ($items as $name => $value) { if (!empty($value)) { $res[$name] = array(); - foreach ($value as $subname=>$subvalue) { + foreach ($value as $subname => $subvalue) { $res[$name][] = $subname; } } diff --git a/app/code/core/Mage/Install/Model/Installer.php b/app/code/core/Mage/Install/Model/Installer.php index c7de515b..f29fbec0 100644 --- a/app/code/core/Mage/Install/Model/Installer.php +++ b/app/code/core/Mage/Install/Model/Installer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -142,7 +142,9 @@ public function getServerCheckStatus() public function installConfig($data) { $data['db_active'] = true; - Mage::getSingleton('install/installer_db')->checkDatabase($data); + + $data = Mage::getSingleton('install/installer_db')->checkDbConnectionData($data); + Mage::getSingleton('install/installer_config') ->setConfigData($data) ->install(); @@ -168,6 +170,13 @@ public function installDb() $setupModel->setConfigData(Mage_Core_Model_Store::XML_PATH_USE_REWRITES, 1); } + if (!empty($data['enable_charts'])) { + $setupModel->setConfigData(Mage_Adminhtml_Block_Dashboard::XML_PATH_ENABLE_CHARTS, 1); + } else { + $setupModel->setConfigData(Mage_Adminhtml_Block_Dashboard::XML_PATH_ENABLE_CHARTS, 0); + } + + $unsecureBaseUrl = Mage::getBaseUrl('web'); if (!empty($data['unsecure_base_url'])) { $unsecureBaseUrl = $data['unsecure_base_url']; @@ -239,7 +248,7 @@ public function createAdministrator($data) { $user = Mage::getModel('admin/user') ->load('admin', 'username'); - if ($user && $user->getPassword()=='4297f44b13955235245b2497399d7a93') { + if ($user && $user->getPassword() == '4297f44b13955235245b2497399d7a93') { $user->delete(); } @@ -313,7 +322,7 @@ public function finish() Mage::app()->cleanCache(); $cacheData = array(); - foreach (Mage::helper('core')->getCacheTypes() as $type=>$label) { + foreach (Mage::helper('core')->getCacheTypes() as $type => $label) { $cacheData[$type] = 1; } Mage::app()->saveUseCache($cacheData); diff --git a/app/code/core/Mage/Install/Model/Installer/Abstract.php b/app/code/core/Mage/Install/Model/Installer/Abstract.php index 1e384f5e..ecad16ac 100644 --- a/app/code/core/Mage/Install/Model/Installer/Abstract.php +++ b/app/code/core/Mage/Install/Model/Installer/Abstract.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -30,7 +30,7 @@ * * @category Mage * @package Mage_Install - * @author Magento Core Team + * @author Magento Core Team */ class Mage_Install_Model_Installer_Abstract { diff --git a/app/code/core/Mage/Install/Model/Installer/Config.php b/app/code/core/Mage/Install/Model/Installer/Config.php index 90ccbbf0..441815a2 100644 --- a/app/code/core/Mage/Install/Model/Installer/Config.php +++ b/app/code/core/Mage/Install/Model/Installer/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -46,7 +46,7 @@ class Mage_Install_Model_Installer_Config extends Mage_Install_Model_Installer_A public function __construct() { - $this->_localConfigFile = Mage::getBaseDir('etc').DS.'local.xml'; + $this->_localConfigFile = Mage::getBaseDir('etc') . DS . 'local.xml'; } public function setConfigData($data) @@ -72,13 +72,19 @@ public function install() } if (isset($data['unsecure_base_url'])) { - $data['unsecure_base_url'] .= substr($data['unsecure_base_url'],-1) != '/' ? '/' : ''; + $data['unsecure_base_url'] .= substr($data['unsecure_base_url'], -1) != '/' ? '/' : ''; + if (strpos($data['unsecure_base_url'], 'http') !== 0) { + $data['unsecure_base_url'] = 'http://' . $data['unsecure_base_url']; + } if (!$this->_getInstaller()->getDataModel()->getSkipBaseUrlValidation()) { $this->_checkUrl($data['unsecure_base_url']); } } if (isset($data['secure_base_url'])) { - $data['secure_base_url'] .= substr($data['secure_base_url'],-1) != '/' ? '/' : ''; + $data['secure_base_url'] .= substr($data['secure_base_url'], -1) != '/' ? '/' : ''; + if (strpos($data['secure_base_url'], 'http') !== 0) { + $data['secure_base_url'] = 'https://' . $data['secure_base_url']; + } if (!empty($data['use_secure']) && !$this->_getInstaller()->getDataModel()->getSkipUrlValidation()) { @@ -94,9 +100,9 @@ public function install() $this->_getInstaller()->getDataModel()->setConfigData($data); - $template = file_get_contents(Mage::getBaseDir('etc').DS.'local.xml.template'); - foreach ($data as $index=>$value) { - $template = str_replace('{{'.$index.'}}', '', $template); + $template = file_get_contents(Mage::getBaseDir('etc') . DS . 'local.xml.template'); + foreach ($data as $index => $value) { + $template = str_replace('{{' . $index . '}}', '', $template); } file_put_contents($this->_localConfigFile, $template); chmod($this->_localConfigFile, 0777); @@ -107,29 +113,35 @@ public function getFormData() $uri = Zend_Uri::factory(Mage::getBaseUrl('web')); $baseUrl = $uri->getUri(); - if ($uri->getScheme()!=='https') { + if ($uri->getScheme() !== 'https') { $uri->setPort(null); $baseSecureUrl = str_replace('http://', 'https://', $uri->getUri()); } else { $baseSecureUrl = $uri->getUri(); } + $connectDefault = Mage::getConfig() + ->getResourceConnectionConfig(Mage_Core_Model_Resource::DEFAULT_SETUP_RESOURCE); + $data = Mage::getModel('varien/object') - ->setDbHost('localhost') - ->setDbName('magento') - ->setDbUser('root') + ->setDbHost($connectDefault->host) + ->setDbName($connectDefault->dbname) + ->setDbUser($connectDefault->username) + ->setDbModel($connectDefault->model) ->setDbPass('') ->setSecureBaseUrl($baseSecureUrl) ->setUnsecureBaseUrl($baseUrl) ->setAdminFrontname('admin') + ->setEnableCharts('1') ; return $data; } protected function _checkHostsInfo($data) { - $url = $data['protocol'] . '://' . $data['host'] . ':' . $data['port'] . $data['base_path']; - $surl= $data['secure_protocol'] . '://' . $data['secure_host'] . ':' . $data['secure_port'] . $data['secure_base_path']; + $url = $data['protocol'] . '://' . $data['host'] . ':' . $data['port'] . $data['base_path']; + $surl = $data['secure_protocol'] . '://' . $data['secure_host'] . ':' . $data['secure_port'] + . $data['secure_base_path']; $this->_checkUrl($url); $this->_checkUrl($surl, true); @@ -137,23 +149,25 @@ protected function _checkHostsInfo($data) return $this; } - protected function _checkUrl($url, $secure=false) + protected function _checkUrl($url, $secure = false) { $prefix = $secure ? 'install/wizard/checkSecureHost/' : 'install/wizard/checkHost/'; - $client = new Varien_Http_Client($url.'index.php/'.$prefix); try { + $client = new Varien_Http_Client($url . 'index.php/' . $prefix); $response = $client->request('GET'); /* @var $responce Zend_Http_Response */ $body = $response->getBody(); } catch (Exception $e){ - $this->_getInstaller()->getDataModel()->addError(Mage::helper('install')->__('The URL "%s" is not accessible.', $url)); + $this->_getInstaller()->getDataModel() + ->addError(Mage::helper('install')->__('The URL "%s" is not accessible.', $url)); throw $e; } if ($body != Mage_Install_Model_Installer::INSTALLER_HOST_RESPONSE) { - $this->_getInstaller()->getDataModel()->addError(Mage::helper('install')->__('The URL "%s" is invalid.', $url)); - Mage::throwException(Mage::helper('install')->__('This URL is invalid.')); + $this->_getInstaller()->getDataModel() + ->addError(Mage::helper('install')->__('The URL "%s" is invalid.', $url)); + Mage::throwException(Mage::helper('install')->__('Response from server isn\'t valid.')); } return $this; } @@ -171,7 +185,7 @@ public function replaceTmpInstallDate($date = null) public function replaceTmpEncryptKey($key = null) { if (!$key) { - $key = md5(time()); + $key = md5(Mage::helper('core')->getRandomString(10)); } $localXml = file_get_contents($this->_localConfigFile); $localXml = str_replace(self::TMP_ENCRYPT_KEY_VALUE, $key, $localXml); diff --git a/app/code/core/Mage/Install/Model/Installer/Console.php b/app/code/core/Mage/Install/Model/Installer/Console.php index 79adfa63..c1d13956 100644 --- a/app/code/core/Mage/Install/Model/Installer/Console.php +++ b/app/code/core/Mage/Install/Model/Installer/Console.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -75,6 +75,7 @@ protected function _getOptions() 'locale' => array('required' => true, 'comment' => ''), 'timezone' => array('required' => true, 'comment' => ''), 'default_currency' => array('required' => true, 'comment' => ''), + 'db_model' => array('comment' => ''), 'db_host' => array('required' => true, 'comment' => ''), 'db_name' => array('required' => true, 'comment' => ''), 'db_user' => array('required' => true, 'comment' => ''), @@ -94,6 +95,7 @@ protected function _getOptions() 'encryption_key' => array('comment' => ''), 'session_save' => array('comment' => ''), 'admin_frontname' => array('comment' => ''), + 'enable_charts' => array('comment' => ''), ); } return $this->_options; @@ -142,7 +144,7 @@ public function setArgs($args = null) */ foreach ($this->_getOptions() as $name => $option) { if (isset($option['required']) && $option['required'] && !isset($args[$name])) { - $error = 'ERROR: ' . 'You should provide the value for --' . $name .' parameter'; + $error = 'ERROR: ' . 'You should provide the value for --' . $name . ' parameter'; if (!empty($option['comment'])) { $error .= ': ' . $option['comment']; } @@ -285,6 +287,7 @@ protected function _prepareData() * Database and web config */ $this->_getDataModel()->setConfigData(array( + 'db_model' => $this->_args['db_model'], 'db_host' => $this->_args['db_host'], 'db_name' => $this->_args['db_name'], 'db_user' => $this->_args['db_user'], @@ -298,6 +301,7 @@ protected function _prepareData() 'session_save' => $this->_checkSessionSave($this->_args['session_save']), 'admin_frontname' => $this->_checkAdminFrontname($this->_args['admin_frontname']), 'skip_url_validation' => $this->_checkFlag($this->_args['skip_url_validation']), + 'enable_charts' => $this->_checkFlag($this->_args['enable_charts']), )); /** @@ -308,7 +312,7 @@ protected function _prepareData() 'lastname' => $this->_args['admin_lastname'], 'email' => $this->_args['admin_email'], 'username' => $this->_args['admin_username'], - 'new_password' => $this->_args['admin_password'], + 'new_password' => $this->_args['admin_password'], )); return $this; @@ -351,7 +355,7 @@ public function install() /** * Install configuration */ - $installer->installConfig($this->_getDataModel()->getConfigData()); // TODO fix wizard and simplify this everythere + $installer->installConfig($this->_getDataModel()->getConfigData()); // TODO fix wizard and simplify this everywhere if ($this->hasErrors()) { return false; @@ -373,6 +377,9 @@ public function install() return false; } + // apply data updates + Mage_Core_Model_Resource_Setup::applyAllDataUpdates(); + /** * Validate entered data for administrator user */ diff --git a/app/code/core/Mage/Install/Model/Installer/Data.php b/app/code/core/Mage/Install/Model/Installer/Data.php index 332782c4..e01190ac 100644 --- a/app/code/core/Mage/Install/Model/Installer/Data.php +++ b/app/code/core/Mage/Install/Model/Installer/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Model/Installer/Db.php b/app/code/core/Mage/Install/Model/Installer/Db.php index 94c9ff50..630a6078 100644 --- a/app/code/core/Mage/Install/Model/Installer/Db.php +++ b/app/code/core/Mage/Install/Model/Installer/Db.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,61 +33,164 @@ */ class Mage_Install_Model_Installer_Db extends Mage_Install_Model_Installer_Abstract { + /** + * @var database resource + */ + protected $_dbResource; + /** * Check database connection - * - * $data = array( - * [db_host] - * [db_name] - * [db_user] - * [db_pass] - * ) + * and return checked connection data * * @param array $data + * @return array */ - public function checkDatabase($data) + public function checkDbConnectionData($data) { - $config = array( - 'host' => $data['db_host'], - 'username' => $data['db_user'], - 'password' => $data['db_pass'], - 'dbname' => $data['db_name'] - ); + $data = $this->_getCheckedData($data); try { - $connection = Mage::getSingleton('core/resource')->createConnection('install', $this->_getConnenctionType(), $config); - $variables = $connection->fetchPairs("SHOW VARIABLES"); + $dbModel = ($data['db_model']); - $version = isset($variables['version']) ? $variables['version'] : 'undefined'; - $match = array(); - if (preg_match("#^([0-9\.]+)#", $version, $match)) { - $version = $match[0]; + if (!$resource = $this->_getDbResource($dbModel)) { + Mage::throwException(Mage::helper('install')->__('No resource for %s DB model.', $dbModel)); } - $requiredVersion = (string)Mage::getSingleton('install/config')->getNode('check/mysql/version'); - // check MySQL Server version + $resource->setConfig($data); + + // check required extensions + $absenteeExtensions = array(); + $extensions = $resource->getRequiredExtensions(); + foreach ($extensions as $extName) { + if (!extension_loaded($extName)) { + $absenteeExtensions[] = $extName; + } + } + if (!empty($absenteeExtensions)) { + Mage::throwException( + Mage::helper('install')->__('PHP Extensions "%s" must be loaded.', implode(',', $absenteeExtensions)) + ); + } + + $version = $resource->getVersion(); + $requiredVersion = (string) Mage::getConfig() + ->getNode(sprintf('install/databases/%s/min_version', $dbModel)); + + // check DB server version if (version_compare($version, $requiredVersion) == -1) { - Mage::throwException(Mage::helper('install')->__('The database server version does not match system requirements (required: %s, actual: %s).', $requiredVersion, $version)); + Mage::throwException( + Mage::helper('install')->__('The database server version doesn\'t match system requirements (required: %s, actual: %s).', $requiredVersion, $version) + ); } // check InnoDB support - if (!isset($variables['have_innodb']) || $variables['have_innodb'] != 'YES') { - Mage::throwException(Mage::helper('install')->__('Database server does not support the InnoDB storage engine.')); + if (!$resource->supportEngine()) { + Mage::throwException( + Mage::helper('install')->__('Database server does not support the InnoDB storage engine.') + ); } + + // TODO: check user roles + } + catch (Mage_Core_Exception $e) { + Mage::logException($e); + Mage::throwException(Mage::helper('install')->__($e->getMessage())); } - catch (Exception $e){ - $this->_getInstaller()->getDataModel()->addError($e->getMessage()); + catch (Exception $e) { + Mage::logException($e); Mage::throwException(Mage::helper('install')->__('Database connection error.')); } + + return $data; + } + + /** + * Check database connection data + * + * @param array $data + * @return array + */ + protected function _getCheckedData($data) + { + if (!isset($data['db_name']) || empty($data['db_name'])) { + Mage::throwException(Mage::helper('install')->__('Database Name cannot be empty.')); + } + //make all table prefix to lower letter + if ($data['db_prefix'] != '') { + $data['db_prefix'] = strtolower($data['db_prefix']); + } + //check table prefix + if ($data['db_prefix'] != '') { + if (!preg_match('/^[a-z]+[a-z0-9_]*$/', $data['db_prefix'])) { + Mage::throwException( + Mage::helper('install')->__('The table prefix should contain only letters (a-z), numbers (0-9) or underscores (_), the first character should be a letter.') + ); + } + } + //set default db model + if (!isset($data['db_model']) || empty($data['db_model'])) { + $data['db_model'] = Mage::getConfig() + ->getResourceConnectionConfig(Mage_Core_Model_Resource::DEFAULT_SETUP_RESOURCE)->model; + } + //set db type according the db model + if (!isset($data['db_type'])) { + $data['db_type'] = (string) Mage::getConfig() + ->getNode(sprintf('install/databases/%s/type', $data['db_model'])); + } + + $dbResource = $this->_getDbResource($data['db_model']); + $data['db_pdo_type'] = $dbResource->getPdoType(); + + if (!isset($data['db_init_statemants'])) { + $data['db_init_statemants'] = (string) Mage::getConfig() + ->getNode(sprintf('install/databases/%s/initStatements', $data['db_model'])); + } + + return $data; + } + + /** + * Retrieve the database resource + * + * @param string $model database type + * @return Mage_Install_Model_Installer_Db_Abstract + */ + protected function _getDbResource($model) + { + if (!isset($this->_dbResource)) { + $resource = Mage::getSingleton(sprintf('install/installer_db_%s', $model)); + if (!$resource) { + Mage::throwException( + Mage::helper('install')->__('Installer does not exist for %s database type', $model) + ); + } + $this->_dbResource = $resource; + } + return $this->_dbResource; } /** * Retrieve Connection Type * * @return string + * + * @deprecated since 1.5.0.0 */ protected function _getConnenctionType() { return (string) Mage::getConfig()->getNode('global/resources/default_setup/connection/type'); } + + + /** + * Check database connection + * + * @param array $data + * + * @deprecated since 1.5.0.0 + */ + public function checkDatabase($data) + { + $this->checkDbConnectionData($data); + } } diff --git a/app/code/core/Mage/Install/Model/Installer/Db/Abstract.php b/app/code/core/Mage/Install/Model/Installer/Db/Abstract.php new file mode 100644 index 00000000..ff1abf4f --- /dev/null +++ b/app/code/core/Mage/Install/Model/Installer/Db/Abstract.php @@ -0,0 +1,158 @@ + + */ +abstract class Mage_Install_Model_Installer_Db_Abstract +{ + /** + * Adapter instance + * + * @var Varien_Db_Adapter_Interface + */ + protected $_connection; + + /** + * Connection configuration + * + * @var array + */ + protected $_connectionData; + + /** + * Connection configuration + * + * @var array + */ + protected $_configData; + + + /** + * Return the name of DB model from config + * + * @return string + */ + public function getModel() + { + return $this->_configData['db_model']; + } + + + /** + * Return the DB type from config + * + * @return string + */ + public function getType() + { + return $this->_configData['db_type']; + } + + /** + * Set configuration data + * + * @param array $config the connection configuration + */ + public function setConfig($config) + { + $this->_configData = $config; + } + + /** + * Retrieve connection data from config + * + * @return array + */ + public function getConnectionData() + { + if (!$this->_connectionData) { + $connectionData = array( + 'host' => $this->_configData['db_host'], + 'username' => $this->_configData['db_user'], + 'password' => $this->_configData['db_pass'], + 'dbname' => $this->_configData['db_name'], + 'pdoType' => $this->getPdoType() + ); + $this->_connectionData = $connectionData; + } + return $this->_connectionData; + } + + /** + * Check InnoDB support + * + * @return bool + */ + public function supportEngine() + { + return true; + } + + /** + * Create new connection with custom config + * + * @return Varien_Db_Adapter_Interface + */ + protected function _getConnection() + { + if (!isset($this->_connection)) { + $resource = Mage::getSingleton('core/resource'); + $connection = $resource->createConnection('install', $this->getType(), $this->getConnectionData()); + $this->_connection = $connection; + } + return $this->_connection; + } + + /** + * Return pdo type + * + * @return null + */ + public function getPdoType() + { + return null; + } + + /** + * Retrieve required PHP extension list for database + * + * @return array + */ + public function getRequiredExtensions() + { + $extensions = array(); + $configExt = (array)Mage::getConfig()->getNode(sprintf('install/databases/%s/extensions', $this->getModel())); + foreach ($configExt as $name=>$value) { + $extensions[] = $name; + } + return $extensions; + } +} diff --git a/app/code/core/Mage/Install/Model/Installer/Db/Mysql4.php b/app/code/core/Mage/Install/Model/Installer/Db/Mysql4.php new file mode 100644 index 00000000..bc482b5d --- /dev/null +++ b/app/code/core/Mage/Install/Model/Installer/Db/Mysql4.php @@ -0,0 +1,65 @@ + + */ +class Mage_Install_Model_Installer_Db_Mysql4 extends Mage_Install_Model_Installer_Db_Abstract +{ + /** + * Retrieve DB server version + * + * @return string (string version number | 'undefined') + */ + public function getVersion() + { + $version = $this->_getConnection() + ->fetchOne('SELECT VERSION()'); + $version = $version ? $version : 'undefined'; + $match = array(); + if (preg_match("#^([0-9\.]+)#", $version, $match)) { + $version = $match[0]; + } + return $version; + } + + /** + * Check InnoDB support + * + * @return bool + */ + public function supportEngine() + { + $variables = $this->_getConnection() + ->fetchPairs('SHOW VARIABLES'); + return (!isset($variables['have_innodb']) || $variables['have_innodb'] != 'YES') ? false : true; + } +} diff --git a/app/code/core/Mage/Install/Model/Installer/Env.php b/app/code/core/Mage/Install/Model/Installer/Env.php index 76cc1e1e..8030e2c5 100644 --- a/app/code/core/Mage/Install/Model/Installer/Env.php +++ b/app/code/core/Mage/Install/Model/Installer/Env.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -75,10 +75,10 @@ protected function _checkExtension($extension) return false; } } - elseif(!extension_loaded($extension)) { - Mage::getSingleton('install/session')->addError( - Mage::helper('install')->__('PHP extension "%s" must be loaded.', $extension) - ); + elseif (!extension_loaded($extension)) { + Mage::getSingleton('install/session')->addError( + Mage::helper('install')->__('PHP extension "%s" must be loaded.', $extension) + ); return false; } else { diff --git a/app/code/core/Mage/Install/Model/Installer/Filesystem.php b/app/code/core/Mage/Install/Model/Installer/Filesystem.php index 2d8c0f37..de522604 100644 --- a/app/code/core/Mage/Install/Model/Installer/Filesystem.php +++ b/app/code/core/Mage/Install/Model/Installer/Filesystem.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -85,7 +85,7 @@ protected function _checkFilesystem() protected function _checkPath($path, $recursive, $existence, $mode) { $res = true; - $fullPath = dirname(Mage::getRoot()).$path; + $fullPath = dirname(Mage::getRoot()) . $path; if ($mode == self::MODE_WRITE) { $setError = false; if ($existence) { @@ -110,7 +110,7 @@ protected function _checkPath($path, $recursive, $existence, $mode) if ($recursive && is_dir($fullPath)) { foreach (new DirectoryIterator($fullPath) as $file) { if (!$file->isDot() && $file->getFilename() != '.svn' && $file->getFilename() != '.htaccess') { - $res = $res && $this->_checkPath($path.DS.$file->getFilename(), $recursive, $existence, $mode); + $res = $res && $this->_checkPath($path . DS . $file->getFilename(), $recursive, $existence, $mode); } } } diff --git a/app/code/core/Mage/Install/Model/Installer/Pear.php b/app/code/core/Mage/Install/Model/Installer/Pear.php index 04f31949..89e3b798 100644 --- a/app/code/core/Mage/Install/Model/Installer/Pear.php +++ b/app/code/core/Mage/Install/Model/Installer/Pear.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Model/Observer.php b/app/code/core/Mage/Install/Model/Observer.php index caca2cf1..3fc54b44 100644 --- a/app/code/core/Mage/Install/Model/Observer.php +++ b/app/code/core/Mage/Install/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Model/Session.php b/app/code/core/Mage/Install/Model/Session.php index a0fcfa2b..d96f67e9 100644 --- a/app/code/core/Mage/Install/Model/Session.php +++ b/app/code/core/Mage/Install/Model/Session.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/Model/Wizard.php b/app/code/core/Mage/Install/Model/Wizard.php index 91f7f2c4..eabffd9b 100644 --- a/app/code/core/Mage/Install/Model/Wizard.php +++ b/app/code/core/Mage/Install/Model/Wizard.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -49,20 +49,20 @@ public function __construct() $this->_getUrl($this->_steps[$index]->getController(), $this->_steps[$index]->getAction()) ); - if (isset($this->_steps[$index+1])) { + if (isset($this->_steps[$index + 1])) { $this->_steps[$index]->setNextUrl( - $this->_getUrl($this->_steps[$index+1]->getController(), $this->_steps[$index+1]->getAction()) + $this->_getUrl($this->_steps[$index + 1]->getController(), $this->_steps[$index + 1]->getAction()) ); $this->_steps[$index]->setNextUrlPath( - $this->_getUrlPath($this->_steps[$index+1]->getController(), $this->_steps[$index+1]->getAction()) + $this->_getUrlPath($this->_steps[$index + 1]->getController(), $this->_steps[$index + 1]->getAction()) ); } - if (isset($this->_steps[$index-1])) { + if (isset($this->_steps[$index - 1])) { $this->_steps[$index]->setPrevUrl( - $this->_getUrl($this->_steps[$index-1]->getController(), $this->_steps[$index-1]->getAction()) + $this->_getUrl($this->_steps[$index - 1]->getController(), $this->_steps[$index - 1]->getAction()) ); $this->_steps[$index]->setPrevUrlPath( - $this->_getUrlPath($this->_steps[$index-1]->getController(), $this->_steps[$index-1]->getAction()) + $this->_getUrlPath($this->_steps[$index - 1]->getController(), $this->_steps[$index - 1]->getAction()) ); } } @@ -77,7 +77,8 @@ public function __construct() public function getStepByRequest(Zend_Controller_Request_Abstract $request) { foreach ($this->_steps as $step) { - if ($step->getController() == $request->getControllerName() && $step->getAction() == $request->getActionName()) { + if ($step->getController() == $request->getControllerName() + && $step->getAction() == $request->getActionName()) { return $step; } } @@ -124,6 +125,6 @@ protected function _getUrl($controller, $action) */ protected function _getUrlPath($controller, $action) { - return 'install/'.$controller.'/'.$action; + return 'install/' . $controller . '/' . $action; } } diff --git a/app/code/core/Mage/Install/controllers/IndexController.php b/app/code/core/Mage/Install/controllers/IndexController.php index 36ea1819..e4c04a0d 100644 --- a/app/code/core/Mage/Install/controllers/IndexController.php +++ b/app/code/core/Mage/Install/controllers/IndexController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Install/controllers/WizardController.php b/app/code/core/Mage/Install/controllers/WizardController.php index ec502d34..2ca8030b 100644 --- a/app/code/core/Mage/Install/controllers/WizardController.php +++ b/app/code/core/Mage/Install/controllers/WizardController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -234,11 +234,11 @@ public function downloadAutoAction() public function installAction() { $pear = Varien_Pear::getInstance(); - $params = array('comment'=>Mage::helper('install')->__("Downloading and installing Magento, please wait...")."\r\n\r\n"); + $params = array('comment'=>Mage::helper('install')->__("Downloading and installing Magento, please wait...") . "\r\n\r\n"); if ($this->getRequest()->getParam('do')) { if ($state = $this->getRequest()->getParam('state', 'beta')) { $result = $pear->runHtmlConsole(array( - 'comment' => Mage::helper('install')->__("Setting preferred state to: %s", $state)."\r\n\r\n", + 'comment' => Mage::helper('install')->__("Setting preferred state to: %s", $state) . "\r\n\r\n", 'command' => 'config-set', 'params' => array('preferred_state', $state) )); @@ -309,23 +309,18 @@ public function configPostAction() $this->_checkIfInstalled(); $step = $this->_getWizard()->getStepByName('config'); - if ($data = $this->getRequest()->getPost('config')) { - //make all table prefix to lower letter - if ($data['db_prefix'] !='') { - $data['db_prefix'] = strtolower($data['db_prefix']); - } + $config = $this->getRequest()->getPost('config'); + $connectionConfig = $this->getRequest()->getPost('connection'); + + if ($config && $connectionConfig && isset($connectionConfig[$config['db_model']])) { + + $data = array_merge($config, $connectionConfig[$config['db_model']]); Mage::getSingleton('install/session') ->setConfigData($data) ->setSkipUrlValidation($this->getRequest()->getPost('skip_url_validation')) ->setSkipBaseUrlValidation($this->getRequest()->getPost('skip_base_url_validation')); try { - if($data['db_prefix']!='') { - if(!preg_match('/^[a-z]+[a-z0-9_]*$/',$data['db_prefix'])) { - Mage::throwException( - Mage::helper('install')->__('The table prefix should contain only letters (a-z), numbers (0-9) or underscores (_), the first character should be a letter.')); - } - } $this->_getInstaller()->installConfig($data); $this->_redirect('*/*/installDb'); return $this; @@ -363,7 +358,7 @@ public function installDbAction() } /** - * Install admininstrator account + * Install administrator account */ public function administratorAction() { diff --git a/app/code/core/Mage/Install/etc/config.xml b/app/code/core/Mage/Install/etc/config.xml index 70e26ba9..5606b3e1 100644 --- a/app/code/core/Mage/Install/etc/config.xml +++ b/app/code/core/Mage/Install/etc/config.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -35,14 +35,17 @@ Mage_Install_Model - install_mysql4 + install_resource - - Mage_Install_Model_Mysql4 - + + Mage_Install_Model_Resource + install_mysql4 + - Mage_Install_Block + + Mage_Install_Block + @@ -60,9 +63,21 @@ + + + pdo_mysql + SET NAMES utf8 + 4.1.20 + + + + + -
    main.xml
    +
    + main.xml +
    diff --git a/app/code/core/Mage/Install/etc/install.xml b/app/code/core/Mage/Install/etc/install.xml index f3c73e88..fdc42f38 100644 --- a/app/code/core/Mage/Install/etc/install.xml +++ b/app/code/core/Mage/Install/etc/install.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Install - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -80,7 +80,6 @@ - @@ -89,8 +88,5 @@ - - 4.1.20 -
    diff --git a/app/code/core/Mage/Media/Helper/Data.php b/app/code/core/Mage/Media/Helper/Data.php index 536e73f1..7680cc3f 100644 --- a/app/code/core/Mage/Media/Helper/Data.php +++ b/app/code/core/Mage/Media/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Media - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Media/Model/File/Image.php b/app/code/core/Mage/Media/Model/File/Image.php index 8e8ffb49..1f75315a 100644 --- a/app/code/core/Mage/Media/Model/File/Image.php +++ b/app/code/core/Mage/Media/Model/File/Image.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Media - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Media/Model/Image.php b/app/code/core/Mage/Media/Model/Image.php index a6093ef7..0095dc6e 100644 --- a/app/code/core/Mage/Media/Model/Image.php +++ b/app/code/core/Mage/Media/Model/Image.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Media - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Media/Model/Image/Config/Interface.php b/app/code/core/Mage/Media/Model/Image/Config/Interface.php index 7138f741..2efb2e44 100644 --- a/app/code/core/Mage/Media/Model/Image/Config/Interface.php +++ b/app/code/core/Mage/Media/Model/Image/Config/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Media - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Media/etc/config.xml b/app/code/core/Mage/Media/etc/config.xml index 68d04f8e..b619fd95 100644 --- a/app/code/core/Mage/Media/etc/config.xml +++ b/app/code/core/Mage/Media/etc/config.xml @@ -21,25 +21,26 @@ * * @category Mage * @package Mage_Media - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.0 + 1.6.0.0 Mage_Media_Model - media_file + media_resource - + Mage_Media_Model_File - + media_file + @@ -72,5 +73,5 @@ - + diff --git a/app/code/core/Mage/Page/Block/Html.php b/app/code/core/Mage/Page/Block/Html.php index 9abc0953..e4443408 100644 --- a/app/code/core/Mage/Page/Block/Html.php +++ b/app/code/core/Mage/Page/Block/Html.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Html/Breadcrumbs.php b/app/code/core/Mage/Page/Block/Html/Breadcrumbs.php index 181a136d..440fd52f 100644 --- a/app/code/core/Mage/Page/Block/Html/Breadcrumbs.php +++ b/app/code/core/Mage/Page/Block/Html/Breadcrumbs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Html/Footer.php b/app/code/core/Mage/Page/Block/Html/Footer.php index b1937c4b..985b0276 100644 --- a/app/code/core/Mage/Page/Block/Html/Footer.php +++ b/app/code/core/Mage/Page/Block/Html/Footer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -56,7 +56,8 @@ public function getCacheKeyInfo() Mage::app()->getStore()->getId(), (int)Mage::app()->getStore()->isCurrentlySecure(), Mage::getDesign()->getPackageName(), - Mage::getDesign()->getTheme('template') + Mage::getDesign()->getTheme('template'), + Mage::getSingleton('customer/session')->isLoggedIn() ); } diff --git a/app/code/core/Mage/Page/Block/Html/Head.php b/app/code/core/Mage/Page/Block/Html/Head.php index c740aa1d..b081413c 100644 --- a/app/code/core/Mage/Page/Block/Html/Head.php +++ b/app/code/core/Mage/Page/Block/Html/Head.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -466,4 +466,50 @@ public function getIncludes() } return $this->_data['includes']; } + + /** + * Getter for path to Favicon + * + * @return string + */ + public function getFaviconFile() + { + if (empty($this->_data['favicon_file'])) { + $this->_data['favicon_file'] = $this->_getFaviconFile(); + } + return $this->_data['favicon_file']; + } + + /** + * Retrieve path to Favicon + * + * @return string + */ + protected function _getFaviconFile() + { + $folderName = Mage_Adminhtml_Model_System_Config_Backend_Image_Favicon::UPLOAD_DIR; + $storeConfig = Mage::getStoreConfig('design/head/shortcut_icon'); + $faviconFile = Mage::getBaseUrl('media') . $folderName . '/' . $storeConfig; + $absolutePath = Mage::getBaseDir('media') . '/' . $folderName . '/' . $storeConfig; + + if(!is_null($storeConfig) && $this->_isFile($absolutePath)) { + $url = $faviconFile; + } else { + $url = $this->getSkinUrl('favicon.ico'); + } + return $url; + } + + /** + * If DB file storage is on - find there, otherwise - just file_exists + * + * @param string $filename + * @return bool + */ + protected function _isFile($filename) { + if (Mage::helper('core/file_storage_database')->checkDbUsage() && !is_file($filename)) { + Mage::helper('core/file_storage_database')->saveFileToFilesystem($filename); + } + return is_file($filename); + } } diff --git a/app/code/core/Mage/Page/Block/Html/Header.php b/app/code/core/Mage/Page/Block/Html/Header.php index e4ce1eda..654b1a0f 100644 --- a/app/code/core/Mage/Page/Block/Html/Header.php +++ b/app/code/core/Mage/Page/Block/Html/Header.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -75,7 +75,7 @@ public function getWelcome() { if (empty($this->_data['welcome'])) { if (Mage::isInstalled() && Mage::getSingleton('customer/session')->isLoggedIn()) { - $this->_data['welcome'] = $this->__('Welcome, %s!', $this->htmlEscape(Mage::getSingleton('customer/session')->getCustomer()->getName())); + $this->_data['welcome'] = $this->__('Welcome, %s!', $this->escapeHtml(Mage::getSingleton('customer/session')->getCustomer()->getName())); } else { $this->_data['welcome'] = Mage::getStoreConfig('design/header/welcome'); } @@ -83,5 +83,4 @@ public function getWelcome() return $this->_data['welcome']; } - } diff --git a/app/code/core/Mage/Page/Block/Html/Notices.php b/app/code/core/Mage/Page/Block/Html/Notices.php index f3a8791f..0ee78c2d 100644 --- a/app/code/core/Mage/Page/Block/Html/Notices.php +++ b/app/code/core/Mage/Page/Block/Html/Notices.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -33,7 +33,6 @@ */ class Mage_Page_Block_Html_Notices extends Mage_Core_Block_Template { - /** * Check if noscript notice should be displayed * @@ -54,4 +53,13 @@ public function displayDemoNotice() return Mage::getStoreConfig('design/head/demonotice'); } + /** + * Get Link to cookie restriction privacy policy page + * + * @return string + */ + public function getPrivacyPolicyLink() + { + return Mage::getUrl('privacy-policy-cookie-restriction-mode'); + } } diff --git a/app/code/core/Mage/Page/Block/Html/Pager.php b/app/code/core/Mage/Page/Block/Html/Pager.php index 096d147e..20636064 100644 --- a/app/code/core/Mage/Page/Block/Html/Pager.php +++ b/app/code/core/Mage/Page/Block/Html/Pager.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Html/Toplinks.php b/app/code/core/Mage/Page/Block/Html/Toplinks.php index 73f2ce9e..d8d6c0cf 100644 --- a/app/code/core/Mage/Page/Block/Html/Toplinks.php +++ b/app/code/core/Mage/Page/Block/Html/Toplinks.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Html/Topmenu.php b/app/code/core/Mage/Page/Block/Html/Topmenu.php new file mode 100644 index 00000000..2b7b162b --- /dev/null +++ b/app/code/core/Mage/Page/Block/Html/Topmenu.php @@ -0,0 +1,206 @@ + + */ +class Mage_Page_Block_Html_Topmenu extends Mage_Core_Block_Template +{ + /** + * Top menu data tree + * + * @var Varien_Data_Tree_Node + */ + protected $_menu; + + /** + * Init top menu tree structure + */ + public function _construct() + { + $this->_menu = new Varien_Data_Tree_Node(array(), 'root', new Varien_Data_Tree()); + } + + /** + * Get top menu html + * + * @param string $outermostClass + * @param string $childrenWrapClass + * @return string + */ + public function getHtml($outermostClass = '', $childrenWrapClass = '') + { + Mage::dispatchEvent('page_block_html_topmenu_gethtml_before', array( + 'menu' => $this->_menu + )); + + $this->_menu->setOutermostClass($outermostClass); + $this->_menu->setChildrenWrapClass($childrenWrapClass); + + $html = $this->_getHtml($this->_menu, $childrenWrapClass); + + Mage::dispatchEvent('page_block_html_topmenu_gethtml_after', array( + 'menu' => $this->_menu, + 'html' => $html + )); + + return $html; + } + + /** + * Recursively generates top menu html from data that is specified in $menuTree + * + * @param Varien_Data_Tree_Node $menuTree + * @param string $childrenWrapClass + * @return string + */ + protected function _getHtml(Varien_Data_Tree_Node $menuTree, $childrenWrapClass) + { + $html = ''; + + $children = $menuTree->getChildren(); + $parentLevel = $menuTree->getLevel(); + $childLevel = is_null($parentLevel) ? 0 : $parentLevel + 1; + + $counter = 1; + $childrenCount = $children->count(); + + $parentPositionClass = $menuTree->getPositionClass(); + $itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-'; + + foreach ($children as $child) { + + $child->setLevel($childLevel); + $child->setIsFirst($counter == 1); + $child->setIsLast($counter == $childrenCount); + $child->setPositionClass($itemPositionClassPrefix . $counter); + + $outermostClassCode = ''; + $outermostClass = $menuTree->getOutermostClass(); + + if ($childLevel == 0 && $outermostClass) { + $outermostClassCode = ' class="' . $outermostClass . '" '; + $child->setClass($outermostClass); + } + + $html .= '
  • _getRenderedMenuItemAttributes($child) . '>'; + $html .= '' + . $this->escapeHtml($child->getName()) . ''; + + if ($child->hasChildren()) { + if (!empty($childrenWrapClass)) { + $html .= '
    '; + } + $html .= '
      '; + $html .= $this->_getHtml($child, $childrenWrapClass); + $html .= '
    '; + + if (!empty($childrenWrapClass)) { + $html .= '
    '; + } + } + $html .= '
  • '; + + $counter++; + } + + return $html; + } + + /** + * Generates string with all attributes that should be present in menu item element + * + * @param Varien_Data_Tree_Node $item + * @return string + */ + protected function _getRenderedMenuItemAttributes(Varien_Data_Tree_Node $item) + { + $html = ''; + $attributes = $this->_getMenuItemAttributes($item); + + foreach ($attributes as $attributeName => $attributeValue) { + $html .= ' ' . $attributeName . '="' . str_replace('"', '\"', $attributeValue) . '"'; + } + + return $html; + } + + /** + * Returns array of menu item's attributes + * + * @param Varien_Data_Tree_Node $item + * @return array + */ + protected function _getMenuItemAttributes(Varien_Data_Tree_Node $item) + { + $menuItemClasses = $this->_getMenuItemClasses($item); + $attributes = array( + 'class' => implode(' ', $menuItemClasses) + ); + + return $attributes; + } + + /** + * Returns array of menu item's classes + * + * @param Varien_Data_Tree_Node $item + * @return array + */ + protected function _getMenuItemClasses(Varien_Data_Tree_Node $item) + { + $classes = array(); + + $classes[] = 'level' . $item->getLevel(); + $classes[] = $item->getPositionClass(); + + if ($item->getIsFirst()) { + $classes[] = 'first'; + } + + if ($item->getIsActive()) { + $classes[] = 'active'; + } + + if ($item->getIsLast()) { + $classes[] = 'last'; + } + + if ($item->getClass()) { + $classes[] = $item->getClass(); + } + + if ($item->hasChildren()) { + $classes[] = 'parent'; + } + + return $classes; + } +} diff --git a/app/code/core/Mage/Page/Block/Html/Welcome.php b/app/code/core/Mage/Page/Block/Html/Welcome.php new file mode 100644 index 00000000..35a42db4 --- /dev/null +++ b/app/code/core/Mage/Page/Block/Html/Welcome.php @@ -0,0 +1,45 @@ + + */ +class Mage_Page_Block_Html_Welcome extends Mage_Core_Block_Template +{ + /** + * Get block messsage + * + * @return string + */ + protected function _toHtml() + { + return Mage::app()->getLayout()->getBlock('header')->getWelcome(); + } +} diff --git a/app/code/core/Mage/Page/Block/Html/Wrapper.php b/app/code/core/Mage/Page/Block/Html/Wrapper.php index cf76848a..a0ce0fa4 100644 --- a/app/code/core/Mage/Page/Block/Html/Wrapper.php +++ b/app/code/core/Mage/Page/Block/Html/Wrapper.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Js/Cookie.php b/app/code/core/Mage/Page/Block/Js/Cookie.php index 7e20a152..b6b778bc 100644 --- a/app/code/core/Mage/Page/Block/Js/Cookie.php +++ b/app/code/core/Mage/Page/Block/Js/Cookie.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Js/Translate.php b/app/code/core/Mage/Page/Block/Js/Translate.php index af229bc0..b9da1ec3 100644 --- a/app/code/core/Mage/Page/Block/Js/Translate.php +++ b/app/code/core/Mage/Page/Block/Js/Translate.php @@ -20,18 +20,19 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ - + /** * Js translation block * + * @deprecated since 1.7.0.0 (used in adminhtml/default/default/layout/main.xml) * @author Magento Core Team */ class Mage_Page_Block_Js_Translate extends Mage_Core_Block_Template { - public function __construct() + public function __construct() { parent::__construct(); } diff --git a/app/code/core/Mage/Page/Block/Redirect.php b/app/code/core/Mage/Page/Block/Redirect.php index 46577e9c..905a3173 100644 --- a/app/code/core/Mage/Page/Block/Redirect.php +++ b/app/code/core/Mage/Page/Block/Redirect.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Switch.php b/app/code/core/Mage/Page/Block/Switch.php index 6295d977..461795eb 100644 --- a/app/code/core/Mage/Page/Block/Switch.php +++ b/app/code/core/Mage/Page/Block/Switch.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -92,6 +92,11 @@ public function getRawStores() return $this->getData('raw_stores'); } + /** + * Retrieve list of store groups with default urls set + * + * @return array + */ public function getGroups() { if (!$this->hasData('groups')) { @@ -101,6 +106,7 @@ public function getGroups() $groups = array(); $localeCode = Mage::getStoreConfig('general/locale/code'); foreach ($rawGroups as $group) { + /* @var $group Mage_Core_Model_Store_Group */ if (!isset($rawStores[$group->getId()])) { continue; } @@ -108,16 +114,9 @@ public function getGroups() $groups[] = $group; continue; } - $store = false; - foreach ($rawStores[$group->getId()] as $s) { - if ($s->getLocaleCode() == $localeCode) { - $store = $s; - break; - } - } - if (!$store && isset($rawStores[$group->getId()][$group->getDefaultStoreId()])) { - $store = $rawStores[$group->getId()][$group->getDefaultStoreId()]; - } + + $store = $group->getDefaultStoreByLocale($localeCode); + if ($store) { $group->setHomeUrl($store->getHomeUrl()); $groups[] = $group; diff --git a/app/code/core/Mage/Page/Block/Template/Container.php b/app/code/core/Mage/Page/Block/Template/Container.php index ed995571..94d9d3a5 100644 --- a/app/code/core/Mage/Page/Block/Template/Container.php +++ b/app/code/core/Mage/Page/Block/Template/Container.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Block/Template/Links.php b/app/code/core/Mage/Page/Block/Template/Links.php index c0a808a4..a7f4b09a 100644 --- a/app/code/core/Mage/Page/Block/Template/Links.php +++ b/app/code/core/Mage/Page/Block/Template/Links.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -34,7 +34,6 @@ */ class Mage_Page_Block_Template_Links extends Mage_Core_Block_Template { - /** * All links * @@ -42,6 +41,13 @@ class Mage_Page_Block_Template_Links extends Mage_Core_Block_Template */ protected $_links = array(); + /** + * Cache key info + * + * @var null|array + */ + protected $_cacheKeyInfo = null; + /** * Set default template * @@ -92,23 +98,45 @@ public function addLink($label, $url='', $title='', $prepare=false, $urlParams=a 'after_text' => $afterText, )); + $this->_links[$this->_getNewPosition($position)] = $link; if (intval($position) > 0) { - while (isset($this->_links[$position])) { - $position++; - } - $this->_links[$position] = $link; - ksort($this->_links); - } else { - $position = 0; - foreach ($this->_links as $k=>$v) { - $position = $k; - } - $this->_links[$position+10] = $link; + ksort($this->_links); } return $this; } + /** + * Add block to link list + * + * @param string $blockName + * @return Mage_Page_Block_Template_Links + */ + public function addLinkBlock($blockName) + { + $block = $this->getLayout()->getBlock($blockName); + if ($block) { + $this->_links[$this->_getNewPosition((int)$block->getPosition())] = $block; + } + return $this; + } + + /** + * Remove Link block by blockName + * + * @param string $blockName + * @return Mage_Page_Block_Template_Links + */ + public function removeLinkBlock($blockName) + { + foreach ($this->_links as $key => $link) { + if ($link instanceof Mage_Core_Block_Abstract && $link->getNameInLayout() == $blockName) { + unset($this->_links[$key]); + } + } + return $this; + } + /** * Removes link by url * @@ -126,6 +154,32 @@ public function removeLinkByUrl($url) return $this; } + /** + * Get cache key informative items + * Provide string array key to share specific info item with FPC placeholder + * + * @return array + */ + public function getCacheKeyInfo() + { + if (is_null($this->_cacheKeyInfo)) { + $links = array(); + if (!empty($this->_links)) { + foreach ($this->_links as $position => $link) { + if ($link instanceof Varien_Object) { + $links[$position] = $link->getData(); + } + } + } + $this->_cacheKeyInfo = parent::getCacheKeyInfo() + array( + 'links' => base64_encode(serialize($links)), + 'name' => $this->getNameInLayout() + ); + } + + return $this->_cacheKeyInfo; + } + /** * Prepare tag attributes * @@ -162,4 +216,26 @@ protected function _beforeToHtml() return parent::_beforeToHtml(); } + /** + * Return new link position in list + * + * @param int $position + * @return int + */ + protected function _getNewPosition($position = 0) + { + if (intval($position) > 0) { + while (isset($this->_links[$position])) { + $position++; + } + } else { + $position = 0; + foreach ($this->_links as $k=>$v) { + $position = $k; + } + $position += 10; + } + return $position; + } + } diff --git a/app/code/core/Mage/Page/Block/Template/Links/Block.php b/app/code/core/Mage/Page/Block/Template/Links/Block.php new file mode 100644 index 00000000..d13cc97b --- /dev/null +++ b/app/code/core/Mage/Page/Block/Template/Links/Block.php @@ -0,0 +1,201 @@ + + */ +class Mage_Page_Block_Template_Links_Block extends Mage_Core_Block_Template +{ + + /** + * First link flag + * + * @var bool + */ + protected $_isFirst = false; + + /** + * Last link flag + * + * @var bool + */ + protected $_isLast = false; + + /** + * Link label + * + * @var string + */ + protected $_label = null; + + /** + * Link url + * + * @var string + */ + protected $_url = null; + + /** + * Link title + * + * @var string + */ + protected $_title = null; + + /** + * Li elemnt params + * + * @var string + */ + protected $_liPparams = null; + + /** + * A elemnt params + * + * @var string + */ + protected $_aPparams = null; + + /** + * Message before link text + * + * @var string + */ + protected $_beforeText = null; + + /** + * Message after link text + * + * @var string + */ + protected $_afterText = null; + + /** + * Position in link list + * @var int + */ + protected $_position = 0; + + /** + * Set default template + * + */ + protected function _construct() + { + $this->setTemplate('page/template/linksblock.phtml'); + } + + + /** + * Return link position in link list + * + * @return in + */ + public function getPosition() + { + return $this->_position; + } + + /** + * Return first position flag + * + * @return bool + */ + public function getIsFirst() + { + return $this->_isFirst; + } + + /** + * Set first list flag + * + * @param bool $value + * return Mage_Page_Block_Template_Links_Block + */ + public function setIsFirst($value) + { + $this->_isFirst = (bool)$value; + return $this; + } + + /** + * Return last position flag + * + * @return bool + */ + public function getIsLast() + { + return $this->_isLast; + } + + /** + * Set last list flag + * + * @param bool $value + * return Mage_Page_Block_Template_Links_Block + */ + public function setIsLast($value) + { + $this->_isLast = (bool)$value; + return $this; + } + + /** + * Return link label + * + * @return string + */ + public function getLabel() + { + return $this->_label; + } + + /** + * Return link title + * + * @return string + */ + public function getTitle() + { + return $this->_title; + } + + /** + * Return link url + * + * @return string + */ + public function getLinkUrl() + { + return $this->_url; + } + +} diff --git a/app/code/core/Mage/Page/Helper/Data.php b/app/code/core/Mage/Page/Helper/Data.php index 2e524083..d7daefb5 100644 --- a/app/code/core/Mage/Page/Helper/Data.php +++ b/app/code/core/Mage/Page/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -29,4 +29,5 @@ */ class Mage_Page_Helper_Data extends Mage_Core_Helper_Abstract { + } diff --git a/app/code/core/Mage/Page/Helper/Html.php b/app/code/core/Mage/Page/Helper/Html.php index 127cb5dd..20fd3e26 100644 --- a/app/code/core/Mage/Page/Helper/Html.php +++ b/app/code/core/Mage/Page/Helper/Html.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Helper/Layout.php b/app/code/core/Mage/Page/Helper/Layout.php index 303934e5..28456c66 100644 --- a/app/code/core/Mage/Page/Helper/Layout.php +++ b/app/code/core/Mage/Page/Helper/Layout.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -91,6 +91,11 @@ public function applyTemplate($pageLayout = null) */ public function getCurrentPageLayout() { + if ($this->getLayout()->getBlock('root') && + $this->getLayout()->getBlock('root')->getLayoutCode()) { + return $this->_getConfig()->getPageLayout($this->getLayout()->getBlock('root')->getLayoutCode()); + } + // All loaded handles $handles = $this->getLayout()->getUpdate()->getHandles(); // Handles used in page layouts diff --git a/app/code/core/Mage/Page/Model/Config.php b/app/code/core/Mage/Page/Model/Config.php index 5a9aae0b..6ce23e67 100644 --- a/app/code/core/Mage/Page/Model/Config.php +++ b/app/code/core/Mage/Page/Model/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/Model/Source/Layout.php b/app/code/core/Mage/Page/Model/Source/Layout.php index 891a5f27..27a75c2d 100644 --- a/app/code/core/Mage/Page/Model/Source/Layout.php +++ b/app/code/core/Mage/Page/Model/Source/Layout.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Page/etc/config.xml b/app/code/core/Mage/Page/etc/config.xml index ed5a9c16..eb075a4f 100644 --- a/app/code/core/Mage/Page/etc/config.xml +++ b/app/code/core/Mage/Page/etc/config.xml @@ -21,14 +21,14 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 0.7.0 + 1.6.0.0 @@ -46,7 +46,7 @@ - + page_empty @@ -129,8 +129,15 @@ Default welcome msg!
    - &copy; 2008 Magento Demo Store. All Rights Reserved. + &copy; 2012 Magento Demo Store. All Rights Reserved.
    + + + + favicon + + +
    diff --git a/app/code/core/Mage/Page/etc/system.xml b/app/code/core/Mage/Page/etc/system.xml index 50837140..bf7b976e 100644 --- a/app/code/core/Mage/Page/etc/system.xml +++ b/app/code/core/Mage/Page/etc/system.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Page - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -37,6 +37,17 @@ 1 1 + + + Allowed file types: ICO, PNG, GIF, JPG, JPEG, APNG, SVG. Not all browsers support all these formats! + image + adminhtml/system_config_backend_image_favicon + favicon + 5 + 1 + 1 + 1 + text diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget.php index fe87e47e..9f73884e 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -40,7 +40,7 @@ public function __construct() $this->_blockGroup = 'widget'; $this->_controller = 'adminhtml'; $this->_mode = 'widget'; - $this->_headerText = 'Widget Insertion'; + $this->_headerText = $this->helper('widget')->__('Widget Insertion'); $this->removeButton('reset'); $this->removeButton('back'); @@ -49,7 +49,8 @@ public function __construct() $this->_updateButton('save', 'id', 'insert_button'); $this->_updateButton('save', 'onclick', 'wWidget.insertWidget()'); - $this->_formScripts[] = 'wWidget = new WysiwygWidget.Widget("widget_options_form", "select_widget_type", "widget_options", "' - . $this->getUrl('*/*/loadOptions').'", "' . $this->getRequest()->getParam('widget_target_id') . '");'; + $this->_formScripts[] = 'wWidget = new WysiwygWidget.Widget(' + . '"widget_options_form", "select_widget_type", "widget_options", "' + . $this->getUrl('*/*/loadOptions') .'", "' . $this->getRequest()->getParam('widget_target_id') . '");'; } } diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Chooser.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Chooser.php index a2b38847..434617b2 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Chooser.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Chooser.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -157,6 +157,9 @@ protected function _toHtml() if ($this->getHiddenEnabled()) { $hidden = new Varien_Data_Form_Element_Hidden($element->getData()); $hidden->setId("{$chooserId}value")->setForm($element->getForm()); + if ($element->getRequired()) { + $hidden->addClass('required-entry'); + } $hiddenHtml = $hidden->getElementHtml(); $element->setValue(''); } @@ -167,16 +170,36 @@ protected function _toHtml() ->setId($chooserId . 'control') ->setClass('btn-chooser') ->setLabel($buttons['open']) - ->setOnclick($chooserId.'.choose()'); + ->setOnclick($chooserId.'.choose()') + ->setDisabled($element->getReadonly()); $chooser->setData('after_element_html', $hiddenHtml . $chooseButton->toHtml()); // render label and chooser scripts $configJson = Mage::helper('core')->jsonEncode($config->getData()); return ' - - + + + '; } } diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Form.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Form.php index e20208d5..a1c0a43f 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Form.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance.php index 34ab0c6b..6dd928cf 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit.php index 4f4fbb97..7c8d5fe7 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Block.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Block.php index ad26683c..1bdd161e 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Block.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Block.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -66,7 +66,7 @@ public function setAllowedBlocks($allowedBlocks) */ public function addAllowedBlock($block) { - $this->_allowedBlocks[] = $type; + $this->_allowedBlocks[] = $block; return $this; } @@ -92,7 +92,7 @@ public function setLayoutHandle($layoutHandle) if (is_string($layoutHandle)) { $layoutHandle = explode(',', $layoutHandle); } - $this->_layoutHandle = array_merge(array('default'), $layoutHandle); + $this->_layoutHandle = array_merge(array('default'), (array)$layoutHandle); return $this; } @@ -155,7 +155,8 @@ protected function _toHtml() $selectBlock = $this->getLayout()->createBlock('core/html_select') ->setName('block') ->setClass('required-entry select') - ->setExtraParams('onchange="WidgetInstance.loadSelectBoxByType(\'block_template\', this.up(\'div.group_container\'), this.value)"') + ->setExtraParams('onchange="WidgetInstance.loadSelectBoxByType(\'block_template\',' + .' this.up(\'div.group_container\'), this.value)"') ->setOptions($this->getBlocks()) ->setValue($this->getSelected()); return parent::_toHtml().$selectBlock->toHtml(); diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Layout.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Layout.php index 0d4706da..ab42b76e 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Layout.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Layout.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -128,7 +128,8 @@ protected function _toHtml() ->setName($this->getSelectName()) ->setId('layout_handle') ->setClass('required-entry select') - ->setExtraParams("onchange=\"WidgetInstance.loadSelectBoxByType(\'block_reference\', this.up(\'div.pages\'), this.value)\"") + ->setExtraParams("onchange=\"WidgetInstance.loadSelectBoxByType(\'block_reference\', " . + "this.up(\'div.pages\'), this.value)\"") ->setOptions($this->getLayoutHandles( $this->getArea(), $this->getPackage(), @@ -166,7 +167,9 @@ protected function _collectLayoutHandles($layoutHandles) foreach ($layoutHandlesArr as $node) { if ($this->_filterLayoutHandle($node->getName())) { $helper = Mage::helper(Mage_Core_Model_Layout::findTranslationModuleName($node)); - $this->_layoutHandles[$node->getName()] = $helper->__((string)$node->label); + $this->_layoutHandles[$node->getName()] = $this->helper('core')->jsQuoteEscape( + $helper->__((string)$node->label) + ); } } asort($this->_layoutHandles, SORT_STRING); diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Template.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Template.php index 441bb32a..8af2d5ad 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Template.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Chooser/Template.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Form.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Form.php index 82f72f6e..272c81af 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Form.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Form.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main.php index 7e1926a3..c322455e 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -148,13 +148,15 @@ protected function _prepareForm() )); if (!Mage::app()->isSingleStoreMode()) { - $fieldset->addField('store_ids', 'multiselect', array( + $field = $fieldset->addField('store_ids', 'multiselect', array( 'name' => 'store_ids[]', 'label' => Mage::helper('widget')->__('Assign to Store Views'), 'title' => Mage::helper('widget')->__('Assign to Store Views'), 'required' => true, 'values' => Mage::getSingleton('adminhtml/system_store')->getStoreValuesForForm(false, true), )); + $renderer = $this->getLayout()->createBlock('adminhtml/store_switcher_form_renderer_fieldset_element'); + $field->setRenderer($renderer); } $fieldset->addField('sort_order', 'text', array( diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php index ac9d410c..c71935de 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Main/Layout.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -150,45 +150,45 @@ protected function _getDisplayOnOptions() $options = array(); $options[] = array( 'value' => '', - 'label' => Mage::helper('widget')->__('-- Please Select --') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('-- Please Select --')) ); $options[] = array( 'label' => Mage::helper('widget')->__('Categories'), 'value' => array( array( 'value' => 'anchor_categories', - 'label' => Mage::helper('widget')->__('Anchor Categories') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Anchor Categories')) ), array( 'value' => 'notanchor_categories', - 'label' => Mage::helper('widget')->__('Non-Anchor Categories') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Non-Anchor Categories')) ) ) ); foreach (Mage_Catalog_Model_Product_Type::getTypes() as $typeId => $type) { $productsOptions[] = array( 'value' => $typeId.'_products', - 'label' => $type['label'] + 'label' => $this->helper('core')->jsQuoteEscape($type['label']) ); } array_unshift($productsOptions, array( 'value' => 'all_products', - 'label' => Mage::helper('widget')->__('All Product Types') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('All Product Types')) )); $options[] = array( - 'label' => Mage::helper('widget')->__('Products'), + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Products')), 'value' => $productsOptions ); $options[] = array( - 'label' => Mage::helper('widget')->__('Generic Pages'), + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Generic Pages')), 'value' => array( array( 'value' => 'all_pages', - 'label' => Mage::helper('widget')->__('All Pages') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('All Pages')) ), array( 'value' => 'pages', - 'label' => Mage::helper('widget')->__('Specified Page') + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Specified Page')) ) ) ); @@ -281,7 +281,7 @@ public function getRemoveLayoutButtonHtml() { $button = $this->getLayout()->createBlock('adminhtml/widget_button') ->setData(array( - 'label' => Mage::helper('widget')->__('Remove Layout Update'), + 'label' => $this->helper('core')->jsQuoteEscape(Mage::helper('widget')->__('Remove Layout Update')), 'onclick' => 'WidgetInstance.removePageGroup(this)', 'class' => 'delete' )); @@ -301,12 +301,12 @@ public function getPageGroups() foreach ($widgetInstance->getPageGroups() as $pageGroup) { $pageGroups[] = array( 'page_id' => $pageGroup['page_id'], - 'group' => $pageGroup['group'], + 'group' => $pageGroup['page_group'], 'block' => $pageGroup['block_reference'], - 'for_value' => $pageGroup['for'], + 'for_value' => $pageGroup['page_for'], 'layout_handle' => $pageGroup['layout_handle'], - $pageGroup['group'].'_entities' => $pageGroup['entities'], - 'template' => $pageGroup['template'] + $pageGroup['page_group'].'_entities' => $pageGroup['entities'], + 'template' => $pageGroup['page_template'] ); } } diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Properties.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Properties.php index 0ef63349..6cf78e2c 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Properties.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Properties.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Settings.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Settings.php index 020a92d8..ef409e9d 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Settings.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tab/Settings.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -112,18 +112,18 @@ protected function _prepareForm() $this->_addElementTypes($fieldset); $fieldset->addField('type', 'select', array( - 'name' => 'type', - 'label' => Mage::helper('widget')->__('Type'), - 'title' => Mage::helper('widget')->__('Type'), - 'class' => '', - 'values' => $this->getTypesOptionsArray() + 'name' => 'type', + 'label' => Mage::helper('widget')->__('Type'), + 'title' => Mage::helper('widget')->__('Type'), + 'required' => true, + 'values' => $this->getTypesOptionsArray() )); $fieldset->addField('package_theme', 'select', array( - 'name' => 'package_theme', - 'label' => Mage::helper('widget')->__('Design Package/Theme'), - 'title' => Mage::helper('widget')->__('Design Package/Theme'), - 'required' => false, + 'name' => 'package_theme', + 'label' => Mage::helper('widget')->__('Design Package/Theme'), + 'title' => Mage::helper('widget')->__('Design Package/Theme'), + 'required' => true, 'values' => $this->getPackegeThemeOptionsArray() )); $continueButton = $this->getLayout() @@ -150,9 +150,10 @@ protected function _prepareForm() public function getContinueUrl() { return $this->getUrl('*/*/*', array( - '_current' => true, - 'type' => '{{type}}', - 'package_theme' => '{{package_theme}}' + '_current' => true, + 'type' => '{{type}}', + 'package' => '{{package}}', + 'theme' => '{{theme}}' )); } diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tabs.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tabs.php index ccf259e1..ddcb11a9 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tabs.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Edit/Tabs.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Grid.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Grid.php index fbaebc72..1bb357ec 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Grid.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Instance/Grid.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -80,7 +80,7 @@ protected function _prepareColumns() $this->addColumn('type', array( 'header' => Mage::helper('widget')->__('Type'), 'align' => 'left', - 'index' => 'type', + 'index' => 'instance_type', 'type' => 'options', 'options' => $this->getTypesOptionsArray() )); diff --git a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Options.php b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Options.php index d650bc6d..75cef7f4 100644 --- a/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Options.php +++ b/app/code/core/Mage/Widget/Block/Adminhtml/Widget/Options.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Block/Interface.php b/app/code/core/Mage/Widget/Block/Interface.php index 28c414ff..caa8ce63 100644 --- a/app/code/core/Mage/Widget/Block/Interface.php +++ b/app/code/core/Mage/Widget/Block/Interface.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Helper/Data.php b/app/code/core/Mage/Widget/Helper/Data.php index 3f94af9a..923988b5 100644 --- a/app/code/core/Mage/Widget/Helper/Data.php +++ b/app/code/core/Mage/Widget/Helper/Data.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Model/Mysql4/Widget.php b/app/code/core/Mage/Widget/Model/Mysql4/Widget.php index 8d5c1c08..1904bb7e 100644 --- a/app/code/core/Mage/Widget/Model/Mysql4/Widget.php +++ b/app/code/core/Mage/Widget/Model/Mysql4/Widget.php @@ -20,10 +20,11 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Preconfigured widget * @@ -31,33 +32,6 @@ * @package Mage_Widget * @author Magento Core Team */ -class Mage_Widget_Model_Mysql4_Widget extends Mage_Core_Model_Mysql4_Abstract +class Mage_Widget_Model_Mysql4_Widget extends Mage_Widget_Model_Resource_Widget { - protected function _construct() - { - $this->_init('widget/widget', 'widget_id'); - } - - /** - * Retrieves preconfigured parameters for widget - * - * @param int $widgetId - * @return array - */ - public function loadPreconfiguredWidget($widgetId) - { - $read = $this->_getReadAdapter(); - $select = $read->select(); - $select->from($this->getMainTable()) - ->where($this->getIdFieldName() . ' = ?', $widgetId); - - $widget = $read->fetchRow($select); - if (is_array($widget)) { - if ($widget['parameters']) { - $widget['parameters'] = unserialize($widget['parameters']); - } - return $widget; - } - return false; - } } diff --git a/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance.php b/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance.php index e2badd3c..608a0f93 100644 --- a/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance.php +++ b/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance.php @@ -20,10 +20,11 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Widget Instance Resource Model * @@ -31,311 +32,6 @@ * @package Mage_Widget * @author Magento Core Team */ -class Mage_Widget_Model_Mysql4_Widget_Instance extends Mage_Core_Model_Mysql4_Abstract +class Mage_Widget_Model_Mysql4_Widget_Instance extends Mage_Widget_Model_Resource_Widget_Instance { - protected $_handlesToCleanCache = array(); - - /** - * Constructor - */ - protected function _construct() - { - $this->_init('widget/widget_instance', 'instance_id'); - } - - /** - * Perform actions after object load - * - * @param Mage_Widget_Model_Widget_Instance $object - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _afterLoad(Mage_Core_Model_Abstract $object) - { - $select = $this->_getReadAdapter()->select() - ->from($this->getTable('widget/widget_instance_page')) - ->where('instance_id = ?', $object->getId()); - $object->setData('page_groups', $this->_getReadAdapter()->fetchAll($select)); - return parent::_afterLoad($object); - } - - /** - * Perform actions after object save - * - * @param Mage_Widget_Model_Widget_Instance $object - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _afterSave(Mage_Core_Model_Abstract $object) - { - $pageTable = $this->getTable('widget/widget_instance_page'); - $pageLayoutTable = $this->getTable('widget/widget_instance_page_layout'); - $layoutUpdateTable = $this->getTable('core/layout_update'); - $layoutLinkTable = $this->getTable('core/layout_link'); - $write = $this->_getWriteAdapter(); - - $select = $write->select() - ->from($pageTable, array('page_id')) - ->where('instance_id = ?', $object->getId()); - $pageIds = $write->fetchCol($select); - - $removePageIds = array_diff($pageIds, $object->getData('page_group_ids')); - - $select = $write->select() - ->from($pageLayoutTable, array('layout_update_id')) - ->where('page_id in (?)', $pageIds); - $removeLayoutUpdateIds = $write->fetchCol($select); - - $this->_deleteWidgetInstancePages($removePageIds); - $write->delete($pageLayoutTable, $write->quoteInto('page_id in (?)', $pageIds)); - $this->_deleteLayoutUpdates($removeLayoutUpdateIds); - - foreach ($object->getData('page_groups') as $pageGroup) { - $pageLayoutUpdateIds = $this->_saveLayoutUpdates($object, $pageGroup); - $data = array( - 'group' => $pageGroup['group'], - 'layout_handle' => $pageGroup['layout_handle'], - 'block_reference' => $pageGroup['block_reference'], - 'for' => $pageGroup['for'], - 'entities' => $pageGroup['entities'], - 'template' => $pageGroup['template'], - ); - $pageId = $pageGroup['page_id']; - if (in_array($pageGroup['page_id'], $pageIds)) { - $write->update($pageTable, $data, $write->quoteInto('page_id = ?', $pageId)); - } else { - $write->insert($pageTable, - array_merge(array( - 'instance_id' => $object->getId()),$data - )); - $pageId = $write->lastInsertId(); - } - foreach ($pageLayoutUpdateIds as $layoutUpdateId) { - $write->insert($pageLayoutTable, array( - 'page_id' => $pageId, - 'layout_update_id' => $layoutUpdateId - )); - } - } - - return parent::_afterSave($object); - } - - /** - * Prepare and save layout updates data - * - * @param Mage_Widget_Model_Widget_Instance $widgetInstance - * @param array $pageGroupData - * @return array of inserted layout updates ids - */ - protected function _saveLayoutUpdates($widgetInstance, $pageGroupData) - { - $write = $this->_getWriteAdapter(); - $pageLayoutUpdateIds = array(); - $storeIds = $this->_prepareStoreIds($widgetInstance->getStoreIds()); - foreach ($pageGroupData['layout_handle_updates'] as $handle) { - $this->_getWriteAdapter()->insert( - $this->getTable('core/layout_update'), array( - 'handle' => $handle, - 'xml' => $widgetInstance->generateLayoutUpdateXml( - $pageGroupData['block_reference'], - $pageGroupData['template']), - 'sort_order' => $widgetInstance->getSortOrder() - )); - $layoutUpdateId = $this->_getWriteAdapter()->lastInsertId(); - $pageLayoutUpdateIds[] = $layoutUpdateId; - foreach ($storeIds as $storeId) { - $this->_getWriteAdapter()->insert( - $this->getTable('core/layout_link'), array( - 'store_id' => $storeId, - 'area' => $widgetInstance->getArea(), - 'package' => $widgetInstance->getPackage(), - 'theme' => $widgetInstance->getTheme(), - 'layout_update_id' => $layoutUpdateId - )); - } - } - return $pageLayoutUpdateIds; - } - - /** - * Prepare store ids. - * If one of store id is default (0) return all store ids - * - * @param array $storeIds - * @return array - */ - protected function _prepareStoreIds($storeIds) - { - if (in_array(0, $storeIds)) { - $storeIds = array(0); - } - return $storeIds; - } - - /** - * Perform actions before object delete. - * Collect page ids and layout update ids and set to object for further delete - * - * @param Varien_Object $object - */ - protected function _beforeDelete(Mage_Core_Model_Abstract $object) - { - $select = $this->_getWriteAdapter()->select() - ->from(array('main_table' => $this->getTable('widget/widget_instance_page')), array()) - ->joinInner( - array('layout_page_table' => $this->getTable('widget/widget_instance_page_layout')), - 'layout_page_table.page_id = main_table.page_id', - array('layout_page_table.layout_update_id') - ) - ->where('main_table.instance_id = ?', $object->getId()); - $object->setLayoutUpdateIdsToDelete($this->_getWriteAdapter()->fetchCol($select)); - return $this; - } - - /** - * Perform actions after object delete. - * Delete layout updates by layout update ids collected in _beforeSave - * - * @param Mage_Widget_Model_Widget_Instance $object - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _afterDelete(Mage_Core_Model_Abstract $object) - { - $this->_deleteLayoutUpdates($object->getLayoutUpdateIdsToDelete()); - return parent::_afterDelete($object); - } - - /** - * Delete widget instance pages by given ids - * - * @param array $pageIds - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _deleteWidgetInstancePages($pageIds) - { - $this->_getWriteAdapter()->delete( - $this->getTable('widget/widget_instance_page'), - $this->_getWriteAdapter()->quoteInto('page_id in (?)', $pageIds) - ); - $this->_getWriteAdapter()->delete( - $this->getTable('widget/widget_instance_page_layout'), - $this->_getWriteAdapter()->quoteInto('page_id in (?)', $pageIds) - ); - return $this; - } - - /** - * Delete layout updates by given ids - * - * @param array $layoutUpdateIds - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _deleteLayoutUpdates($layoutUpdateIds) - { - $this->_getWriteAdapter()->delete( - $this->getTable('core/layout_update'), - $this->_getWriteAdapter()->quoteInto('layout_update_id in (?)', $layoutUpdateIds) - ); - $this->_getWriteAdapter()->delete( - $this->getTable('core/layout_link'), - $this->_getWriteAdapter()->quoteInto('layout_update_id in (?)', $layoutUpdateIds) - ); - return $this; - } - - /** - * Get store ids to which specified item is assigned - * - * @param int $id - * @return array - */ - public function lookupStoreIds($id) - { - $storeIds = $this->_getReadAdapter()->fetchOne($this->_getReadAdapter()->select() - ->from($this->getMainTable(), 'store_ids') - ->where("{$this->getIdFieldName()} = ?", $id) - ); - return $storeIds ? explode(',', $storeIds) : array(); - } - - - - - - - /** - * Cache related methods are deprecated after 1.4.0.0-rc1 - * Added cache invalidation on model level - */ - - /** - * Clean cache by handles - * @deprecated - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _cleanLayoutCache() - { - $handles = $this->getHandlesToCleanCache(); - if (!empty($handles) && Mage::app()->useCache('layout')) { - Mage::app()->cleanCache($handles); - } - return $this; - } - - /** - * Clean blocks HTML otput cache - * @deprecated - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - protected function _cleanBlocksOutputCache() - { - if (Mage::app()->useCache('block_html')) { - Mage::app()->cleanCache(array('block_html')); - } - return $this; - } - - /** - * Reset handles to clean in cache - * @deprecated - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - public function resetHandlesToCleanCache() - { - $this->_handlesToCleanCache = array(); - return $this; - } - - /** - * Setter - * @deprecated - * @param array $handles - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - public function setHandlesToCleanCache($handles) - { - $this->_handlesToCleanCache = $handles; - return $this; - } - - /** - * Add handle to clean in cache - * @deprecated - * @param string $handle - * @return Mage_Widget_Model_Mysql4_Widget_Instance - */ - public function addHandleToCleanCache($handle) - { - $this->_handlesToCleanCache[] = $handle; - return $this; - } - - /** - * Getter - * @deprecated - * @return array - */ - public function getHandlesToCleanCache() - { - return $this->_handlesToCleanCache; - } } diff --git a/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance/Collection.php b/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance/Collection.php index 3d59532e..36a52019 100644 --- a/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance/Collection.php +++ b/app/code/core/Mage/Widget/Model/Mysql4/Widget/Instance/Collection.php @@ -20,10 +20,11 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ + /** * Widget Instance Collection * @@ -31,36 +32,6 @@ * @package Mage_Widget * @author Magento Core Team */ -class Mage_Widget_Model_Mysql4_Widget_Instance_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract +class Mage_Widget_Model_Mysql4_Widget_Instance_Collection extends Mage_Widget_Model_Resource_Widget_Instance_Collection { - /** - * Constructor - */ - protected function _construct() - { - parent::_construct(); - $this->_init('widget/widget_instance'); - } - - /** - * Filter by store ids - * - * @param array|integer $storeIds - * @param boolean $withDefaultStore if TRUE also filter by store id '0' - * @return Mage_Widget_Model_Mysql4_Widget_Instance_Collection - */ - public function addStoreFilter($storeIds = array(), $withDefaultStore = true) - { - if (!is_array($storeIds)) { - $storeIds = array($storeIds); - } - if ($withDefaultStore && !in_array(0, $storeIds)) { - array_unshift($storeIds, 0); - } - $select = $this->getSelect(); - foreach ($storeIds as $storeId) { - $select->orWhere('FIND_IN_SET(?, `store_ids`)', $storeId); - } - return $this; - } } diff --git a/app/code/core/Mage/Widget/Model/Observer.php b/app/code/core/Mage/Widget/Model/Observer.php index 6bbbf165..43432abe 100644 --- a/app/code/core/Mage/Widget/Model/Observer.php +++ b/app/code/core/Mage/Widget/Model/Observer.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Model/Resource/Widget.php b/app/code/core/Mage/Widget/Model/Resource/Widget.php new file mode 100755 index 00000000..d709c0c5 --- /dev/null +++ b/app/code/core/Mage/Widget/Model/Resource/Widget.php @@ -0,0 +1,69 @@ + + */ +class Mage_Widget_Model_Resource_Widget extends Mage_Core_Model_Resource_Db_Abstract +{ + + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('widget/widget', 'widget_id'); + } + + /** + * Retrieves pre-configured parameters for widget + * + * @param int $widgetId + * @return array + */ + public function loadPreconfiguredWidget($widgetId) + { + $readAdapter = $this->_getReadAdapter(); + $select = $readAdapter->select() + ->from($this->getMainTable()) + ->where($this->getIdFieldName() . '=:' . $this->getIdFieldName()); + $bind = array($this->getIdFieldName() => $widgetId); + $widget = $readAdapter->fetchRow($select, $bind); + if (is_array($widget)) { + if ($widget['parameters']) { + $widget['parameters'] = unserialize($widget['parameters']); + } + return $widget; + } + return false; + } +} diff --git a/app/code/core/Mage/Widget/Model/Resource/Widget/Instance.php b/app/code/core/Mage/Widget/Model/Resource/Widget/Instance.php new file mode 100755 index 00000000..b2c84017 --- /dev/null +++ b/app/code/core/Mage/Widget/Model/Resource/Widget/Instance.php @@ -0,0 +1,282 @@ + + */ +class Mage_Widget_Model_Resource_Widget_Instance extends Mage_Core_Model_Resource_Db_Abstract +{ + /** + * Define main table + * + */ + protected function _construct() + { + $this->_init('widget/widget_instance', 'instance_id'); + } + + /** + * Perform actions after object load + * + * @param Mage_Widget_Model_Widget_Instance $object + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _afterLoad(Mage_Core_Model_Abstract $object) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getTable('widget/widget_instance_page')) + ->where('instance_id = ?', (int)$object->getId()); + $result = $adapter->fetchAll($select); + $object->setData('page_groups', $result); + return parent::_afterLoad($object); + } + + /** + * Perform actions after object save + * + * @param Mage_Widget_Model_Widget_Instance $object + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _afterSave(Mage_Core_Model_Abstract $object) + { + $pageTable = $this->getTable('widget/widget_instance_page'); + $pageLayoutTable = $this->getTable('widget/widget_instance_page_layout'); + $readAdapter = $this->_getReadAdapter(); + $writeAdapter = $this->_getWriteAdapter(); + + $select = $readAdapter->select() + ->from($pageTable, 'page_id') + ->where('instance_id = ?', (int)$object->getId()); + $pageIds = $readAdapter->fetchCol($select); + + $removePageIds = array_diff($pageIds, $object->getData('page_group_ids')); + + if (is_array($pageIds) && count($pageIds) > 0) { + $inCond = $readAdapter->prepareSqlCondition('page_id', array('in' => $pageIds)); + + $select = $readAdapter->select() + ->from($pageLayoutTable, 'layout_update_id') + ->where($inCond); + $removeLayoutUpdateIds = $readAdapter->fetchCol($select); + + $writeAdapter->delete($pageLayoutTable, $inCond); + $this->_deleteLayoutUpdates($removeLayoutUpdateIds); + } + + $this->_deleteWidgetInstancePages($removePageIds); + + foreach ($object->getData('page_groups') as $pageGroup) { + $pageLayoutUpdateIds = $this->_saveLayoutUpdates($object, $pageGroup); + $data = array( + 'page_group' => $pageGroup['group'], + 'layout_handle' => $pageGroup['layout_handle'], + 'block_reference' => $pageGroup['block_reference'], + 'page_for' => $pageGroup['for'], + 'entities' => $pageGroup['entities'], + 'page_template' => $pageGroup['template'], + ); + $pageId = $pageGroup['page_id']; + if (in_array($pageGroup['page_id'], $pageIds)) { + $writeAdapter->update($pageTable, $data, array('page_id = ?' => (int)$pageId)); + } else { + $writeAdapter->insert($pageTable, + array_merge(array('instance_id' => $object->getId()), + $data)); + $pageId = $writeAdapter->lastInsertId($pageTable); + } + foreach ($pageLayoutUpdateIds as $layoutUpdateId) { + $writeAdapter->insert($pageLayoutTable, array( + 'page_id' => $pageId, + 'layout_update_id' => $layoutUpdateId + )); + } + } + + return parent::_afterSave($object); + } + + /** + * Prepare and save layout updates data + * + * @param Mage_Widget_Model_Widget_Instance $widgetInstance + * @param array $pageGroupData + * @return array of inserted layout updates ids + */ + protected function _saveLayoutUpdates($widgetInstance, $pageGroupData) + { + $writeAdapter = $this->_getWriteAdapter(); + $pageLayoutUpdateIds = array(); + $storeIds = $this->_prepareStoreIds($widgetInstance->getStoreIds()); + $layoutUpdateTable = $this->getTable('core/layout_update'); + $layoutUpdateLinkTable = $this->getTable('core/layout_link'); + + foreach ($pageGroupData['layout_handle_updates'] as $handle) { + $xml = $widgetInstance->generateLayoutUpdateXml( + $pageGroupData['block_reference'], + $pageGroupData['template'] + ); + $insert = array( + 'handle' => $handle, + 'xml' => $xml + ); + if (strlen($widgetInstance->getSortOrder())) { + $insert['sort_order'] = $widgetInstance->getSortOrder(); + }; + + $writeAdapter->insert($layoutUpdateTable, $insert); + $layoutUpdateId = $writeAdapter->lastInsertId($layoutUpdateTable); + $pageLayoutUpdateIds[] = $layoutUpdateId; + + $data = array(); + foreach ($storeIds as $storeId) { + $data[] = array( + 'store_id' => $storeId, + 'area' => $widgetInstance->getArea(), + 'package' => $widgetInstance->getPackage(), + 'theme' => $widgetInstance->getTheme(), + 'layout_update_id' => $layoutUpdateId); + } + $writeAdapter->insertMultiple($layoutUpdateLinkTable, $data); + } + return $pageLayoutUpdateIds; + } + + /** + * Prepare store ids. + * If one of store id is default (0) return all store ids + * + * @param array $storeIds + * @return array + */ + protected function _prepareStoreIds($storeIds) + { + if (in_array('0', $storeIds)) { + $storeIds = array(0); + } + return $storeIds; + } + + /** + * Perform actions before object delete. + * Collect page ids and layout update ids and set to object for further delete + * + * @param Varien_Object $object + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _beforeDelete(Mage_Core_Model_Abstract $object) + { + $writeAdapter = $this->_getWriteAdapter(); + $select = $writeAdapter->select() + ->from(array('main_table' => $this->getTable('widget/widget_instance_page')), array()) + ->joinInner( + array('layout_page_table' => $this->getTable('widget/widget_instance_page_layout')), + 'layout_page_table.page_id = main_table.page_id', + array('layout_update_id') + ) + ->where('main_table.instance_id=?', $object->getId()); + $result = $writeAdapter->fetchCol($select); + $object->setLayoutUpdateIdsToDelete($result); + return $this; + } + + /** + * Perform actions after object delete. + * Delete layout updates by layout update ids collected in _beforeSave + * + * @param Mage_Widget_Model_Widget_Instance $object + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _afterDelete(Mage_Core_Model_Abstract $object) + { + $this->_deleteLayoutUpdates($object->getLayoutUpdateIdsToDelete()); + return parent::_afterDelete($object); + } + + /** + * Delete widget instance pages by given ids + * + * @param array $pageIds + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _deleteWidgetInstancePages($pageIds) + { + $writeAdapter = $this->_getWriteAdapter(); + if ($pageIds) { + $inCond = $writeAdapter->prepareSqlCondition('page_id', array( + 'in' => $pageIds + )); + $writeAdapter->delete( + $this->getTable('widget/widget_instance_page'), + $inCond + ); + } + return $this; + } + + /** + * Delete layout updates by given ids + * + * @param array $layoutUpdateIds + * @return Mage_Widget_Model_Resource_Widget_Instance + */ + protected function _deleteLayoutUpdates($layoutUpdateIds) + { + $writeAdapter = $this->_getWriteAdapter(); + if ($layoutUpdateIds) { + $inCond = $writeAdapter->prepareSqlCondition('layout_update_id', array( + 'in' => $layoutUpdateIds + )); + $writeAdapter->delete( + $this->getTable('core/layout_update'), + $inCond + ); + } + return $this; + } + + /** + * Get store ids to which specified item is assigned + * + * @param int $id + * @return array + */ + public function lookupStoreIds($id) + { + $adapter = $this->_getReadAdapter(); + $select = $adapter->select() + ->from($this->getMainTable(), 'store_ids') + ->where("{$this->getIdFieldName()} = ?", (int)$id); + $storeIds = $adapter->fetchOne($select); + return $storeIds ? explode(',', $storeIds) : array(); + } + +} diff --git a/app/code/core/Mage/Widget/Model/Resource/Widget/Instance/Collection.php b/app/code/core/Mage/Widget/Model/Resource/Widget/Instance/Collection.php new file mode 100755 index 00000000..8c63ced0 --- /dev/null +++ b/app/code/core/Mage/Widget/Model/Resource/Widget/Instance/Collection.php @@ -0,0 +1,79 @@ + + */ +class Mage_Widget_Model_Resource_Widget_Instance_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract +{ + /** + * Fields map for corellation names & real selected fields + * + * @var array + */ + protected $_map = array('fields' => array('type' => 'instance_type')); + + + /** + * Constructor + * + */ + protected function _construct() + { + parent::_construct(); + $this->_init('widget/widget_instance'); + } + + /** + * Filter by store ids + * + * @param array|integer $storeIds + * @param boolean $withDefaultStore if TRUE also filter by store id '0' + * @return Mage_Widget_Model_Resource_Widget_Instance_Collection + */ + public function addStoreFilter($storeIds = array(), $withDefaultStore = true) + { + if (!is_array($storeIds)) { + $storeIds = array($storeIds); + } + if ($withDefaultStore && !in_array('0', $storeIds)) { + array_unshift($storeIds, 0); + } + $where = array(); + foreach ($storeIds as $storeId) { + $where[] = $this->_getConditionSql('store_ids', array('finset' => $storeId)); + } + + $this->_select->where(implode(' OR ', $where)); + + return $this; + } +} diff --git a/app/code/core/Mage/Widget/Model/Template/Filter.php b/app/code/core/Mage/Widget/Model/Template/Filter.php index f97d0440..cf6961ab 100644 --- a/app/code/core/Mage/Widget/Model/Template/Filter.php +++ b/app/code/core/Mage/Widget/Model/Template/Filter.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -55,7 +55,7 @@ public function widgetDirective($construction) } elseif (!empty($params['id'])) { $preconfigured = Mage::getResourceSingleton('widget/widget') ->loadPreconfiguredWidget($params['id']); - $type = $preconfigured['type']; + $type = $preconfigured['widget_type']; $params = $preconfigured['parameters']; } else { return ''; diff --git a/app/code/core/Mage/Widget/Model/Widget.php b/app/code/core/Mage/Widget/Model/Widget.php index c7c785db..b2579029 100644 --- a/app/code/core/Mage/Widget/Model/Widget.php +++ b/app/code/core/Mage/Widget/Model/Widget.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Model/Widget/Config.php b/app/code/core/Mage/Widget/Model/Widget/Config.php index 427c93b7..8d7c7841 100644 --- a/app/code/core/Mage/Widget/Model/Widget/Config.php +++ b/app/code/core/Mage/Widget/Model/Widget/Config.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/Model/Widget/Instance.php b/app/code/core/Mage/Widget/Model/Widget/Instance.php index ba9764ad..b5e316cc 100644 --- a/app/code/core/Mage/Widget/Model/Widget/Instance.php +++ b/app/code/core/Mage/Widget/Model/Widget/Instance.php @@ -20,13 +20,22 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Widget Instance Model * + * @method Mage_Widget_Model_Resource_Widget_Instance _getResource() + * @method Mage_Widget_Model_Resource_Widget_Instance getResource() + * @method string getTitle() + * @method Mage_Widget_Model_Widget_Instance setTitle(string $value) + * @method Mage_Widget_Model_Widget_Instance setStoreIds(string $value) + * @method Mage_Widget_Model_Widget_Instance setWidgetParameters(string $value) + * @method int getSortOrder() + * @method Mage_Widget_Model_Widget_Instance setSortOrder(int $value) + * * @category Mage * @package Mage_Widget * @author Magento Core Team @@ -55,6 +64,13 @@ class Mage_Widget_Model_Widget_Instance extends Mage_Core_Model_Abstract */ protected $_widgetConfigXml = null; + /** + * Prefix of model events names + * + * @var string + */ + protected $_eventPrefix = 'widget_widget_instance'; + /** * Internal Constructor */ @@ -74,11 +90,26 @@ protected function _construct() 'all_products' => self::SINGLE_PRODUCT_LAYOUT_HANLDE, ); foreach (Mage_Catalog_Model_Product_Type::getTypes() as $typeId => $type) { - $this->_layoutHandles[$typeId.'_products'] = str_replace('{{TYPE}}', $typeId, self::PRODUCT_TYPE_LAYOUT_HANDLE) ; - $this->_specificEntitiesLayoutHandles[$typeId.'_products'] = self::SINGLE_PRODUCT_LAYOUT_HANLDE; + $layoutHandle = str_replace('{{TYPE}}', $typeId, self::PRODUCT_TYPE_LAYOUT_HANDLE); + $this->_layoutHandles[$typeId . '_products'] = $layoutHandle; + $this->_specificEntitiesLayoutHandles[$typeId . '_products'] = self::SINGLE_PRODUCT_LAYOUT_HANLDE; } } + /** + * Init mapping array of short fields to + * its full names + * + * @return Varien_Object + */ + protected function _initOldFieldsMap() + { + $this->_oldFieldsMap = array( + 'type' => 'instance_type', + ); + return $this; + } + /** * Processing object before save data * @@ -136,6 +167,7 @@ protected function _beforeSave() } $this->setData('page_groups', $tmpPageGroups); $this->setData('page_group_ids', $pageGroupIds); + return parent::_beforeSave(); } @@ -211,7 +243,6 @@ protected function _prepareType() public function setPackageTheme($packageTheme) { $this->setData('package_theme', $packageTheme); - $this->_preparePackageTheme(); return $this; } @@ -223,20 +254,18 @@ public function setPackageTheme($packageTheme) */ public function getPackageTheme() { - $this->_preparePackageTheme(); return $this->_getData('package_theme'); } /** * Replace '_' to '/', if was set from request(GET request) * + * @deprecated after 1.6.1.0-alpha1 + * * @return Mage_Widget_Model_Widget_Instance */ protected function _preparePackageTheme() { - if (strpos($this->_getData('package_theme'), '_') >= 0) { - $this->setData('package_theme', str_replace('_', '/', $this->_getData('package_theme'))); - } return $this; } @@ -320,11 +349,11 @@ public function getWidgetParameters() if (is_string($this->getData('widget_parameters'))) { return unserialize($this->getData('widget_parameters')); } - return $this->getData('widget_parameters'); + return (is_array($this->getData('widget_parameters'))) ? $this->getData('widget_parameters') : array(); } /** - * Retrieve option arra of widget types + * Retrieve option array of widget types * * @return array */ @@ -474,16 +503,22 @@ public function generateLayoutUpdateXml($blockReference, $templatePath = '') if ($templatePath) { $template = ' template="' . $templatePath . '"'; } - $xml .= ''; + + $hash = Mage::helper('core')->uniqHash(); + $xml .= ''; foreach ($parameters as $name => $value) { if (is_array($value)) { $value = implode(',', $value); } if ($name && strlen((string)$value)) { - $xml .= '' . $name . '' . Mage::helper('widget')->htmlEscape($value) . ''; + $xml .= '' + . '' . $name . '' + . '' . Mage::helper('widget')->htmlEscape($value) . '' + . ''; } } $xml .= ''; + return $xml; } @@ -507,7 +542,7 @@ protected function _invalidateCache() */ protected function _afterSave() { - if ($this->dataHasChangedFor('page_groups')) { + if ($this->dataHasChangedFor('page_groups') || $this->dataHasChangedFor('widget_parameters')) { $this->_invalidateCache(); } return parent::_afterSave(); diff --git a/app/code/core/Mage/Widget/controllers/Adminhtml/Widget/InstanceController.php b/app/code/core/Mage/Widget/controllers/Adminhtml/Widget/InstanceController.php index 1a9a8c21..48517492 100644 --- a/app/code/core/Mage/Widget/controllers/Adminhtml/Widget/InstanceController.php +++ b/app/code/core/Mage/Widget/controllers/Adminhtml/Widget/InstanceController.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ @@ -62,25 +62,28 @@ protected function _initAction() /** * Init widget instance object and set it to registry * - * @return age_Widget_Model_Widget_Instance|boolean + * @return Mage_Widget_Model_Widget_Instance|boolean */ protected function _initWidgetInstance() { $this->_title($this->__('CMS'))->_title($this->__('Widgets')); + /** @var $widgetInstance Mage_Widget_Model_Widget_Instance */ $widgetInstance = Mage::getModel('widget/widget_instance'); + $instanceId = $this->getRequest()->getParam('instance_id', null); - $type = $this->getRequest()->getParam('type', null); - $packageTheme = $this->getRequest()->getParam('package_theme', null); + $type = $this->getRequest()->getParam('type', null); + $package = $this->getRequest()->getParam('package', null); + $theme = $this->getRequest()->getParam('theme', null); + if ($instanceId) { $widgetInstance->load($instanceId); if (!$widgetInstance->getId()) { - $this->_getSession()->addError(Mage::helper('widget')->__('Wrong wigdet instance specified.')); + $this->_getSession()->addError(Mage::helper('widget')->__('Wrong widget instance specified.')); return false; } - $data['type'] = $widgetInstance->getType(); - $data['package_theme'] = $widgetInstance->getPackageTheme(); } else { + $packageTheme = $package . '/' . $theme == '/' ? null : $package . '/' . $theme; $widgetInstance->setType($type) ->setPackageTheme($packageTheme); } @@ -127,6 +130,17 @@ public function editAction() $this->renderLayout(); } + /** + * Set body to response + * + * @param string $body + */ + private function setBody($body) + { + Mage::getSingleton('core/translate_inline')->processResponseBody($body); + $this->getResponse()->setBody($body); + } + /** * Validate action * @@ -143,7 +157,7 @@ public function validateAction() $response->setError(true); $response->setMessage($this->getLayout()->getMessagesBlock()->getGroupedHtml()); } - $this->getResponse()->setBody($response->toJson()); + $this->setBody($response->toJson()); } /** @@ -220,7 +234,7 @@ public function categoriesAction() ->setId(Mage::helper('core')->uniqHash('categories')) ->setIsAnchorOnly($isAnchorOnly) ->setSelectedCategories(explode(',', $selected)); - $this->getResponse()->setBody($chooser->toHtml()); + $this->setBody($chooser->toHtml()); } /** @@ -240,7 +254,7 @@ public function productsAction() /* @var $serializer Mage_Adminhtml_Block_Widget_Grid_Serializer */ $serializer = $this->getLayout()->createBlock('adminhtml/widget_grid_serializer'); $serializer->initSerializerBlock($chooser, 'getSelectedProducts', 'selected_products', 'selected_products'); - $this->getResponse()->setBody($chooser->toHtml().$serializer->toHtml()); + $this->setBody($chooser->toHtml().$serializer->toHtml()); } /** @@ -261,7 +275,7 @@ public function blocksAction() ->setLayoutHandle($layout) ->setSelected($selected) ->setAllowedBlocks($widgetInstance->getWidgetSupportedBlocks()); - $this->getResponse()->setBody($blocksChooser->toHtml()); + $this->setBody($blocksChooser->toHtml()); } /** @@ -278,7 +292,7 @@ public function templateAction() ->createBlock('widget/adminhtml_widget_instance_edit_chooser_template') ->setSelected($selected) ->setWidgetTemplates($widgetInstance->getWidgetSupportedTemplatesByBlock($block)); - $this->getResponse()->setBody($templateChooser->toHtml()); + $this->setBody($templateChooser->toHtml()); } /** diff --git a/app/code/core/Mage/Widget/controllers/Adminhtml/WidgetController.php b/app/code/core/Mage/Widget/controllers/Adminhtml/WidgetController.php index 7e2a5e90..3ffa1bdc 100644 --- a/app/code/core/Mage/Widget/controllers/Adminhtml/WidgetController.php +++ b/app/code/core/Mage/Widget/controllers/Adminhtml/WidgetController.php @@ -20,16 +20,16 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ /** * Widgets management controller * - * @category Mage - * @package Mage_Widget - * @author Magento Core Team + * @category Mage + * @package Mage_Widget + * @author Magento Core Team */ class Mage_Widget_Adminhtml_WidgetController extends Mage_Adminhtml_Controller_Action { diff --git a/app/code/core/Mage/Widget/etc/adminhtml.xml b/app/code/core/Mage/Widget/etc/adminhtml.xml index dc2b79ff..faee7eff 100644 --- a/app/code/core/Mage/Widget/etc/adminhtml.xml +++ b/app/code/core/Mage/Widget/etc/adminhtml.xml @@ -21,7 +21,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/code/core/Mage/Widget/etc/config.xml b/app/code/core/Mage/Widget/etc/config.xml index a156f9a5..7fbd4711 100644 --- a/app/code/core/Mage/Widget/etc/config.xml +++ b/app/code/core/Mage/Widget/etc/config.xml @@ -21,31 +21,40 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - 1.4.0.0.0 + 1.6.0.0 Mage_Widget_Model - widget_mysql4 + widget_resource - - Mage_Widget_Model_Mysql4 + + Mage_Widget_Model_Resource + widget_mysql4 - widget
    - widget_instance
    - widget_instance_page
    - widget_instance_page_layout
    + + widget
    +
    + + widget_instance
    +
    + + widget_instance_page
    +
    + + widget_instance_page_layout
    +
    -
    +
    diff --git a/app/code/core/Mage/Widget/etc/jstranslator.xml b/app/code/core/Mage/Widget/etc/jstranslator.xml new file mode 100644 index 00000000..8036b548 --- /dev/null +++ b/app/code/core/Mage/Widget/etc/jstranslator.xml @@ -0,0 +1,34 @@ + + + + + + Insert Widget... + + + diff --git a/app/code/core/Mage/Widget/sql/widget_setup/install-1.6.0.0.php b/app/code/core/Mage/Widget/sql/widget_setup/install-1.6.0.0.php new file mode 100644 index 00000000..652fb870 --- /dev/null +++ b/app/code/core/Mage/Widget/sql/widget_setup/install-1.6.0.0.php @@ -0,0 +1,208 @@ +startSetup(); + +/** + * Create table 'widget/widget' + */ +if (!$installer->getConnection()->isTableExists($installer->getTable('widget/widget'))) { + $table = $installer->getConnection() + ->newTable($installer->getTable('widget/widget')) + ->addColumn('widget_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Widget Id') + ->addColumn('widget_code', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Widget code for template directive') + ->addColumn('widget_type', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Widget Type') + ->addColumn('parameters', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + 'nullable' => true, + ), 'Parameters') + ->addIndex($installer->getIdxName('widget/widget', 'widget_code'), 'widget_code') + ->setComment('Preconfigured Widgets'); + $installer->getConnection()->createTable($table); +} else { + + $installer->getConnection()->dropIndex( + $installer->getTable('widget/widget'), + 'IDX_CODE' + ); + + $tables = array( + $installer->getTable('widget/widget') => array( + 'columns' => array( + 'widget_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Widget Id' + ), + 'parameters' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Parameters' + ) + ), + 'comment' => 'Preconfigured Widgets' + ) + ); + + $installer->getConnection()->modifyTables($tables); + + $installer->getConnection()->changeColumn( + $installer->getTable('widget/widget'), + 'code', + 'widget_code', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Widget code for template directive' + ) + ); + + $installer->getConnection()->changeColumn( + $installer->getTable('widget/widget'), + 'type', + 'widget_type', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Widget Type' + ) + ); + + $installer->getConnection()->addIndex( + $installer->getTable('widget/widget'), + $installer->getIdxName('widget/widget', array('widget_code')), + array('widget_code') + ); +} + +/** + * Create table 'widget/widget_instance' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('widget/widget_instance')) + ->addColumn('instance_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Instance Id') + ->addColumn('instance_type', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Instance Type') + ->addColumn('package_theme', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Package Theme') + ->addColumn('title', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Widget Title') + ->addColumn('store_ids', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + 'nullable' => false, + 'default' => '0', + ), 'Store ids') + ->addColumn('widget_parameters', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Widget parameters') + ->addColumn('sort_order', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Sort order') + ->setComment('Instances of Widget for Package Theme'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'widget/widget_instance_page' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('widget/widget_instance_page')) + ->addColumn('page_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + ), 'Page Id') + ->addColumn('instance_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Instance Id') + ->addColumn('page_group', Varien_Db_Ddl_Table::TYPE_TEXT, 25, array( + ), 'Block Group Type') + ->addColumn('layout_handle', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Layout Handle') + ->addColumn('block_reference', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Block Reference') + ->addColumn('page_for', Varien_Db_Ddl_Table::TYPE_TEXT, 25, array( + ), 'For instance entities') + ->addColumn('entities', Varien_Db_Ddl_Table::TYPE_TEXT, '64k', array( + ), 'Catalog entities (comma separated)') + ->addColumn('page_template', Varien_Db_Ddl_Table::TYPE_TEXT, 255, array( + ), 'Path to widget template') + ->addIndex($installer->getIdxName('widget/widget_instance_page', 'instance_id'), 'instance_id') + ->addForeignKey($installer->getFkName('widget/widget_instance_page', 'instance_id', 'widget/widget_instance', 'instance_id'), + 'instance_id', $installer->getTable('widget/widget_instance'), 'instance_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Instance of Widget on Page'); +$installer->getConnection()->createTable($table); + +/** + * Create table 'widget/widget_instance_page_layout' + */ +$table = $installer->getConnection() + ->newTable($installer->getTable('widget/widget_instance_page_layout')) + ->addColumn('page_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Page Id') + ->addColumn('layout_update_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + ), 'Layout Update Id') + ->addIndex($installer->getIdxName('widget/widget_instance_page_layout', 'page_id'), 'page_id') + ->addIndex($installer->getIdxName('widget/widget_instance_page_layout', 'layout_update_id'), 'layout_update_id') + ->addIndex($installer->getIdxName('widget/widget_instance_page_layout', array('layout_update_id', 'page_id'), Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE), + array('layout_update_id', 'page_id'), + array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)) + ->addForeignKey($installer->getFkName('widget/widget_instance_page_layout', 'page_id', 'widget/widget_instance_page', 'page_id'), + 'page_id', $installer->getTable('widget/widget_instance_page'), 'page_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->addForeignKey($installer->getFkName('widget/widget_instance_page_layout', 'layout_update_id', 'core/layout_update', 'layout_update_id'), + 'layout_update_id', $installer->getTable('core/layout_update'), 'layout_update_id', + Varien_Db_Ddl_Table::ACTION_CASCADE, Varien_Db_Ddl_Table::ACTION_CASCADE) + ->setComment('Layout updates'); +$installer->getConnection()->createTable($table); + +$installer->endSetup(); diff --git a/app/code/core/Mage/Widget/sql/widget_setup/mysql4-install-1.4.0.0.0.php b/app/code/core/Mage/Widget/sql/widget_setup/mysql4-install-1.4.0.0.0.php index b12ccb0d..feb4c7ed 100644 --- a/app/code/core/Mage/Widget/sql/widget_setup/mysql4-install-1.4.0.0.0.php +++ b/app/code/core/Mage/Widget/sql/widget_setup/mysql4-install-1.4.0.0.0.php @@ -20,7 +20,7 @@ * * @category Mage * @package Mage_Widget - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/app/code/core/Mage/Widget/sql/widget_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php b/app/code/core/Mage/Widget/sql/widget_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php new file mode 100644 index 00000000..dbbcf38b --- /dev/null +++ b/app/code/core/Mage/Widget/sql/widget_setup/mysql4-upgrade-1.5.9.9-1.6.0.0.php @@ -0,0 +1,351 @@ +startSetup(); + +/** + * Drop foreign keys + */ +$installer->getConnection()->dropForeignKey( + $installer->getTable('widget/widget_instance_page'), + 'FK_WIDGET_WIDGET_INSTANCE_ID' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('widget/widget_instance_page_layout'), + 'FK_WIDGET_WIDGET_INSTANCE_LAYOUT_UPDATE_ID' +); + +$installer->getConnection()->dropForeignKey( + $installer->getTable('widget/widget_instance_page_layout'), + 'FK_WIDGET_WIDGET_INSTANCE_PAGE_ID' +); + + +/** + * Drop indexes + */ +$installer->getConnection()->dropIndex( + $installer->getTable('widget/widget'), + 'IDX_CODE' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('widget/widget_instance_page'), + 'IDX_WIDGET_WIDGET_INSTANCE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('widget/widget_instance_page_layout'), + 'PAGE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('widget/widget_instance_page_layout'), + 'IDX_WIDGET_WIDGET_INSTANCE_PAGE_ID' +); + +$installer->getConnection()->dropIndex( + $installer->getTable('widget/widget_instance_page_layout'), + 'IDX_WIDGET_WIDGET_INSTANCE_LAYOUT_UPDATE_ID' +); + + +/** + * Change columns + */ +$tables = array( + $installer->getTable('widget/widget') => array( + 'columns' => array( + 'widget_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Widget Id' + ), + 'parameters' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Parameters' + ) + ), + 'comment' => 'Preconfigured Widgets' + ), + $installer->getTable('widget/widget_instance') => array( + 'columns' => array( + 'instance_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Instance Id' + ), + 'package_theme' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Package Theme' + ), + 'title' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Widget Title' + ), + 'store_ids' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Store ids' + ), + 'widget_parameters' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Widget parameters' + ), + 'sort_order' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_SMALLINT, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Sort order' + ) + ), + 'comment' => 'Instances of Widget for Package Theme' + ), + $installer->getTable('widget/widget_instance_page') => array( + 'columns' => array( + 'page_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'comment' => 'Page Id' + ), + 'instance_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => '0', + 'comment' => 'Instance Id' + ), + 'layout_handle' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Layout Handle' + ), + 'block_reference' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Block Reference' + ), + 'entities' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => '64K', + 'comment' => 'Catalog entities (comma separated)' + ) + ), + 'comment' => 'Instance of Widget on Page' + ), + $installer->getTable('widget/widget_instance_page_layout') => array( + 'columns' => array( + 'page_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Page Id' + ), + 'layout_update_id' => array( + 'type' => Varien_Db_Ddl_Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'default' => '0', + 'comment' => 'Layout Update Id' + ) + ), + 'comment' => 'Layout updates' + ) +); + +$installer->getConnection()->modifyTables($tables); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget'), + 'code', + 'widget_code', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Widget code for template directive' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget'), + 'type', + 'widget_type', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Widget Type' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget_instance'), + 'type', + 'instance_type', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Instance Type' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget_instance_page'), + 'group', + 'page_group', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 25, + 'comment' => 'Block Group Type' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget_instance_page'), + 'for', + 'page_for', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 25, + 'comment' => 'For instance entities' + ) +); + +$installer->getConnection()->changeColumn( + $installer->getTable('widget/widget_instance_page'), + 'template', + 'page_template', + array( + 'type' => Varien_Db_Ddl_Table::TYPE_TEXT, + 'length' => 255, + 'comment' => 'Path to widget template' + ) +); + + +/** + * Add indexes + */ +$installer->getConnection()->addIndex( + $installer->getTable('widget/widget'), + $installer->getIdxName('widget/widget', array('widget_code')), + array('widget_code') +); + +$installer->getConnection()->addIndex( + $installer->getTable('widget/widget_instance_page'), + $installer->getIdxName('widget/widget_instance_page', array('instance_id')), + array('instance_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('widget/widget_instance_page_layout'), + $installer->getIdxName( + 'widget/widget_instance_page_layout', + array('layout_update_id', 'page_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE + ), + array('layout_update_id', 'page_id'), + Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE +); + +$installer->getConnection()->addIndex( + $installer->getTable('widget/widget_instance_page_layout'), + $installer->getIdxName('widget/widget_instance_page_layout', array('page_id')), + array('page_id') +); + +$installer->getConnection()->addIndex( + $installer->getTable('widget/widget_instance_page_layout'), + $installer->getIdxName('widget/widget_instance_page_layout', array('layout_update_id')), + array('layout_update_id') +); + + +/** + * Add foreign keys + */ +$installer->getConnection()->addForeignKey( + $installer->getFkName( + 'widget/widget_instance_page', + 'instance_id', + 'widget/widget_instance', + 'instance_id' + ), + $installer->getTable('widget/widget_instance_page'), + 'instance_id', + $installer->getTable('widget/widget_instance'), + 'instance_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName( + 'widget/widget_instance_page_layout', + 'page_id', + 'widget/widget_instance_page', + 'page_id' + ), + $installer->getTable('widget/widget_instance_page_layout'), + 'page_id', + $installer->getTable('widget/widget_instance_page'), + 'page_id' +); + +$installer->getConnection()->addForeignKey( + $installer->getFkName( + 'widget/widget_instance_page_layout', + 'layout_update_id', + 'core/layout_update', + 'layout_update_id' + ), + $installer->getTable('widget/widget_instance_page_layout'), + 'layout_update_id', + $installer->getTable('core/layout_update'), + 'layout_update_id' +); + +$installer->endSetup(); diff --git a/app/code/core/Zend/Date.php b/app/code/core/Zend/Date.php new file mode 100644 index 00000000..1992f51b --- /dev/null +++ b/app/code/core/Zend/Date.php @@ -0,0 +1,5008 @@ + 'iso', // format for date strings 'iso' or 'php' + 'fix_dst' => true, // fix dst on summer/winter time change + 'extend_month' => false, // false - addMonth like SQL, true like excel + 'cache' => null, // cache to set + 'timesync' => null // timesync server to set + ); + + // Class wide Date Constants + const DAY = 'dd'; + const DAY_SHORT = 'd'; + const DAY_SUFFIX = 'SS'; + const DAY_OF_YEAR = 'D'; + const WEEKDAY = 'EEEE'; + const WEEKDAY_SHORT = 'EEE'; + const WEEKDAY_NARROW = 'E'; + const WEEKDAY_NAME = 'EE'; + const WEEKDAY_8601 = 'eee'; + const WEEKDAY_DIGIT = 'e'; + const WEEK = 'ww'; + const MONTH = 'MM'; + const MONTH_SHORT = 'M'; + const MONTH_DAYS = 'ddd'; + const MONTH_NAME = 'MMMM'; + const MONTH_NAME_SHORT = 'MMM'; + const MONTH_NAME_NARROW = 'MMMMM'; + const YEAR = 'y'; + const YEAR_SHORT = 'yy'; + const YEAR_8601 = 'Y'; + const YEAR_SHORT_8601 = 'YY'; + const LEAPYEAR = 'l'; + const MERIDIEM = 'a'; + const SWATCH = 'B'; + const HOUR = 'HH'; + const HOUR_SHORT = 'H'; + const HOUR_AM = 'hh'; + const HOUR_SHORT_AM = 'h'; + const MINUTE = 'mm'; + const MINUTE_SHORT = 'm'; + const SECOND = 'ss'; + const SECOND_SHORT = 's'; + const MILLISECOND = 'S'; + const TIMEZONE_NAME = 'zzzz'; + const DAYLIGHT = 'I'; + const GMT_DIFF = 'Z'; + const GMT_DIFF_SEP = 'ZZZZ'; + const TIMEZONE = 'z'; + const TIMEZONE_SECS = 'X'; + const ISO_8601 = 'c'; + const RFC_2822 = 'r'; + const TIMESTAMP = 'U'; + const ERA = 'G'; + const ERA_NAME = 'GGGG'; + const ERA_NARROW = 'GGGGG'; + const DATES = 'F'; + const DATE_FULL = 'FFFFF'; + const DATE_LONG = 'FFFF'; + const DATE_MEDIUM = 'FFF'; + const DATE_SHORT = 'FF'; + const TIMES = 'WW'; + const TIME_FULL = 'TTTTT'; + const TIME_LONG = 'TTTT'; + const TIME_MEDIUM = 'TTT'; + const TIME_SHORT = 'TT'; + const DATETIME = 'K'; + const DATETIME_FULL = 'KKKKK'; + const DATETIME_LONG = 'KKKK'; + const DATETIME_MEDIUM = 'KKK'; + const DATETIME_SHORT = 'KK'; + const ATOM = 'OOO'; + const COOKIE = 'CCC'; + const RFC_822 = 'R'; + const RFC_850 = 'RR'; + const RFC_1036 = 'RRR'; + const RFC_1123 = 'RRRR'; + const RFC_3339 = 'RRRRR'; + const RSS = 'SSS'; + const W3C = 'WWW'; + + /** + * Minimum allowed year value + */ + const YEAR_MIN_VALUE = -10000; + + /** + * Maximum allowed year value + */ + const YEAR_MAX_VALUE = 10000; + + /** + * Generates the standard date object, could be a unix timestamp, localized date, + * string, integer, array and so on. Also parts of dates or time are supported + * Always set the default timezone: http://php.net/date_default_timezone_set + * For example, in your bootstrap: date_default_timezone_set('America/Los_Angeles'); + * For detailed instructions please look in the docu. + * + * @param string|integer|Zend_Date|array $date OPTIONAL Date value or value of date part to set + * ,depending on $part. If null the actual time is set + * @param string $part OPTIONAL Defines the input format of $date + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function __construct($date = null, $part = null, $locale = null) + { + if (is_object($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date)) { + if ($locale instanceof Zend_Locale) { + $locale = $date; + $date = null; + $part = null; + } else { + $date = (string) $date; + } + } + + if (($date !== null) and !is_array($date) and !($date instanceof Zend_TimeSync_Protocol) and + !($date instanceof Zend_Date) and !defined($date) and Zend_Locale::isLocale($date, true, false)) { + $locale = $date; + $date = null; + $part = null; + } else if (($part !== null) and !defined($part) and Zend_Locale::isLocale($part, true, false)) { + $locale = $part; + $part = null; + } + + $this->setLocale($locale); + if (is_string($date) && ($part === null) && (strlen($date) <= 5)) { + $part = $date; + $date = null; + } + + if ($date === null) { + if ($part === null) { + $date = time(); + } else if ($part !== self::TIMESTAMP) { + $date = self::now($locale); + $date = $date->get($part); + } + } + + if ($date instanceof Zend_TimeSync_Protocol) { + $date = $date->getInfo(); + $date = $this->_getTime($date['offset']); + $part = null; + } else if (parent::$_defaultOffset != 0) { + $date = $this->_getTime(parent::$_defaultOffset); + } + + // set the timezone and offset for $this + $zone = @date_default_timezone_get(); + $this->setTimezone($zone); + + // try to get timezone from date-string + if (!is_int($date)) { + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + } + + // set datepart + if (($part !== null && $part !== self::TIMESTAMP) or (!is_numeric($date))) { + // switch off dst handling for value setting + $this->setUnixTimestamp($this->getGmtOffset()); + $this->set($date, $part, $this->_locale); + + // DST fix + if (is_array($date) === true) { + if (!isset($date['hour'])) { + $date['hour'] = 0; + } + + $hour = $this->toString('H', 'iso', true); + $hour = $date['hour'] - $hour; + switch ($hour) { + case 1 : + case -23 : + $this->addTimestamp(3600); + break; + case -1 : + case 23 : + $this->subTimestamp(3600); + break; + case 2 : + case -22 : + $this->addTimestamp(7200); + break; + case -2 : + case 22 : + $this->subTimestamp(7200); + break; + } + } + } else { + $this->setUnixTimestamp($date); + } + } + + /** + * Sets class wide options, if no option was given, the actual set options will be returned + * + * @param array $options Options to set + * @throws Zend_Date_Exception + * @return Options array if no option was given + */ + public static function setOptions(array $options = array()) + { + if (empty($options)) { + return self::$_options; + } + + foreach ($options as $name => $value) { + $name = strtolower($name); + + if (array_key_exists($name, self::$_options)) { + switch($name) { + case 'format_type' : + if ((strtolower($value) != 'php') && (strtolower($value) != 'iso')) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown format type ($value) for dates, only 'iso' and 'php' supported", 0, null, $value); + } + break; + case 'fix_dst' : + if (!is_bool($value)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'fix_dst' has to be boolean", 0, null, $value); + } + break; + case 'extend_month' : + if (!is_bool($value)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("'extend_month' has to be boolean", 0, null, $value); + } + break; + case 'cache' : + if ($value === null) { + parent::$_cache = null; + } else { + if (!$value instanceof Zend_Cache_Core) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_Cache expected"); + } + + parent::$_cache = $value; + parent::$_cacheTags = Zend_Date_DateObject::_getTagSupportForCache(); + Zend_Locale_Data::setCache($value); + } + break; + case 'timesync' : + if ($value === null) { + parent::$_defaultOffset = 0; + } else { + if (!$value instanceof Zend_TimeSync_Protocol) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Instance of Zend_TimeSync expected"); + } + + $date = $value->getInfo(); + parent::$_defaultOffset = $date['offset']; + } + break; + } + self::$_options[$name] = $value; + } + else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("Unknown option: $name = $value"); + } + } + } + + /** + * Returns this object's internal UNIX timestamp (equivalent to Zend_Date::TIMESTAMP). + * If the timestamp is too large for integers, then the return value will be a string. + * This function does not return the timestamp as an object. + * Use clone() or copyPart() instead. + * + * @return integer|string UNIX timestamp + */ + public function getTimestamp() + { + return $this->getUnixTimestamp(); + } + + /** + * Returns the calculated timestamp + * HINT: timestamps are always GMT + * + * @param string $calc Type of calculation to make + * @param string|integer|array|Zend_Date $stamp Timestamp to calculate, when null the actual timestamp is calculated + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _timestamp($calc, $stamp) + { + if ($stamp instanceof Zend_Date) { + // extract timestamp from object + $stamp = $stamp->getTimestamp(); + } + + if (is_array($stamp)) { + if (isset($stamp['timestamp']) === true) { + $stamp = $stamp['timestamp']; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('no timestamp given in array'); + } + } + + if ($calc === 'set') { + $return = $this->setUnixTimestamp($stamp); + } else { + $return = $this->_calcdetail($calc, $stamp, self::TIMESTAMP, null); + } + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + /** + * Sets a new timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to set + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTimestamp($timestamp) + { + return $this->_timestamp('set', $timestamp); + } + + /** + * Adds a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to add + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTimestamp($timestamp) + { + return $this->_timestamp('add', $timestamp); + } + + /** + * Subtracts a timestamp + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to sub + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subTimestamp($timestamp) + { + return $this->_timestamp('sub', $timestamp); + } + + /** + * Compares two timestamps, returning the difference as integer + * + * @param integer|string|array|Zend_Date $timestamp Timestamp to compare + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTimestamp($timestamp) + { + return $this->_timestamp('cmp', $timestamp); + } + + /** + * Returns a string representation of the object + * Supported format tokens are: + * G - era, y - year, Y - ISO year, M - month, w - week of year, D - day of year, d - day of month + * E - day of week, e - number of weekday (1-7), h - hour 1-12, H - hour 0-23, m - minute, s - second + * A - milliseconds of day, z - timezone, Z - timezone offset, S - fractional second, a - period of day + * + * Additionally format tokens but non ISO conform are: + * SS - day suffix, eee - php number of weekday(0-6), ddd - number of days per month + * l - Leap year, B - swatch internet time, I - daylight saving time, X - timezone offset in seconds + * r - RFC2822 format, U - unix timestamp + * + * Not supported ISO tokens are + * u - extended year, Q - quarter, q - quarter, L - stand alone month, W - week of month + * F - day of week of month, g - modified julian, c - stand alone weekday, k - hour 0-11, K - hour 1-24 + * v - wall zone + * + * @param string $format OPTIONAL Rule for formatting output. If null the default date format is used + * @param string $type OPTIONAL Type for the format string which overrides the standard setting + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function toString($format = null, $type = null, $locale = null) + { + if (is_object($format)) { + if ($format instanceof Zend_Locale) { + $locale = $format; + $format = null; + } else { + $format = (string) $format; + } + } + + if (is_object($type)) { + if ($type instanceof Zend_Locale) { + $locale = $type; + $type = null; + } else { + $type = (string) $type; + } + } + + if (($format !== null) && !defined($format) + && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') + && Zend_Locale::isLocale($format, null, false)) { + $locale = $format; + $format = null; + } + + if (($type !== null) and ($type != 'php') and ($type != 'iso') and + Zend_Locale::isLocale($type, null, false)) { + $locale = $type; + $type = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale) . ' ' . Zend_Locale_Format::getTimeFormat($locale); + } else if (((self::$_options['format_type'] == 'php') && ($type === null)) or ($type == 'php')) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + return $this->date($this->_toToken($format, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Returns a string representation of the date which is equal with the timestamp + * + * @return string + */ + public function __toString() + { + return $this->toString(null, $this->_locale); + } + + /** + * Returns a integer representation of the object + * But returns false when the given part is no value f.e. Month-Name + * + * @param string|integer|Zend_Date $part OPTIONAL Defines the date or datepart to return as integer + * @return integer|false + */ + public function toValue($part = null) + { + $result = $this->get($part); + if (is_numeric($result)) { + return intval("$result"); + } else { + return false; + } + } + + /** + * Returns an array representation of the object + * + * @return array + */ + public function toArray() + { + return array('day' => $this->toString(self::DAY_SHORT, 'iso'), + 'month' => $this->toString(self::MONTH_SHORT, 'iso'), + 'year' => $this->toString(self::YEAR, 'iso'), + 'hour' => $this->toString(self::HOUR_SHORT, 'iso'), + 'minute' => $this->toString(self::MINUTE_SHORT, 'iso'), + 'second' => $this->toString(self::SECOND_SHORT, 'iso'), + 'timezone' => $this->toString(self::TIMEZONE, 'iso'), + 'timestamp' => $this->toString(self::TIMESTAMP, 'iso'), + 'weekday' => $this->toString(self::WEEKDAY_8601, 'iso'), + 'dayofyear' => $this->toString(self::DAY_OF_YEAR, 'iso'), + 'week' => $this->toString(self::WEEK, 'iso'), + 'gmtsecs' => $this->toString(self::TIMEZONE_SECS, 'iso')); + } + + /** + * Returns a representation of a date or datepart + * This could be for example a localized monthname, the time without date, + * the era or only the fractional seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string $part OPTIONAL Part of the date to return, if null the timestamp is returned + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string date or datepart + */ + public function get($part = null, $locale = null) + { + if ($locale === null) { + $locale = $this->getLocale(); + } + + if (($part !== null) && !defined($part) + && ($part != 'ee') && ($part != 'ss') && ($part != 'GG') && ($part != 'MM') && ($part != 'EE') && ($part != 'TT') + && Zend_Locale::isLocale($part, null, false)) { + $locale = $part; + $part = null; + } + + if ($part === null) { + $part = self::TIMESTAMP; + } else if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + return $this->date($this->_toToken($part, $locale), $this->getUnixTimestamp(), false); + } + + /** + * Internal method to apply tokens + * + * @param string $part + * @param string $locale + * @return string + */ + private function _toToken($part, $locale) { + // get format tokens + $comment = false; + $format = ''; + $orig = ''; + for ($i = 0; isset($part[$i]); ++$i) { + if ($part[$i] == "'") { + $comment = $comment ? false : true; + if (isset($part[$i+1]) && ($part[$i+1] == "'")) { + $comment = $comment ? false : true; + $format .= "\\'"; + ++$i; + } + + $orig = ''; + continue; + } + + if ($comment) { + $format .= '\\' . $part[$i]; + $orig = ''; + } else { + $orig .= $part[$i]; + if (!isset($part[$i+1]) || (isset($orig[0]) && ($orig[0] != $part[$i+1]))) { + $format .= $this->_parseIsoToDate($orig, $locale); + $orig = ''; + } + } + } + + return $format; + } + + /** + * Internal parsing method + * + * @param string $token + * @param string $locale + * @return string + */ + private function _parseIsoToDate($token, $locale) { + switch($token) { + case self::DAY : + return 'd'; + break; + + case self::WEEKDAY_SHORT : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 3, 'UTF-8')); + break; + + case self::DAY_SHORT : + return 'j'; + break; + + case self::WEEKDAY : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'wide', $weekday))); + break; + + case self::WEEKDAY_8601 : + return 'N'; + break; + + case 'ee' : + return $this->_toComment(str_pad($this->date('N', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::DAY_SUFFIX : + return 'S'; + break; + + case self::WEEKDAY_DIGIT : + return 'w'; + break; + + case self::DAY_OF_YEAR : + return 'z'; + break; + + case 'DDD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 3, '0', STR_PAD_LEFT)); + break; + + case 'DD' : + return $this->_toComment(str_pad($this->date('z', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::WEEKDAY_NARROW : + case 'EEEEE' : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + $day = Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday)); + return $this->_toComment(iconv_substr($day, 0, 1, 'UTF-8')); + break; + + case self::WEEKDAY_NAME : + $weekday = strtolower($this->date('D', $this->getUnixTimestamp(), false)); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'day', array('gregorian', 'format', 'abbreviated', $weekday))); + break; + + case 'w' : + $week = $this->date('W', $this->getUnixTimestamp(), false); + return $this->_toComment(($week[0] == '0') ? $week[1] : $week); + break; + + case self::WEEK : + return 'W'; + break; + + case self::MONTH_NAME : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'wide', $month))); + break; + + case self::MONTH : + return 'm'; + break; + + case self::MONTH_NAME_SHORT : + $month = $this->date('n', $this->getUnixTimestamp(), false); + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month))); + break; + + case self::MONTH_SHORT : + return 'n'; + break; + + case self::MONTH_DAYS : + return 't'; + break; + + case self::MONTH_NAME_NARROW : + $month = $this->date('n', $this->getUnixTimestamp(), false); + $mon = Zend_Locale_Data::getContent($locale, 'month', array('gregorian', 'format', 'abbreviated', $month)); + return $this->_toComment(iconv_substr($mon, 0, 1, 'UTF-8')); + break; + + case self::LEAPYEAR : + return 'L'; + break; + + case self::YEAR_8601 : + return 'o'; + break; + + case self::YEAR : + return 'Y'; + break; + + case self::YEAR_SHORT : + return 'y'; + break; + + case self::YEAR_SHORT_8601 : + return $this->_toComment(substr($this->date('o', $this->getUnixTimestamp(), false), -2, 2)); + break; + + case self::MERIDIEM : + $am = $this->date('a', $this->getUnixTimestamp(), false); + if ($am == 'am') { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'am')); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'pm')); + break; + + case self::SWATCH : + return 'B'; + break; + + case self::HOUR_SHORT_AM : + return 'g'; + break; + + case self::HOUR_SHORT : + return 'G'; + break; + + case self::HOUR_AM : + return 'h'; + break; + + case self::HOUR : + return 'H'; + break; + + case self::MINUTE : + return $this->_toComment(str_pad($this->date('i', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::SECOND : + return $this->_toComment(str_pad($this->date('s', $this->getUnixTimestamp(), false), 2, '0', STR_PAD_LEFT)); + break; + + case self::MINUTE_SHORT : + return 'i'; + break; + + case self::SECOND_SHORT : + return 's'; + break; + + case self::MILLISECOND : + return $this->_toComment($this->getMilliSecond()); + break; + + case self::TIMEZONE_NAME : + case 'vvvv' : + return 'e'; + break; + + case self::DAYLIGHT : + return 'I'; + break; + + case self::GMT_DIFF : + case 'ZZ' : + case 'ZZZ' : + return 'O'; + break; + + case self::GMT_DIFF_SEP : + return 'P'; + break; + + case self::TIMEZONE : + case 'v' : + case 'zz' : + case 'zzz' : + return 'T'; + break; + + case self::TIMEZONE_SECS : + return 'Z'; + break; + + case self::ISO_8601 : + return 'c'; + break; + + case self::RFC_2822 : + return 'r'; + break; + + case self::TIMESTAMP : + return 'U'; + break; + + case self::ERA : + case 'GG' : + case 'GGG' : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1'))); + break; + + case self::ERA_NARROW : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '0')), 0, 1, 'UTF-8')) . '.'; + } + + return $this->_toComment(iconv_substr(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Abbr', '1')), 0, 1, 'UTF-8')) . '.'; + break; + + case self::ERA_NAME : + $year = $this->date('Y', $this->getUnixTimestamp(), false); + if ($year < 0) { + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '0'))); + } + + return $this->_toComment(Zend_Locale_Data::getContent($locale, 'era', array('gregorian', 'Names', '1'))); + break; + + case self::DATES : + return $this->_toToken(Zend_Locale_Format::getDateFormat($locale), $locale); + break; + + case self::DATE_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')), $locale); + break; + + case self::DATE_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')), $locale); + break; + + case self::DATE_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')), $locale); + break; + + case self::DATE_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')), $locale); + break; + + case self::TIMES : + return $this->_toToken(Zend_Locale_Format::getTimeFormat($locale), $locale); + break; + + case self::TIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'full'), $locale); + break; + + case self::TIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'long'), $locale); + break; + + case self::TIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'medium'), $locale); + break; + + case self::TIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'time', 'short'), $locale); + break; + + case self::DATETIME : + return $this->_toToken(Zend_Locale_Format::getDateTimeFormat($locale), $locale); + break; + + case self::DATETIME_FULL : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')), $locale); + break; + + case self::DATETIME_LONG : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')), $locale); + break; + + case self::DATETIME_MEDIUM : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')), $locale); + break; + + case self::DATETIME_SHORT : + return $this->_toToken(Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')), $locale); + break; + + case self::ATOM : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::COOKIE : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_822 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_850 : + return 'l\, d\-M\-y H\:i\:s e'; + break; + + case self::RFC_1036 : + return 'D\, d M y H\:i\:s O'; + break; + + case self::RFC_1123 : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::RFC_3339 : + return 'Y\-m\-d\TH\:i\:sP'; + break; + + case self::RSS : + return 'D\, d M Y H\:i\:s O'; + break; + + case self::W3C : + return 'Y\-m\-d\TH\:i\:sP'; + break; + } + + if ($token == '') { + return ''; + } + + switch ($token[0]) { + case 'y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'Y'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('Y', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'Y' : + if ((strlen($token) == 4) && (abs($this->getUnixTimestamp()) <= 0x7FFFFFFF)) { + return 'o'; + } + + $length = iconv_strlen($token, 'UTF-8'); + return $this->_toComment(str_pad($this->date('o', $this->getUnixTimestamp(), false), $length, '0', STR_PAD_LEFT)); + break; + + case 'A' : + $length = iconv_strlen($token, 'UTF-8'); + $result = substr($this->getMilliSecond(), 0, 3); + $result += $this->date('s', $this->getUnixTimestamp(), false) * 1000; + $result += $this->date('i', $this->getUnixTimestamp(), false) * 60000; + $result += $this->date('H', $this->getUnixTimestamp(), false) * 3600000; + + return $this->_toComment(str_pad($result, $length, '0', STR_PAD_LEFT)); + break; + } + + return $this->_toComment($token); + } + + /** + * Private function to make a comment of a token + * + * @param string $token + * @return string + */ + private function _toComment($token) + { + $token = str_split($token); + $result = ''; + foreach ($token as $tok) { + $result .= '\\' . $tok; + } + + return $result; + } + + /** + * Return digit from standard names (english) + * Faster implementation than locale aware searching + * + * @param string $name + * @return integer Number of this month + * @throws Zend_Date_Exception + */ + private function _getDigitFromName($name) + { + switch($name) { + case "Jan": + return 1; + + case "Feb": + return 2; + + case "Mar": + return 3; + + case "Apr": + return 4; + + case "May": + return 5; + + case "Jun": + return 6; + + case "Jul": + return 7; + + case "Aug": + return 8; + + case "Sep": + return 9; + + case "Oct": + return 10; + + case "Nov": + return 11; + + case "Dec": + return 12; + + default: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Month ($name) is not a known month'); + } + } + + /** + * Counts the exact year number + * < 70 - 2000 added, >70 < 100 - 1900, others just returned + * + * @param integer $value year number + * @return integer Number of year + */ + public static function getFullYear($value) + { + if ($value >= 0) { + if ($value < 70) { + $value += 2000; + } else if ($value < 100) { + $value += 1900; + } + } + return $value; + } + + /** + * Sets the given date as new date or a given datepart as new datepart returning the new datepart + * This could be for example a localized dayname, the date without time, + * the month or only the seconds. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * + * @param string|integer|array|Zend_Date $date Date or datepart to set + * @param string $part OPTIONAL Part of the date to set, if null the timestamp is set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function set($date, $part = null, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $zone = $this->getTimezoneFromString($date); + $this->setTimezone($zone); + + $this->_calculate('set', $date, $part, $locale); + return $this; + } + + /** + * Adds a date or datepart to the existing date, by extracting $part from $date, + * and modifying this object by adding that part. The $part is then extracted from + * this object and returned as an integer or numeric string (for large values, or $part's + * corresponding to pre-defined formatted date strings). + * This could be for example a ISO 8601 date, the hour the monthname or only the minute. + * There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu. + * + * @param string|integer|array|Zend_Date $date Date or datepart to add + * @param string $part OPTIONAL Part of the date to add, if null the timestamp is added + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function add($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('add', $date, $part, $locale); + return $this; + } + + /** + * Subtracts a date from another date. + * This could be for example a RFC2822 date, the time, + * the year or only the timestamp. There are about 50 different supported date parts. + * For a complete list of supported datepart values look into the docu + * Be aware: Adding -2 Months is not equal to Subtracting 2 Months !!! + * + * @param string|integer|array|Zend_Date $date Date or datepart to subtract + * @param string $part OPTIONAL Part of the date to sub, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function sub($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $this->_calculate('sub', $date, $part, $locale); + return $this; + } + + /** + * Compares a date or datepart with the existing one. + * Returns -1 if earlier, 0 if equal and 1 if later. + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with the date object + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compare($date, $part = self::TIMESTAMP, $locale = null) + { + if (self::$_options['format_type'] == 'php') { + $part = Zend_Locale_Format::convertPhpToIsoFormat($part); + } + + $compare = $this->_calculate('cmp', $date, $part, $locale); + + if ($compare > 0) { + return 1; + } else if ($compare < 0) { + return -1; + } + return 0; + } + + /** + * Returns a new instance of Zend_Date with the selected part copied. + * To make an exact copy, use PHP's clone keyword. + * For a complete list of supported date part values look into the docu. + * If a date part is copied, all other date parts are set to standard values. + * For example: If only YEAR is copied, the returned date object is equal to + * 01-01-YEAR 00:00:00 (01-01-1970 00:00:00 is equal to timestamp 0) + * If only HOUR is copied, the returned date object is equal to + * 01-01-1970 HOUR:00:00 (so $this contains a timestamp equal to a timestamp of 0 plus HOUR). + * + * @param string $part Part of the date to compare, if null the timestamp is subtracted + * @param string|Zend_Locale $locale OPTIONAL New object's locale. No adjustments to timezone are made. + * @return Zend_Date New clone with requested part + */ + public function copyPart($part, $locale = null) + { + $clone = clone $this; // copy all instance variables + $clone->setUnixTimestamp(0); // except the timestamp + if ($locale != null) { + $clone->setLocale($locale); // set an other locale if selected + } + $clone->set($this, $part); + return $clone; + } + + /** + * Internal function, returns the offset of a given timezone + * + * @param string $zone + * @return integer + */ + public function getTimezoneFromString($zone) + { + if (is_array($zone)) { + return $this->getTimezone(); + } + + if ($zone instanceof Zend_Date) { + return $zone->getTimezone(); + } + + $match = array(); + preg_match('/\dZ$/', $zone, $match); + if (!empty($match)) { + return "Etc/UTC"; + } + + preg_match('/([+-]\d{2}):{0,1}\d{2}/', $zone, $match); + if (!empty($match) and ($match[count($match) - 1] <= 12) and ($match[count($match) - 1] >= -12)) { + $zone = "Etc/GMT"; + $zone .= ($match[count($match) - 1] < 0) ? "+" : "-"; + $zone .= (int) abs($match[count($match) - 1]); + return $zone; + } + + preg_match('/([[:alpha:]\/]{3,30})(?!.*([[:alpha:]\/]{3,30}))/', $zone, $match); + try { + if (!empty($match) and (!is_int($match[count($match) - 1]))) { + $oldzone = $this->getTimezone(); + $this->setTimezone($match[count($match) - 1]); + $result = $this->getTimezone(); + $this->setTimezone($oldzone); + if ($result !== $oldzone) { + return $match[count($match) - 1]; + } + } + } catch (Exception $e) { + // fall through + } + + return $this->getTimezone(); + } + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make + * @param string|integer $date Date for calculation + * @param string|integer $comp Second date for calculation + * @param boolean|integer $dst Use dst correction if option is set + * @return integer|string|Zend_Date new timestamp or Zend_Date depending on calculation + */ + private function _assign($calc, $date, $comp = 0, $dst = false) + { + switch ($calc) { + case 'set' : + if (!empty($comp)) { + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $comp)); + } + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'add' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$add, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + case 'sub' : + $this->setUnixTimestamp(call_user_func(Zend_Locale_Math::$sub, $this->getUnixTimestamp(), $date)); + $value = $this->getUnixTimestamp(); + break; + default : + // cmp - compare + return call_user_func(Zend_Locale_Math::$comp, $comp, $date); + break; + } + + // dst-correction if 'fix_dst' = true and dst !== false but only for non UTC and non GMT + if ((self::$_options['fix_dst'] === true) and ($dst !== false) and ($this->_dst === true)) { + $hour = $this->toString(self::HOUR, 'iso'); + if ($hour != $dst) { + if (($dst == ($hour + 1)) or ($dst == ($hour - 23))) { + $value += 3600; + } else if (($dst == ($hour - 1)) or ($dst == ($hour + 23))) { + $value -= 3600; + } + $this->setUnixTimestamp($value); + } + } + return $this->getUnixTimestamp(); + } + + + /** + * Calculates the date or object + * + * @param string $calc Calculation to make, one of: 'add'|'sub'|'cmp'|'copy'|'set' + * @param string|integer|array|Zend_Date $date Date or datepart to calculate with + * @param string $part Part of the date to calculate, if null the timestamp is used + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string|Zend_Date new timestamp + * @throws Zend_Date_Exception + */ + private function _calculate($calc, $date, $part, $locale) + { + if ($date === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if (($part !== null) && (strlen($part) !== 2) && (Zend_Locale::isLocale($part, null, false))) { + $locale = $part; + $part = null; + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + $locale = (string) $locale; + + // Create date parts + $year = $this->toString(self::YEAR, 'iso'); + $month = $this->toString(self::MONTH_SHORT, 'iso'); + $day = $this->toString(self::DAY_SHORT, 'iso'); + $hour = $this->toString(self::HOUR_SHORT, 'iso'); + $minute = $this->toString(self::MINUTE_SHORT, 'iso'); + $second = $this->toString(self::SECOND_SHORT, 'iso'); + // If object extract value + if ($date instanceof Zend_Date) { + $date = $date->toString($part, 'iso', $locale); + } + + if (is_array($date) === true) { + if (empty($part) === false) { + switch($part) { + // Fall through + case self::DAY: + case self::DAY_SHORT: + if (isset($date['day']) === true) { + $date = $date['day']; + } + break; + // Fall through + case self::WEEKDAY_SHORT: + case self::WEEKDAY: + case self::WEEKDAY_8601: + case self::WEEKDAY_DIGIT: + case self::WEEKDAY_NARROW: + case self::WEEKDAY_NAME: + if (isset($date['weekday']) === true) { + $date = $date['weekday']; + $part = self::WEEKDAY_DIGIT; + } + break; + case self::DAY_OF_YEAR: + if (isset($date['day_of_year']) === true) { + $date = $date['day_of_year']; + } + break; + // Fall through + case self::MONTH: + case self::MONTH_SHORT: + case self::MONTH_NAME: + case self::MONTH_NAME_SHORT: + case self::MONTH_NAME_NARROW: + if (isset($date['month']) === true) { + $date = $date['month']; + } + break; + // Fall through + case self::YEAR: + case self::YEAR_SHORT: + case self::YEAR_8601: + case self::YEAR_SHORT_8601: + if (isset($date['year']) === true) { + $date = $date['year']; + } + break; + // Fall through + case self::HOUR: + case self::HOUR_AM: + case self::HOUR_SHORT: + case self::HOUR_SHORT_AM: + if (isset($date['hour']) === true) { + $date = $date['hour']; + } + break; + // Fall through + case self::MINUTE: + case self::MINUTE_SHORT: + if (isset($date['minute']) === true) { + $date = $date['minute']; + } + break; + // Fall through + case self::SECOND: + case self::SECOND_SHORT: + if (isset($date['second']) === true) { + $date = $date['second']; + } + break; + // Fall through + case self::TIMEZONE: + case self::TIMEZONE_NAME: + if (isset($date['timezone']) === true) { + $date = $date['timezone']; + } + break; + case self::TIMESTAMP: + if (isset($date['timestamp']) === true) { + $date = $date['timestamp']; + } + break; + case self::WEEK: + if (isset($date['week']) === true) { + $date = $date['week']; + } + break; + case self::TIMEZONE_SECS: + if (isset($date['gmtsecs']) === true) { + $date = $date['gmtsecs']; + } + break; + default: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("datepart for part ($part) not found in array"); + break; + } + } else { + $hours = 0; + if (isset($date['hour']) === true) { + $hours = $date['hour']; + } + $minutes = 0; + if (isset($date['minute']) === true) { + $minutes = $date['minute']; + } + $seconds = 0; + if (isset($date['second']) === true) { + $seconds = $date['second']; + } + $months = 0; + if (isset($date['month']) === true) { + $months = $date['month']; + } + $days = 0; + if (isset($date['day']) === true) { + $days = $date['day']; + } + $years = 0; + if (isset($date['year']) === true) { + $years = $date['year']; + } + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, $months, $days, $years, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), $hour); + } + } + + // $date as object, part of foreign date as own date + switch($part) { + + // day formats + case self::DAY: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_SHORT: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 3, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + intval($day), 1970, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY: + $daylist = Zend_Locale_Data::getList($locale, 'day'); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_8601: + $weekday = (int) $this->toString(self::WEEKDAY_8601, 'iso', $locale); + if ((intval($date) > 0) and (intval($date) < 8)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + intval($date), 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_SUFFIX: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('day suffix not supported', 0, null, $date); + break; + + case self::WEEKDAY_DIGIT: + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + if (is_numeric($date) and (intval($date) >= 0) and (intval($date) < 7)) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $date, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::DAY_OF_YEAR: + if (is_numeric($date)) { + if (($calc == 'add') || ($calc == 'sub')) { + $year = 1970; + ++$date; + ++$day; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, $date, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, day expected", 0, null, $date); + break; + + case self::WEEKDAY_NARROW: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + case self::WEEKDAY_NAME: + $daylist = Zend_Locale_Data::getList($locale, 'day', array('gregorian', 'format', 'abbreviated')); + $weekday = (int) $this->toString(self::WEEKDAY_DIGIT, 'iso', $locale); + $cnt = 0; + foreach ($daylist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $cnt; + break; + } + ++$cnt; + } + + // Weekday found + if ($cnt < 7) { + return $this->_assign($calc, $this->mktime(0, 0, 0, 1, 1 + $found, 1970, true), + $this->mktime(0, 0, 0, 1, 1 + $weekday, 1970, true), $hour); + } + + // Weekday not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, weekday expected", 0, null, $date); + break; + + // week formats + case self::WEEK: + if (is_numeric($date)) { + $week = (int) $this->toString(self::WEEK, 'iso', $locale); + return $this->_assign($calc, parent::mktime(0, 0, 0, 1, 1 + ($date * 7), 1970, true), + parent::mktime(0, 0, 0, 1, 1 + ($week * 7), 1970, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, week expected", 0, null, $date); + break; + + // month formats + case self::MONTH_NAME: + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH: + if (is_numeric($date)) { + $fixday = 0; + if ($calc == 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] == false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_NAME_SHORT: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc == 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc == 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_SHORT: + if (is_numeric($date) === true) { + $fixday = 0; + if ($calc === 'add') { + $date += $month; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $date; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + case self::MONTH_DAYS: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('month days not supported', 0, null, $date); + break; + + case self::MONTH_NAME_NARROW: + $monthlist = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'stand-alone', 'narrow')); + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) === strtoupper($date)) { + $found = $key; + break; + } + ++$cnt; + } + $date = array_search($date, $monthlist); + + // Monthname found + if ($cnt < 12) { + $fixday = 0; + if ($calc === 'add') { + $date += $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } else if ($calc === 'sub') { + $date = $month - $found; + $calc = 'set'; + if (self::$_options['extend_month'] === false) { + $parts = $this->getDateParts($this->mktime($hour, $minute, $second, $date, $day, $year, false)); + if ($parts['mday'] != $day) { + $fixday = ($parts['mday'] < $day) ? -$parts['mday'] : ($parts['mday'] - $day); + } + } + } + return $this->_assign($calc, $this->mktime(0, 0, 0, $date, $day + $fixday, $year, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), $hour); + } + + // Monthname not found + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, month expected", 0, null, $date); + break; + + // year formats + case self::LEAPYEAR: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('leap year not supported', 0, null, $date); + break; + + case self::YEAR_8601: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR: + if (is_numeric($date)) { + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, intval($date), true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT: + if (is_numeric($date)) { + $date = intval($date); + if (($calc == 'set') || ($calc == 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + case self::YEAR_SHORT_8601: + if (is_numeric($date)) { + $date = intval($date); + if (($calc === 'set') || ($calc === 'cmp')) { + $date = self::getFullYear($date); + } + if ($calc === 'add') { + $date += $year; + $calc = 'set'; + } else if ($calc === 'sub') { + $date = $year - $date; + $calc = 'set'; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, $month, $day, $date, true), + $this->mktime(0, 0, 0, $month, $day, $year, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, year expected", 0, null, $date); + break; + + // time formats + case self::MERIDIEM: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('meridiem not supported', 0, null, $date); + break; + + case self::SWATCH: + if (is_numeric($date)) { + $rest = intval($date); + $hours = floor($rest * 24 / 1000); + $rest = $rest - ($hours * 1000 / 24); + $minutes = floor($rest * 1440 / 1000); + $rest = $rest - ($minutes * 1000 / 1440); + $seconds = floor($rest * 86400 / 1000); + return $this->_assign($calc, $this->mktime($hours, $minutes, $seconds, 1, 1, 1970, true), + $this->mktime($hour, $minute, $second, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, swatchstamp expected", 0, null, $date); + break; + + case self::HOUR_SHORT_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR_AM: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::HOUR: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(intval($date), 0, 0, 1, 1, 1970, true), + $this->mktime($hour, 0, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, hour expected", 0, null, $date); + break; + + case self::MINUTE: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + case self::MILLISECOND: + if (is_numeric($date)) { + switch($calc) { + case 'set' : + return $this->setMillisecond($date); + break; + case 'add' : + return $this->addMillisecond($date); + break; + case 'sub' : + return $this->subMillisecond($date); + break; + } + + return $this->compareMillisecond($date); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, milliseconds expected", 0, null, $date); + break; + + case self::MINUTE_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, intval($date), 0, 1, 1, 1970, true), + $this->mktime(0, $minute, 0, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, minute expected", 0, null, $date); + break; + + case self::SECOND_SHORT: + if (is_numeric($date)) { + return $this->_assign($calc, $this->mktime(0, 0, intval($date), 1, 1, 1970, true), + $this->mktime(0, 0, $second, 1, 1, 1970, true), false); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, second expected", 0, null, $date); + break; + + // timezone formats + // break intentionally omitted + case self::TIMEZONE_NAME: + case self::TIMEZONE: + case self::TIMEZONE_SECS: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('timezone not supported', 0, null, $date); + break; + + case self::DAYLIGHT: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('daylight not supported', 0, null, $date); + break; + + case self::GMT_DIFF: + case self::GMT_DIFF_SEP: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('gmtdiff not supported', 0, null, $date); + break; + + // date strings + case self::ISO_8601: + // (-)YYYY-MM-dd + preg_match('/^(-{0,1}\d{4})-(\d{2})-(\d{2})/', $date, $datematch); + // (-)YY-MM-dd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})-(\d{2})-(\d{2})/', $date, $datematch); + } + // (-)YYYYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{4})(\d{2})(\d{2})/', $date, $datematch); + } + // (-)YYMMdd + if (empty($datematch)) { + preg_match('/^(-{0,1}\d{2})(\d{2})(\d{2})/', $date, $datematch); + } + $tmpdate = $date; + if (!empty($datematch)) { + $dateMatchCharCount = iconv_strlen($datematch[0], 'UTF-8'); + $tmpdate = iconv_substr($date, + $dateMatchCharCount, + iconv_strlen($date, 'UTF-8') - $dateMatchCharCount, + 'UTF-8'); + } + // (T)hh:mm:ss + preg_match('/[T,\s]{0,1}(\d{2}):(\d{2}):(\d{2})/', $tmpdate, $timematch); + if (empty($timematch)) { + preg_match('/[T,\s]{0,1}(\d{2})(\d{2})(\d{2})/', $tmpdate, $timematch); + } + if (empty($datematch) and empty($timematch)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unsupported ISO8601 format ($date)", 0, null, $date); + } + if (!empty($timematch)) { + $timeMatchCharCount = iconv_strlen($timematch[0], 'UTF-8'); + $tmpdate = iconv_substr($tmpdate, + $timeMatchCharCount, + iconv_strlen($tmpdate, 'UTF-8') - $timeMatchCharCount, + 'UTF-8'); + } + if (empty($datematch)) { + $datematch[1] = 1970; + $datematch[2] = 1; + $datematch[3] = 1; + } else if (iconv_strlen($datematch[1], 'UTF-8') == 2) { + $datematch[1] = self::getFullYear($datematch[1]); + } + if (empty($timematch)) { + $timematch[1] = 0; + $timematch[2] = 0; + $timematch[3] = 0; + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$datematch[2]; + --$month; + --$datematch[3]; + --$day; + $datematch[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($timematch[1], $timematch[2], $timematch[3], 1 + $datematch[2], 1 + $datematch[3], 1970 + $datematch[1], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_2822: + $result = preg_match('/^\w{3},\s(\d{1,2})\s(\w{3})\s(\d{4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4})$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no RFC 2822 format ($date)", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::TIMESTAMP: + if (is_numeric($date)) { + return $this->_assign($calc, $date, $this->getUnixTimestamp()); + } + + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, timestamp expected", 0, null, $date); + break; + + // additional formats + // break intentionally omitted + case self::ERA: + case self::ERA_NAME: + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('era not supported', 0, null, $date); + break; + + case self::DATES: + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATE_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime(0, 0, 0, 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime(0, 0, 0, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIMES: + try { + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + $parsed = Zend_Locale_Format::getTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::TIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if ($calc != 'set') { + $month = 1; + $day = 1; + $year = 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], $month, $day, $year, true), + $this->mktime($hour, $minute, $second, $month, $day, $year, true), false); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME: + try { + $parsed = Zend_Locale_Format::getDateTime($date, array('locale' => $locale, 'format_type' => 'iso', 'fix_date' => true)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_FULL: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_LONG: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + if (($calc == 'set') || ($calc == 'cmp')){ + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_MEDIUM: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + case self::DATETIME_SHORT: + try { + $format = Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + $parsed = Zend_Locale_Format::getDateTime($date, array('date_format' => $format, 'format_type' => 'iso', 'locale' => $locale)); + + $parsed['year'] = self::getFullYear($parsed['year']); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$parsed['month']; + --$month; + --$parsed['day']; + --$day; + $parsed['year'] -= 1970; + $year -= 1970; + } + + if (!isset($parsed['second'])) { + $parsed['second'] = 0; + } + + return $this->_assign($calc, $this->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], 1 + $parsed['month'], 1 + $parsed['day'], 1970 + $parsed['year'], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), $hour); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + break; + + // ATOM and RFC_3339 are identical + case self::ATOM: + case self::RFC_3339: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\d{0,4}([+-]{1}\d{2}:\d{2}|Z)$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, ATOM format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::COOKIE: + $result = preg_match("/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,20}$/", $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, COOKIE format expected", 0, null, $date); + } + $matchStartPos = iconv_strpos($match[0], ' ', 0, 'UTF-8') + 1; + $match[0] = iconv_substr($match[0], + $matchStartPos, + iconv_strlen($match[0], 'UTF-8') - $matchStartPos, + 'UTF-8'); + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_822: + case self::RFC_1036: + // new RFC 822 format, identical to RFC 1036 standard + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 822 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], false), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, false), false); + break; + + case self::RFC_850: + $result = preg_match('/^\w{6,9},\s(\d{2})-(\w{3})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s.{3,21}$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 850 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RFC_1123: + $result = preg_match('/^\w{0,3},{0,1}\s{0,1}(\d{1,2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):{0,1}(\d{0,2})\s([+-]{1}\d{4}|\w{1,20})$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RFC 1123 date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::RSS: + $result = preg_match('/^\w{3},\s(\d{2})\s(\w{3})\s(\d{2,4})\s(\d{1,2}):(\d{2}):(\d{2})\s.{1,21}$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, RSS date format expected", 0, null, $date); + } + + $months = $this->_getDigitFromName($match[2]); + $match[3] = self::getFullYear($match[3]); + + if (($calc == 'set') || ($calc == 'cmp')) { + --$months; + --$month; + --$match[1]; + --$day; + $match[3] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $months, 1 + $match[1], 1970 + $match[3], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + case self::W3C: + $result = preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})[+-]{1}\d{2}:\d{2}$/', $date, $match); + if (!$result) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid date ($date) operand, W3C date format expected", 0, null, $date); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + --$match[2]; + --$month; + --$match[3]; + --$day; + $match[1] -= 1970; + $year -= 1970; + } + return $this->_assign($calc, $this->mktime($match[4], $match[5], $match[6], 1 + $match[2], 1 + $match[3], 1970 + $match[1], true), + $this->mktime($hour, $minute, $second, 1 + $month, 1 + $day, 1970 + $year, true), false); + break; + + default: + if (!is_numeric($date) || !empty($part)) { + try { + if (empty($part)) { + $part = Zend_Locale_Format::getDateFormat($locale) . " "; + $part .= Zend_Locale_Format::getTimeFormat($locale); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $part, 'locale' => $locale, 'fix_date' => true, 'format_type' => 'iso')); + if ((strpos(strtoupper($part), 'YY') !== false) and (strpos(strtoupper($part), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + + if (($calc == 'set') || ($calc == 'cmp')) { + if (isset($parsed['month'])) { + --$parsed['month']; + } else { + $parsed['month'] = 0; + } + + if (isset($parsed['day'])) { + --$parsed['day']; + } else { + $parsed['day'] = 0; + } + + if (isset($parsed['year'])) { + $parsed['year'] -= 1970; + } else { + $parsed['year'] = 0; + } + } + + return $this->_assign($calc, $this->mktime( + isset($parsed['hour']) ? $parsed['hour'] : 0, + isset($parsed['minute']) ? $parsed['minute'] : 0, + isset($parsed['second']) ? $parsed['second'] : 0, + isset($parsed['month']) ? (1 + $parsed['month']) : 1, + isset($parsed['day']) ? (1 + $parsed['day']) : 1, + isset($parsed['year']) ? (1970 + $parsed['year']) : 1970, + false), $this->getUnixTimestamp(), false); + } catch (Zend_Locale_Exception $e) { + if (!is_numeric($date)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e, $date); + } + } + } + + return $this->_assign($calc, $date, $this->getUnixTimestamp(), false); + break; + } + } + + /** + * Returns true when both date objects or date parts are equal. + * For example: + * 15.May.2000 <-> 15.June.2000 Equals only for Day or Year... all other will return false + * + * @param string|integer|array|Zend_Date $date Date or datepart to equal with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function equals($date, $part = self::TIMESTAMP, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 0) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is earlier + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for day, year and date, but not for month + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isEarlier($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == -1) { + return true; + } + + return false; + } + + /** + * Returns if the given date or datepart is later + * For example: + * 15.May.2000 <-> 13.June.1999 will return true for month but false for day, year and date + * Returns if the given date is later + * + * @param string|integer|array|Zend_Date $date Date or datepart to compare with + * @param string $part OPTIONAL Part of the date to compare, if null the timestamp is used + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return boolean + * @throws Zend_Date_Exception + */ + public function isLater($date, $part = null, $locale = null) + { + $result = $this->compare($date, $part, $locale); + + if ($result == 1) { + return true; + } + + return false; + } + + /** + * Returns only the time of the date as new Zend_Date object + * For example: + * 15.May.2000 10:11:23 will return a dateobject equal to 01.Jan.1970 10:11:23 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getTime($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'H:i:s'; + } else { + $format = self::TIME_MEDIUM; + } + + return $this->copyPart($format, $locale); + } + + /** + * Returns the calculated time + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $time Time to calculate with, if null the actual time is taken + * @param string $format Timeformat for parsing input + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _time($calc, $time, $format, $locale) + { + if ($time === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $time must be set, null is not allowed'); + } + + if ($time instanceof Zend_Date) { + // extract time from object + $time = $time->toString('HH:mm:ss', 'iso'); + } else { + if (is_array($time)) { + if ((isset($time['hour']) === true) or (isset($time['minute']) === true) or + (isset($time['second']) === true)) { + $parsed = $time; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no hour, minute or second given in array"); + } + } else { + if (self::$_options['format_type'] == 'php') { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getTime($time, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('hour', $parsed)) { + $parsed['hour'] = 0; + } + + if (!array_key_exists('minute', $parsed)) { + $parsed['minute'] = 0; + } + + if (!array_key_exists('second', $parsed)) { + $parsed['second'] = 0; + } + + $time = str_pad($parsed['hour'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['minute'], 2, '0', STR_PAD_LEFT) . ":"; + $time .= str_pad($parsed['second'], 2, '0', STR_PAD_LEFT); + } + + $return = $this->_calcdetail($calc, $time, self::TIMES, 'de'); + if ($calc != 'cmp') { + return $this; + } + + return $return; + } + + + /** + * Sets a new time for the date object. Format defines how to parse the time string. + * Also a complete date can be given, but only the time is used for setting. + * For example: dd.MMMM.yyTHH:mm' and 'ss sec'-> 10.May.07T25:11 and 44 sec => 1h11min44sec + 1 day + * Returned is the new date object and the existing date is left as it was before + * + * @param string|integer|array|Zend_Date $time Time to set + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setTime($time, $format = null, $locale = null) + { + return $this->_time('set', $time, $format, $locale); + } + + + /** + * Adds a time to the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> +10 hours + * + * @param string|integer|array|Zend_Date $time Time to add + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addTime($time, $format = null, $locale = null) + { + return $this->_time('add', $time, $format, $locale); + } + + + /** + * Subtracts a time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> -10 hours + * + * @param string|integer|array|Zend_Date $time Time to sub + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid inteface + * @throws Zend_Date_Exception + */ + public function subTime($time, $format = null, $locale = null) + { + return $this->_time('sub', $time, $format, $locale); + } + + + /** + * Compares the time from the existing date. Format defines how to parse the time string. + * If only parts are given the other parts are set to default. + * If no format us given, the standardformat of this locale is used. + * For example: HH:mm:ss -> 10 -> 10 hours + * + * @param string|integer|array|Zend_Date $time Time to compare + * @param string $format OPTIONAL Timeformat for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareTime($time, $format = null, $locale = null) + { + return $this->_time('cmp', $time, $format, $locale); + } + + /** + * Returns a clone of $this, with the time part set to 00:00:00. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDate($locale = null) + { + $orig = self::$_options['format_type']; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + } + + $date = $this->copyPart(self::DATE_MEDIUM, $locale); + $date->addTimestamp($this->getGmtOffset()); + self::$_options['format_type'] = $orig; + + return $date; + } + + /** + * Returns the calculated date + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date to calculate with, if null the actual date is taken + * @param string $format Date format for parsing + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _date($calc, $date, $format, $locale) + { + if ($date === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $date must be set, null is not allowed'); + } + + if ($date instanceof Zend_Date) { + // extract date from object + $date = $date->toString('d.M.y', 'iso'); + } else { + if (is_array($date)) { + if ((isset($date['year']) === true) or (isset($date['month']) === true) or + (isset($date['day']) === true)) { + $parsed = $date; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day,month or year given in array"); + } + } else { + if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + try { + if ($locale === null) { + $locale = $this->getLocale(); + } + + $parsed = Zend_Locale_Format::getDate($date, array('date_format' => $format, 'locale' => $locale, 'format_type' => 'iso')); + if ((strpos(strtoupper($format), 'YY') !== false) and (strpos(strtoupper($format), 'YYYY') === false)) { + $parsed['year'] = self::getFullYear($parsed['year']); + } + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + } + + if (!array_key_exists('day', $parsed)) { + $parsed['day'] = 1; + } + + if (!array_key_exists('month', $parsed)) { + $parsed['month'] = 1; + } + + if (!array_key_exists('year', $parsed)) { + $parsed['year'] = 0; + } + + $date = $parsed['day'] . "." . $parsed['month'] . "." . $parsed['year']; + } + + $return = $this->_calcdetail($calc, $date, self::DATE_MEDIUM, 'de'); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new date for the date object. Format defines how to parse the date string. + * Also a complete date with time can be given, but only the date is used for setting. + * For example: MMMM.yy HH:mm-> May.07 22:11 => 01.May.07 00:00 + * Returned is the new date object and the existing time is left as it was before + * + * @param string|integer|array|Zend_Date $date Date to set + * @param string $format OPTIONAL Date format for parsing + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDate($date, $format = null, $locale = null) + { + return $this->_date('set', $date, $format, $locale); + } + + + /** + * Adds a date to the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> +10 months + * + * @param string|integer|array|Zend_Date $date Date to add + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDate($date, $format = null, $locale = null) + { + return $this->_date('add', $date, $format, $locale); + } + + + /** + * Subtracts a date from the existing date object. Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: MM.dd.YYYY -> 10 -> -10 months + * Be aware: Subtracting 2 months is not equal to Adding -2 months !!! + * + * @param string|integer|array|Zend_Date $date Date to sub + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDate($date, $format = null, $locale = null) + { + return $this->_date('sub', $date, $format, $locale); + } + + + /** + * Compares the date from the existing date object, ignoring the time. + * Format defines how to parse the date string. + * If only parts are given the other parts are set to 0. + * If no format is given, the standardformat of this locale is used. + * For example: 10.01.2000 => 10.02.1999 -> false + * + * @param string|integer|array|Zend_Date $date Date to compare + * @param string $format OPTIONAL Date format for parsing input + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDate($date, $format = null, $locale = null) + { + return $this->_date('cmp', $date, $format, $locale); + } + + + /** + * Returns the full ISO 8601 date from the date object. + * Always the complete ISO 8601 specifiction is used. If an other ISO date is needed + * (ISO 8601 defines several formats) use toString() instead. + * This function does not return the ISO date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getIso($locale = null) + { + return $this->toString(self::ISO_8601, 'iso', $locale); + } + + + /** + * Sets a new date for the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> 01.Sept.2005 00:00:00, 20050201T10:00:30 -> 01.Feb.2005 10h00m30s + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setIso($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Adds a ISO date to the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> + 01.Sept.2005 00:00:00, 10:00:00 -> +10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addIso($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Subtracts a ISO date from the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returned is the new date object + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subIso($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Compares a ISO date with the date object. Not given parts are set to default. + * Only supported ISO 8601 formats are accepted. + * For example: 050901 -> - 01.Sept.2005 00:00:00, 10:00:00 -> -10h + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date ISO Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareIso($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'iso', self::ISO_8601, $locale); + } + + + /** + * Returns a RFC 822 compilant datestring from the date object. + * This function does not return the RFC date as object. Use copy() instead. + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return string + */ + public function getArpa($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D\, d M y H\:i\:s O'; + } else { + $format = self::RFC_822; + } + + return $this->toString($format, 'iso', $locale); + } + + + /** + * Sets a RFC 822 date as new date for the date object. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setArpa($date, $locale = null) + { + return $this->_calcvalue('set', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Adds a RFC 822 date to the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addArpa($date, $locale = null) + { + return $this->_calcvalue('add', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Subtracts a RFC 822 date from the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returned is the new date object + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subArpa($date, $locale = null) + { + return $this->_calcvalue('sub', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Compares a RFC 822 compilant date with the date object. + * ARPA messages are used in emails or HTTP Headers. + * Only RFC 822 compilant date strings are accepted. + * For example: Sat, 14 Feb 09 00:31:30 +0100 + * Returns if equal, earlier or later + * + * @param string|integer|Zend_Date $date RFC 822 Date to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareArpa($date, $locale = null) + { + return $this->_calcvalue('cmp', $date, 'arpa', self::RFC_822, $locale); + } + + + /** + * Check if location is supported + * + * @param $location array - locations array + * @return $horizon float + */ + private function _checkLocation($location) + { + if (!isset($location['longitude']) or !isset($location['latitude'])) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Location must include \'longitude\' and \'latitude\'', 0, null, $location); + } + if (($location['longitude'] > 180) or ($location['longitude'] < -180)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Longitude must be between -180 and 180', 0, null, $location); + } + if (($location['latitude'] > 90) or ($location['latitude'] < -90)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('Latitude must be between -90 and 90', 0, null, $location); + } + + if (!isset($location['horizon'])){ + $location['horizon'] = 'effective'; + } + + switch ($location['horizon']) { + case 'civil' : + return -0.104528; + break; + case 'nautic' : + return -0.207912; + break; + case 'astronomic' : + return -0.309017; + break; + default : + return -0.0145439; + break; + } + } + + + /** + * Returns the time of sunrise for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of sunrise + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunrise($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + return $result; + } + + + /** + * Returns the time of sunset for this date and a given location as new date object + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of sunset + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return Zend_Date + * @throws Zend_Date_Exception + */ + public function getSunset($location) + { + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + return $result; + } + + + /** + * Returns an array with the sunset and sunrise dates for all horizon types + * For a list of cities and correct locations use the class Zend_Date_Cities + * + * @param $location array - location of suninfo + * ['horizon'] -> civil, nautic, astronomical, effective (default) + * ['longitude'] -> longitude of location + * ['latitude'] -> latitude of location + * @return array - [sunset|sunrise][effective|civil|nautic|astronomic] + * @throws Zend_Date_Exception + */ + public function getSunInfo($location) + { + $suninfo = array(); + for ($i = 0; $i < 4; ++$i) { + switch ($i) { + case 0 : + $location['horizon'] = 'effective'; + break; + case 1 : + $location['horizon'] = 'civil'; + break; + case 2 : + $location['horizon'] = 'nautic'; + break; + case 3 : + $location['horizon'] = 'astronomic'; + break; + } + $horizon = $this->_checkLocation($location); + $result = clone $this; + $result->set($this->calcSun($location, $horizon, true), self::TIMESTAMP); + $suninfo['sunrise'][$location['horizon']] = $result; + $result = clone $this; + $result->set($this->calcSun($location, $horizon, false), self::TIMESTAMP); + $suninfo['sunset'][$location['horizon']] = $result; + } + return $suninfo; + } + + + /** + * Check a given year for leap year. + * + * @param integer|array|Zend_Date $year Year to check + * @return boolean + */ + public static function checkLeapYear($year) + { + if ($year instanceof Zend_Date) { + $year = (int) $year->toString(self::YEAR, 'iso'); + } + + if (is_array($year)) { + if (isset($year['year']) === true) { + $year = $year['year']; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no year given in array"); + } + } + + if (!is_numeric($year)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("year ($year) has to be integer for checkLeapYear()", 0, null, $year); + } + + return (bool) parent::isYearLeapYear($year); + } + + + /** + * Returns true, if the year is a leap year. + * + * @return boolean + */ + public function isLeapYear() + { + return self::checkLeapYear($this); + } + + + /** + * Returns if the set date is todays date + * + * @return boolean + */ + public function isToday() + { + $today = $this->date('Ymd', $this->_getTime()); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return ($today == $day); + } + + + /** + * Returns if the set date is yesterdays date + * + * @return boolean + */ + public function isYesterday() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $yesterday = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day -1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $yesterday; + } + + + /** + * Returns if the set date is tomorrows date + * + * @return boolean + */ + public function isTomorrow() + { + list($year, $month, $day) = explode('-', $this->date('Y-m-d', $this->_getTime())); + // adjusts for leap days and DST changes that are timezone specific + $tomorrow = $this->date('Ymd', $this->mktime(0, 0, 0, $month, $day +1, $year)); + $day = $this->date('Ymd', $this->getUnixTimestamp()); + return $day == $tomorrow; + } + + /** + * Returns the actual date as new date object + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public static function now($locale = null) + { + return new Zend_Date(time(), self::TIMESTAMP, $locale); + } + + /** + * Calculate date details + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $date Date or Part to calculate + * @param string $part Datepart for Calculation + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|string new date + * @throws Zend_Date_Exception + */ + private function _calcdetail($calc, $date, $type, $locale) + { + $old = false; + if (self::$_options['format_type'] == 'php') { + self::$_options['format_type'] = 'iso'; + $old = true; + } + + switch($calc) { + case 'set' : + $return = $this->set($date, $type, $locale); + break; + case 'add' : + $return = $this->add($date, $type, $locale); + break; + case 'sub' : + $return = $this->sub($date, $type, $locale); + break; + default : + $return = $this->compare($date, $type, $locale); + break; + } + + if ($old) { + self::$_options['format_type'] = 'php'; + } + + return $return; + } + + /** + * Internal calculation, returns the requested date type + * + * @param string $calc Calculation to make + * @param string|integer|Zend_Date $value Datevalue to calculate with, if null the actual value is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new date + * @throws Zend_Date_Exception + */ + private function _calcvalue($calc, $value, $type, $parameter, $locale) + { + if ($value === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("parameter $type must be set, null is not allowed"); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($value instanceof Zend_Date) { + // extract value from object + $value = $value->toString($parameter, 'iso', $locale); + } else if (!is_array($value) && !is_numeric($value) && ($type != 'iso') && ($type != 'arpa')) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid $type ($value) operand", 0, null, $value); + } + + $return = $this->_calcdetail($calc, $value, $parameter, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Returns only the year from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.Jan.2000 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'Y'; + } else { + $format = self::YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new year + * If the year is between 0 and 69, 2000 will be set (2000-2069) + * If the year if between 70 and 99, 1999 will be set (1970-1999) + * 3 or 4 digit years are set as expected. If you need to set year 0-99 + * use set() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setYear($year, $locale = null) + { + return $this->_calcvalue('set', $year, 'year', self::YEAR, $locale); + } + + + /** + * Adds the year to the existing date object + * If the year is between 0 and 69, 2000 will be added (2000-2069) + * If the year if between 70 and 99, 1999 will be added (1970-1999) + * 3 or 4 digit years are added as expected. If you need to add years from 0-99 + * use add() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addYear($year, $locale = null) + { + return $this->_calcvalue('add', $year, 'year', self::YEAR, $locale); + } + + + /** + * Subs the year from the existing date object + * If the year is between 0 and 69, 2000 will be subtracted (2000-2069) + * If the year if between 70 and 99, 1999 will be subtracted (1970-1999) + * 3 or 4 digit years are subtracted as expected. If you need to subtract years from 0-99 + * use sub() instead. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $date Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subYear($year, $locale = null) + { + return $this->_calcvalue('sub', $year, 'year', self::YEAR, $locale); + } + + + /** + * Compares the year with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.02.2000 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $year Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareYear($year, $locale = null) + { + return $this->_calcvalue('cmp', $year, 'year', self::YEAR, $locale); + } + + + /** + * Returns only the month from the date object as new object. + * For example: 10.May.2000 10:30:00 -> 01.May.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMonth($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'm'; + } else { + $format = self::MONTH; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated month + * + * @param string $calc Calculation to make + * @param string|integer|array|Zend_Date $month Month to calculate with, if null the actual month is taken + * @param string|Zend_Locale $locale Locale for parsing input + * @return integer|Zend_Date new time + * @throws Zend_Date_Exception + */ + private function _month($calc, $month, $locale) + { + if ($month === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $month must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($month instanceof Zend_Date) { + // extract month from object + $found = $month->toString(self::MONTH_SHORT, 'iso', $locale); + } else { + if (is_numeric($month)) { + $found = $month; + } else if (is_array($month)) { + if (isset($month['month']) === true) { + $month = $month['month']; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no month given in array"); + } + } else { + $monthlist = Zend_Locale_Data::getList($locale, 'month'); + $monthlist2 = Zend_Locale_Data::getList($locale, 'month', array('gregorian', 'format', 'abbreviated')); + + $monthlist = array_merge($monthlist, $monthlist2); + $found = 0; + $cnt = 0; + foreach ($monthlist as $key => $value) { + if (strtoupper($value) == strtoupper($month)) { + $found = ($key % 12) + 1; + break; + } + ++$cnt; + } + if ($found == 0) { + foreach ($monthlist2 as $key => $value) { + if (strtoupper(iconv_substr($value, 0, 1, 'UTF-8')) == strtoupper($month)) { + $found = $key + 1; + break; + } + ++$cnt; + } + } + if ($found == 0) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("unknown month name ($month)", 0, null, $month); + } + } + } + $return = $this->_calcdetail($calc, $found, self::MONTH_SHORT, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new month + * The month can be a number or a string. Setting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMonth($month, $locale = null) + { + return $this->_month('set', $month, $locale); + } + + + /** + * Adds months to the existing date object. + * The month can be a number or a string. Adding months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMonth($month, $locale = null) + { + return $this->_month('add', $month, $locale); + } + + + /** + * Subtracts months from the existing date object. + * The month can be a number or a string. Subtracting months lower then 0 and greater then 12 + * will result in adding or subtracting the relevant year. (12 months equal one year) + * If a localized monthname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * + * @param string|integer|array|Zend_Date $month Month to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMonth($month, $locale = null) + { + return $this->_month('sub', $month, $locale); + } + + + /** + * Compares the month with the existing date object, ignoring other date parts. + * For example: 10.03.2000 -> 15.03.1950 -> true + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $month Month to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMonth($month, $locale = null) + { + return $this->_month('cmp', $month, $locale); + } + + + /** + * Returns the day as new date object + * Example: 20.May.1986 -> 20.Jan.1970 00:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDay($locale = null) + { + return $this->copyPart(self::DAY_SHORT, $locale); + } + + + /** + * Returns the calculated day + * + * @param $calc string Type of calculation to make + * @param $day string|integer|Zend_Date Day to calculate, when null the actual day is calculated + * @param $locale string|Zend_Locale Locale for parsing input + * @return Zend_Date|integer + */ + private function _day($calc, $day, $locale) + { + if ($day === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $day must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($day instanceof Zend_Date) { + $day = $day->toString(self::DAY_SHORT, 'iso', $locale); + } + + if (is_numeric($day)) { + $type = self::DAY_SHORT; + } else if (is_array($day)) { + if (isset($day['day']) === true) { + $day = $day['day']; + $type = self::WEEKDAY; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no day given in array"); + } + } else { + switch (iconv_strlen($day, 'UTF-8')) { + case 1 : + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $day, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new day + * The day can be a number or a string. Setting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: setDay('Montag', 'de_AT'); will set the monday of this week as day. + * + * @param string|integer|array|Zend_Date $month Day to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDay($day, $locale = null) + { + return $this->_day('set', $day, $locale); + } + + + /** + * Adds days to the existing date object. + * The day can be a number or a string. Adding days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDay($day, $locale = null) + { + return $this->_day('add', $day, $locale); + } + + + /** + * Subtracts days from the existing date object. + * The day can be a number or a string. Subtracting days lower then 0 or greater than the number of this months days + * will result in adding or subtracting the relevant month. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * + * @param string|integer|array|Zend_Date $month Day to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDay($day, $locale = null) + { + return $this->_day('sub', $day, $locale); + } + + + /** + * Compares the day with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDay($day, $locale = null) + { + return $this->_day('cmp', $day, $locale); + } + + + /** + * Returns the weekday as new date object + * Weekday is always from 1-7 + * Example: 09-Jan-2007 -> 2 = Tuesday -> 02-Jan-1970 (when 02.01.1970 is also Tuesday) + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeekday($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'l'; + } else { + $format = self::WEEKDAY; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Returns the calculated weekday + * + * @param $calc string Type of calculation to make + * @param $weekday string|integer|array|Zend_Date Weekday to calculate, when null the actual weekday is calculated + * @param $locale string|Zend_Locale Locale for parsing input + * @return Zend_Date|integer + * @throws Zend_Date_Exception + */ + private function _weekday($calc, $weekday, $locale) + { + if ($weekday === null) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('parameter $weekday must be set, null is not allowed'); + } + + if ($locale === null) { + $locale = $this->getLocale(); + } + + if ($weekday instanceof Zend_Date) { + $weekday = $weekday->toString(self::WEEKDAY_8601, 'iso', $locale); + } + + if (is_numeric($weekday)) { + $type = self::WEEKDAY_8601; + } else if (is_array($weekday)) { + if (isset($weekday['weekday']) === true) { + $weekday = $weekday['weekday']; + $type = self::WEEKDAY; + } else { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("no weekday given in array"); + } + } else { + switch(iconv_strlen($weekday, 'UTF-8')) { + case 1: + $type = self::WEEKDAY_NARROW; + break; + case 2: + $type = self::WEEKDAY_NAME; + break; + case 3: + $type = self::WEEKDAY_SHORT; + break; + default: + $type = self::WEEKDAY; + break; + } + } + $return = $this->_calcdetail($calc, $weekday, $type, $locale); + if ($calc != 'cmp') { + return $this; + } + return $return; + } + + + /** + * Sets a new weekday + * The weekday can be a number or a string. If a localized weekday name is given, + * then it will be parsed as a date in $locale (defaults to the same locale as $this). + * Returned is the new date object. + * Example: setWeekday(3); will set the wednesday of this week as day. + * + * @param string|integer|array|Zend_Date $month Weekday to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeekday($weekday, $locale = null) + { + return $this->_weekday('set', $weekday, $locale); + } + + + /** + * Adds weekdays to the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: addWeekday(3); will add the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeekday($weekday, $locale = null) + { + return $this->_weekday('add', $weekday, $locale); + } + + + /** + * Subtracts weekdays from the existing date object. + * The weekday can be a number or a string. + * If a localized dayname is given it will be parsed with the default locale or the optional + * set locale. + * Returned is the new date object + * Example: subWeekday(3); will subtract the difference of days from the begining of the month until + * wednesday. + * + * @param string|integer|array|Zend_Date $month Weekday to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeekday($weekday, $locale = null) + { + return $this->_weekday('sub', $weekday, $locale); + } + + + /** + * Compares the weekday with the existing date object, ignoring other date parts. + * For example: 'Monday', 'en' -> 08.Jan.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $weekday Weekday to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareWeekday($weekday, $locale = null) + { + return $this->_weekday('cmp', $weekday, $locale); + } + + + /** + * Returns the day of year as new date object + * Example: 02.Feb.1986 10:00:00 -> 02.Feb.1970 00:00:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getDayOfYear($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'D'; + } else { + $format = self::DAY_OF_YEAR; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new day of year + * The day of year is always a number. + * Returned is the new date object + * Example: 04.May.2004 -> setDayOfYear(10) -> 10.Jan.2004 + * + * @param string|integer|array|Zend_Date $day Day of Year to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setDayOfYear($day, $locale = null) + { + return $this->_calcvalue('set', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Adds a day of year to the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: addDayOfYear(10); will add 10 days to the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addDayOfYear($day, $locale = null) + { + return $this->_calcvalue('add', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Subtracts a day of year from the existing date object. + * The day of year is always a number. + * Returned is the new date object + * Example: subDayOfYear(10); will subtract 10 days from the existing date object. + * + * @param string|integer|array|Zend_Date $day Day of Year to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subDayOfYear($day, $locale = null) + { + return $this->_calcvalue('sub', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Compares the day of year with the existing date object. + * For example: compareDayOfYear(33) -> 02.Feb.2007 -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $day Day of Year to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareDayOfYear($day, $locale = null) + { + return $this->_calcvalue('cmp', $day, 'day of year', self::DAY_OF_YEAR, $locale); + } + + + /** + * Returns the hour as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 10:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getHour($locale = null) + { + return $this->copyPart(self::HOUR, $locale); + } + + + /** + * Sets a new hour + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setHour(7); -> 04.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setHour($hour, $locale = null) + { + return $this->_calcvalue('set', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Adds hours to the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addHour(12); -> 05.May.1993 01:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addHour($hour, $locale = null) + { + return $this->_calcvalue('add', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Subtracts hours from the existing date object. + * The hour is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subHour(6); -> 05.May.1993 07:07:25 + * + * @param string|integer|array|Zend_Date $hour Hour to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subHour($hour, $locale = null) + { + return $this->_calcvalue('sub', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Compares the hour with the existing date object. + * For example: 10:30:25 -> compareHour(10) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $hour Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareHour($hour, $locale = null) + { + return $this->_calcvalue('cmp', $hour, 'hour', self::HOUR_SHORT, $locale); + } + + + /** + * Returns the minute as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:30:00 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getMinute($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'i'; + } else { + $format = self::MINUTE; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets a new minute + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setMinute(29); -> 04.May.1993 13:29:25 + * + * @param string|integer|array|Zend_Date $minute Minute to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setMinute($minute, $locale = null) + { + return $this->_calcvalue('set', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Adds minutes to the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addMinute(65); -> 04.May.1993 13:12:25 + * + * @param string|integer|array|Zend_Date $minute Minute to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addMinute($minute, $locale = null) + { + return $this->_calcvalue('add', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Subtracts minutes from the existing date object. + * The minute is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subMinute(9); -> 04.May.1993 12:58:25 + * + * @param string|integer|array|Zend_Date $minute Minute to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subMinute($minute, $locale = null) + { + return $this->_calcvalue('sub', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Compares the minute with the existing date object. + * For example: 10:30:25 -> compareMinute(30) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $minute Hour to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareMinute($minute, $locale = null) + { + return $this->_calcvalue('cmp', $minute, 'minute', self::MINUTE_SHORT, $locale); + } + + + /** + * Returns the second as new date object + * Example: 02.Feb.1986 10:30:25 -> 01.Jan.1970 00:00:25 + * + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getSecond($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 's'; + } else { + $format = self::SECOND; + } + + return $this->copyPart($format, $locale); + } + + + /** + * Sets new seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> setSecond(100); -> 04.May.1993 13:08:40 + * + * @param string|integer|array|Zend_Date $second Second to set + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setSecond($second, $locale = null) + { + return $this->_calcvalue('set', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Adds seconds to the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> addSecond(65); -> 04.May.1993 13:08:30 + * + * @param string|integer|array|Zend_Date $second Second to add + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addSecond($second, $locale = null) + { + return $this->_calcvalue('add', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Subtracts seconds from the existing date object. + * The second is always a number. + * Returned is the new date object + * Example: 04.May.1993 13:07:25 -> subSecond(10); -> 04.May.1993 13:07:15 + * + * @param string|integer|array|Zend_Date $second Second to sub + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subSecond($second, $locale = null) + { + return $this->_calcvalue('sub', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Compares the second with the existing date object. + * For example: 10:30:25 -> compareSecond(25) -> 0 + * Returns if equal, earlier or later + * + * @param string|integer|array|Zend_Date $second Second to compare + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + * @throws Zend_Date_Exception + */ + public function compareSecond($second, $locale = null) + { + return $this->_calcvalue('cmp', $second, 'second', self::SECOND_SHORT, $locale); + } + + + /** + * Returns the precision for fractional seconds + * + * @return integer + */ + public function getFractionalPrecision() + { + return $this->_precision; + } + + + /** + * Sets a new precision for fractional seconds + * + * @param integer $precision Precision for the fractional datepart 3 = milliseconds + * @throws Zend_Date_Exception + * @return Zend_Date Provides fluid interface + */ + public function setFractionalPrecision($precision) + { + if (!intval($precision) or ($precision < 0) or ($precision > 9)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_precision = (int) $precision; + if ($this->_precision < strlen($this->_fractional)) { + $this->_fractional = substr($this->_fractional, 0, $this->_precision); + } else { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_RIGHT); + } + + return $this; + } + + + /** + * Returns the milliseconds of the date object + * + * @return string + */ + public function getMilliSecond() + { + return $this->_fractional; + } + + + /** + * Sets new milliseconds for the date object + * Example: setMilliSecond(550, 2) -> equals +5 Sec +50 MilliSec + * + * @param integer|Zend_Date $milli (Optional) Millisecond to set, when null the actual millisecond is set + * @param integer $precision (Optional) Fraction precision of the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function setMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + $precision = 6; + } else if (!is_numeric($milli)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = $this->_precision; + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional = 0; + $this->addMilliSecond($milli, $precision); + return $this; + } + + + /** + * Adds milliseconds to the date object + * + * @param integer|Zend_Date $milli (Optional) Millisecond to add, when null the actual millisecond is added + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function addMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (!is_numeric($milli)) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + if ($milli < 0) { + --$precision; + } + } + + if (!is_int($precision) || $precision < 1 || $precision > 9) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + $this->_fractional += $milli; + + // Add/sub milliseconds + add/sub seconds + $max = pow(10, $this->_precision); + // Milli includes seconds + if ($this->_fractional >= $max) { + while ($this->_fractional >= $max) { + $this->addSecond(1); + $this->_fractional -= $max; + } + } + + if ($this->_fractional < 0) { + while ($this->_fractional < 0) { + $this->subSecond(1); + $this->_fractional += $max; + } + } + + if ($this->_precision > strlen($this->_fractional)) { + $this->_fractional = str_pad($this->_fractional, $this->_precision, '0', STR_PAD_LEFT); + } + + return $this; + } + + + /** + * Subtracts a millisecond + * + * @param integer|Zend_Date $milli (Optional) Millisecond to sub, when null the actual millisecond is subtracted + * @param integer $precision (Optional) Fractional precision for the given milliseconds + * @return Zend_Date Provides fluid interface + */ + public function subMilliSecond($milli = null, $precision = null) + { + $this->addMilliSecond(0 - $milli, $precision); + return $this; + } + + /** + * Compares only the millisecond part, returning the difference + * + * @param integer|Zend_Date $milli OPTIONAL Millisecond to compare, when null the actual millisecond is compared + * @param integer $precision OPTIONAL Fractional precision for the given milliseconds + * @throws Zend_Date_Exception On invalid input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareMilliSecond($milli = null, $precision = null) + { + if ($milli === null) { + list($milli, $time) = explode(" ", microtime()); + $milli = intval($milli); + } else if (is_numeric($milli) === false) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("invalid milli second ($milli) operand", 0, null, $milli); + } + + if ($precision === null) { + $precision = strlen($milli); + } else if (!is_int($precision) || $precision < 1 || $precision > 9) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception("precision ($precision) must be a positive integer less than 10", 0, null, $precision); + } + + if ($precision === 0) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception('precision is 0'); + } + + if ($precision != $this->_precision) { + if ($precision > $this->_precision) { + $diff = $precision - $this->_precision; + $milli = (int) ($milli / (10 * $diff)); + } else { + $diff = $this->_precision - $precision; + $milli = (int) ($milli * (10 * $diff)); + } + } + + $comp = $this->_fractional - $milli; + if ($comp < 0) { + return -1; + } else if ($comp > 0) { + return 1; + } + return 0; + } + + /** + * Returns the week as new date object using monday as begining of the week + * Example: 12.Jan.2007 -> 08.Jan.1970 00:00:00 + * + * @param $locale string|Zend_Locale OPTIONAL Locale for parsing input + * @return Zend_Date + */ + public function getWeek($locale = null) + { + if (self::$_options['format_type'] == 'php') { + $format = 'W'; + } else { + $format = self::WEEK; + } + + return $this->copyPart($format, $locale); + } + + /** + * Sets a new week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> setWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to set + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function setWeek($week, $locale = null) + { + return $this->_calcvalue('set', $week, 'week', self::WEEK, $locale); + } + + /** + * Adds a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> addWeek(1); -> 16.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to add + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function addWeek($week, $locale = null) + { + return $this->_calcvalue('add', $week, 'week', self::WEEK, $locale); + } + + /** + * Subtracts a week. The week is always a number. The day of week is not changed. + * Returned is the new date object + * Example: 09.Jan.2007 13:07:25 -> subWeek(1); -> 02.Jan.2007 13:07:25 + * + * @param string|integer|array|Zend_Date $week Week to sub + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return Zend_Date Provides fluid interface + * @throws Zend_Date_Exception + */ + public function subWeek($week, $locale = null) + { + return $this->_calcvalue('sub', $week, 'week', self::WEEK, $locale); + } + + /** + * Compares only the week part, returning the difference + * Returned is the new date object + * Returns if equal, earlier or later + * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0 + * + * @param string|integer|array|Zend_Date $week Week to compare + * @param string|Zend_Locale $locale OPTIONAL Locale for parsing input + * @return integer 0 = equal, 1 = later, -1 = earlier + */ + public function compareWeek($week, $locale = null) + { + return $this->_calcvalue('cmp', $week, 'week', self::WEEK, $locale); + } + + /** + * Sets a new standard locale for the date object. + * This locale will be used for all functions + * Returned is the really set locale. + * Example: 'de_XX' will be set to 'de' because 'de_XX' does not exist + * 'xx_YY' will be set to 'root' because 'xx' does not exist + * + * @param string|Zend_Locale $locale (Optional) Locale for parsing input + * @throws Zend_Date_Exception When the given locale does not exist + * @return Zend_Date Provides fluent interface + */ + public function setLocale($locale = null) + { + try { + $this->_locale = Zend_Locale::findLocale($locale); + } catch (Zend_Locale_Exception $e) { + #require_once 'Zend/Date/Exception.php'; + throw new Zend_Date_Exception($e->getMessage(), 0, $e); + } + + return $this; + } + + /** + * Returns the actual set locale + * + * @return string + */ + public function getLocale() + { + return $this->_locale; + } + + /** + * Checks if the given date is a real date or datepart. + * Returns false if a expected datepart is missing or a datepart exceeds its possible border. + * But the check will only be done for the expected dateparts which are given by format. + * If no format is given the standard dateformat for the actual locale is used. + * f.e. 30.February.2007 will return false if format is 'dd.MMMM.YYYY' + * + * @param string|array|Zend_Date $date Date to parse for correctness + * @param string $format (Optional) Format for parsing the date string + * @param string|Zend_Locale $locale (Optional) Locale for parsing date parts + * @return boolean True when all date parts are correct + */ + public static function isDate($date, $format = null, $locale = null) + { + if (!is_string($date) && !is_numeric($date) && !($date instanceof Zend_Date) && + !is_array($date)) { + return false; + } + + if (($format !== null) && ($format != 'ee') && ($format != 'ss') && ($format != 'GG') && ($format != 'MM') && ($format != 'EE') && ($format != 'TT') + && (Zend_Locale::isLocale($format, null, false))) { + $locale = $format; + $format = null; + } + + $locale = Zend_Locale::findLocale($locale); + + if ($format === null) { + $format = Zend_Locale_Format::getDateFormat($locale); + } else if ((self::$_options['format_type'] == 'php') && !defined($format)) { + $format = Zend_Locale_Format::convertPhpToIsoFormat($format); + } + + $format = self::_getLocalizedToken($format, $locale); + if (!is_array($date)) { + try { + $parsed = Zend_Locale_Format::getDate($date, array('locale' => $locale, + 'date_format' => $format, 'format_type' => 'iso', + 'fix_date' => false)); + } catch (Zend_Locale_Exception $e) { + // Date can not be parsed + return false; + } + } else { + $parsed = $date; + } + + if (((strpos($format, 'Y') !== false) or (strpos($format, 'y') !== false)) and + (!isset($parsed['year']))) { + // Year expected but not found + return false; + } + + if ((strpos($format, 'M') !== false) and (!isset($parsed['month']))) { + // Month expected but not found + return false; + } + + if ((strpos($format, 'd') !== false) and (!isset($parsed['day']))) { + // Day expected but not found + return false; + } + + if (((strpos($format, 'H') !== false) or (strpos($format, 'h') !== false)) and + (!isset($parsed['hour']))) { + // Hour expected but not found + return false; + } + + if ((strpos($format, 'm') !== false) and (!isset($parsed['minute']))) { + // Minute expected but not found + return false; + } + + if ((strpos($format, 's') !== false) and (!isset($parsed['second']))) { + // Second expected but not found + return false; + } + + // Set not given dateparts + if (isset($parsed['hour']) === false) { + $parsed['hour'] = 12; + } + + if (isset($parsed['minute']) === false) { + $parsed['minute'] = 0; + } + + if (isset($parsed['second']) === false) { + $parsed['second'] = 0; + } + + if (isset($parsed['month']) === false) { + $parsed['month'] = 1; + } + + if (isset($parsed['day']) === false) { + $parsed['day'] = 1; + } + + if (isset($parsed['year']) === false) { + $parsed['year'] = 1970; + } + + if (self::isYearLeapYear($parsed['year'])) { + $parsed['year'] = 1972; + } else { + $parsed['year'] = 1971; + } + + $date = new self($parsed, null, $locale); + $timestamp = $date->mktime($parsed['hour'], $parsed['minute'], $parsed['second'], + $parsed['month'], $parsed['day'], $parsed['year']); + + if ($parsed['year'] != $date->date('Y', $timestamp)) { + // Given year differs from parsed year + return false; + } + + if ($parsed['month'] != $date->date('n', $timestamp)) { + // Given month differs from parsed month + return false; + } + + if ($parsed['day'] != $date->date('j', $timestamp)) { + // Given day differs from parsed day + return false; + } + + if ($parsed['hour'] != $date->date('G', $timestamp)) { + // Given hour differs from parsed hour + return false; + } + + if ($parsed['minute'] != $date->date('i', $timestamp)) { + // Given minute differs from parsed minute + return false; + } + + if ($parsed['second'] != $date->date('s', $timestamp)) { + // Given second differs from parsed second + return false; + } + + return true; + } + + /** + * Returns the ISO Token for all localized constants + * + * @param string $token Token to normalize + * @param string $locale Locale to search + * @return string + */ + protected static function _getLocalizedToken($token, $locale) + { + switch($token) { + case self::ISO_8601 : + return "yyyy-MM-ddThh:mm:ss"; + break; + case self::RFC_2822 : + return "EEE, dd MMM yyyy HH:mm:ss"; + break; + case self::DATES : + return Zend_Locale_Data::getContent($locale, 'date'); + break; + case self::DATE_FULL : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'full')); + break; + case self::DATE_LONG : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'long')); + break; + case self::DATE_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'medium')); + break; + case self::DATE_SHORT : + return Zend_Locale_Data::getContent($locale, 'date', array('gregorian', 'short')); + break; + case self::TIMES : + return Zend_Locale_Data::getContent($locale, 'time'); + break; + case self::TIME_FULL : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'full')); + break; + case self::TIME_LONG : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'long')); + break; + case self::TIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'medium')); + break; + case self::TIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'time', array('gregorian', 'short')); + break; + case self::DATETIME : + return Zend_Locale_Data::getContent($locale, 'datetime'); + break; + case self::DATETIME_FULL : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'full')); + break; + case self::DATETIME_LONG : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'long')); + break; + case self::DATETIME_MEDIUM : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'medium')); + break; + case self::DATETIME_SHORT : + return Zend_Locale_Data::getContent($locale, 'datetime', array('gregorian', 'short')); + break; + case self::ATOM : + case self::RFC_3339 : + case self::W3C : + return "yyyy-MM-DD HH:mm:ss"; + break; + case self::COOKIE : + case self::RFC_850 : + return "EEEE, dd-MM-yyyy HH:mm:ss"; + break; + case self::RFC_822 : + case self::RFC_1036 : + case self::RFC_1123 : + case self::RSS : + return "EEE, dd MM yyyy HH:mm:ss"; + break; + } + + return $token; + } + + /** + * Get unix timestamp. + * Added limitation: $year value must be between -10 000 and 10 000 + * Parent method implementation causes 504 error if it gets too big(small) year value + * + * @see Zend_Date_DateObject::mktime + * @throws Zend_Date_Exception + * @param $hour + * @param $minute + * @param $second + * @param $month + * @param $day + * @param $year + * @param bool $gmt + * @return float|int + */ + protected function mktime($hour, $minute, $second, $month, $day, $year, $gmt = false) + { + $day = intval($day); + $month = intval($month); + $year = intval($year); + + // correct months > 12 and months < 1 + if ($month > 12) { + $overlap = floor($month / 12); + $year += $overlap; + $month -= $overlap * 12; + } else { + $overlap = ceil((1 - $month) / 12); + $year -= $overlap; + $month += $overlap * 12; + } + + if ($year > self::YEAR_MAX_VALUE || $year < self::YEAR_MIN_VALUE) { + throw new Zend_Date_Exception('Invalid year, it must be between ' . self::YEAR_MIN_VALUE . ' and ' + . self::YEAR_MAX_VALUE); + } + + return parent::mktime($hour, $minute, $second, $month, $day, $year, $gmt); + } +} diff --git a/app/design/adminhtml/default/default/layout/admin.xml b/app/design/adminhtml/default/default/layout/admin.xml index 1cda1717..b69dfd08 100644 --- a/app/design/adminhtml/default/default/layout/admin.xml +++ b/app/design/adminhtml/default/default/layout/admin.xml @@ -21,12 +21,25 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> + + + + + + + + + + + + + diff --git a/app/design/adminhtml/default/default/layout/adminnotification.xml b/app/design/adminhtml/default/default/layout/adminnotification.xml index 6fda8dd9..b1fbbf7e 100644 --- a/app/design/adminhtml/default/default/layout/adminnotification.xml +++ b/app/design/adminhtml/default/default/layout/adminnotification.xml @@ -21,7 +21,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/design/adminhtml/default/default/layout/cms.xml b/app/design/adminhtml/default/default/layout/cms.xml index 55dddc2f..67fbc206 100644 --- a/app/design/adminhtml/default/default/layout/cms.xml +++ b/app/design/adminhtml/default/default/layout/cms.xml @@ -21,7 +21,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/design/adminhtml/default/default/layout/compiler.xml b/app/design/adminhtml/default/default/layout/compiler.xml index 81a0692f..7ff96fd7 100644 --- a/app/design/adminhtml/default/default/layout/compiler.xml +++ b/app/design/adminhtml/default/default/layout/compiler.xml @@ -21,15 +21,15 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> - + - + diff --git a/app/design/adminhtml/default/default/layout/index.xml b/app/design/adminhtml/default/default/layout/index.xml index af021a42..de77d959 100644 --- a/app/design/adminhtml/default/default/layout/index.xml +++ b/app/design/adminhtml/default/default/layout/index.xml @@ -21,7 +21,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/app/design/adminhtml/default/default/layout/main.xml b/app/design/adminhtml/default/default/layout/main.xml index 7962b52d..f3ce2d94 100644 --- a/app/design/adminhtml/default/default/layout/main.xml +++ b/app/design/adminhtml/default/default/layout/main.xml @@ -21,7 +21,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -36,11 +36,17 @@ Supported layout update handles (special): - admin_denied - preview - systemPreview - --> + + + + + @@ -50,8 +56,8 @@ Default layout, loads most of the pages Magento Admin + jsextjs/fix-defer-before.jscan_load_ext_js - @@ -71,9 +77,11 @@ Default layout, loads most of the pages + reset.css boxes.css + custom.css skin_cssiestyles.csslt IE 8 skin_cssbelow_ie7.csslt IE 7 @@ -91,6 +99,7 @@ Default layout, loads most of the pages jscalendar/calendar-setup.js jsextjs/ext-tree.jscan_load_ext_js + jsextjs/fix-defer.jscan_load_ext_js jsextjs/ext-tree-checkbox.jscan_load_ext_js js_cssextjs/resources/css/ext-all.csscan_load_ext_js js_cssextjs/resources/css/ytheme-magento.csscan_load_ext_js @@ -108,10 +117,15 @@ Default layout, loads most of the pages + + - + + + + @@ -162,7 +176,7 @@ Layout for editor element js_cssprototype/windows/themes/default.css - js_cssprototype/windows/themes/magento.css + lib/prototype/windows/themes/magento.css @@ -198,6 +212,25 @@ Base preview layout (?)
    + + + + + + + + + + + + + + + + + + + @@ -217,10 +250,42 @@ Base preview layout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/app/design/adminhtml/default/default/layout/widget.xml b/app/design/adminhtml/default/default/layout/widget.xml index 9980ec6e..2eba73bb 100644 --- a/app/design/adminhtml/default/default/layout/widget.xml +++ b/app/design/adminhtml/default/default/layout/widget.xml @@ -21,7 +21,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> @@ -47,7 +47,7 @@ jsprototype/window.js js_cssprototype/windows/themes/default.css - js_cssprototype/windows/themes/magento.css + lib/prototype/windows/themes/magento.css diff --git a/app/design/adminhtml/default/default/template/access_denied.phtml b/app/design/adminhtml/default/default/template/access_denied.phtml index c5ede4b6..0e49161e 100644 --- a/app/design/adminhtml/default/default/template/access_denied.phtml +++ b/app/design/adminhtml/default/default/template/access_denied.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml b/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml index 712cce5f..79ffafe5 100644 --- a/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml +++ b/app/design/adminhtml/default/default/template/api/role_users_grid_js.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> @@ -36,9 +36,9 @@ function registerUserRole(grid, element, checked){ if(checked){ - inRoleUsers[element.value] = 0; + inRoleUsers.set(element.value, 0); } else { - inRoleUsers.remove(element.value); + inRoleUsers.unset(element.value); } $('in_role_user').value = inRoleUsers.toQueryString(); grid.reloadParams = {'in_role_user[]':inRoleUsers.keys()}; @@ -54,11 +54,11 @@ if (warning && checkBoxes.size() > 0) { if ( !confirm("__('Warning!\r\nThis action will remove this user from already assigned role\r\nAre you sure?') ?>") ) { checkbox[0].checked = false; - for(i in checkBoxes) { - if( checkBoxes[i].status == 1) { - checkBoxes[i].object.checked = true; + checkBoxes.each(function(elem) { + if (elem.value.status == 1) { + elem.value.object.checked = true; } - } + }); return false; } warning = false; @@ -71,25 +71,27 @@ function roleUsersRowInit(grid, row){ var checkbox = $(row).getElementsByClassName('checkbox')[0]; if (checkbox) { - checkBoxes[checkbox.value] = {'status' : ((checkbox.checked) ? 1 : 0), 'object' : checkbox}; + checkBoxes.set(checkbox.value, {'status' : ((checkbox.checked) ? 1 : 0), 'object' : checkbox}); } } - function myhandler(o) + function myhandler(obj) { if (checkBoxes.size() > 0) { if ( !confirm("__('Warning!\r\nThis action will remove those users from already assigned roles\r\nAre you sure?') ?>") ) { - o.checked = false; - for(i in checkBoxes) { - if( checkBoxes[i].status == 1) { - checkBoxes[i].object.checked = true; + obj.checked = false; + checkBoxes.each(function(elem) { + if (elem.value.status == 1) { + elem.value.object.checked = true; } - } + }); return false; } warning = false; } - for(i in checkBoxes) getJsObjectName() ?>.setCheckboxChecked(checkBoxes[i].object, o.checked); + checkBoxes.each(function(elem) { + getJsObjectName() ?>.setCheckboxChecked(elem.value.object, obj.checked); + }); } getJsObjectName() ?>.rowClickCallback = roleUsersRowClick; diff --git a/app/design/adminhtml/default/default/template/api/roleinfo.phtml b/app/design/adminhtml/default/default/template/api/roleinfo.phtml index 2527dd91..f41a0001 100644 --- a/app/design/adminhtml/default/default/template/api/roleinfo.phtml +++ b/app/design/adminhtml/default/default/template/api/roleinfo.phtml @@ -20,14 +20,16 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ +/** @var $this Mage_Adminhtml_Block_Api_Tab_Roleinfo */ + ?>
    - +

    getRoleId() > 0 ) ? ($this->__('Edit Role') . " '{$this->getRoleInfo()->getRoleName()}'") : $this->__('Add New Role') ?>

    getRoleId() > 0 ) ? ($this->__('Edit Role') . " '{$this->escapeHtml($this->getRoleInfo()->getRoleName())}'") : $this->__('Add New Role') ?>

    getBackButtonHtml() ?> getResetButtonHtml() ?> diff --git a/app/design/adminhtml/default/default/template/api/roles.phtml b/app/design/adminhtml/default/default/template/api/roles.phtml index b43a57bf..cebb8dcb 100644 --- a/app/design/adminhtml/default/default/template/api/roles.phtml +++ b/app/design/adminhtml/default/default/template/api/roles.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> @@ -29,7 +29,7 @@

    __('Roles') ?>

    - +
    diff --git a/app/design/adminhtml/default/default/template/api/rolesedit.phtml b/app/design/adminhtml/default/default/template/api/rolesedit.phtml index fd6ceec4..e031b7da 100644 --- a/app/design/adminhtml/default/default/template/api/rolesedit.phtml +++ b/app/design/adminhtml/default/default/template/api/rolesedit.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/rolesusers.phtml b/app/design/adminhtml/default/default/template/api/rolesusers.phtml index f7d8772f..e09eb58a 100644 --- a/app/design/adminhtml/default/default/template/api/rolesusers.phtml +++ b/app/design/adminhtml/default/default/template/api/rolesusers.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/user_roles_grid_js.phtml b/app/design/adminhtml/default/default/template/api/user_roles_grid_js.phtml index 48784180..cc042e99 100644 --- a/app/design/adminhtml/default/default/template/api/user_roles_grid_js.phtml +++ b/app/design/adminhtml/default/default/template/api/user_roles_grid_js.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/userinfo.phtml b/app/design/adminhtml/default/default/template/api/userinfo.phtml index 9fe3dcb5..5290bbcf 100644 --- a/app/design/adminhtml/default/default/template/api/userinfo.phtml +++ b/app/design/adminhtml/default/default/template/api/userinfo.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/usernroles.phtml b/app/design/adminhtml/default/default/template/api/usernroles.phtml index 045100a5..9ccb82ac 100644 --- a/app/design/adminhtml/default/default/template/api/usernroles.phtml +++ b/app/design/adminhtml/default/default/template/api/usernroles.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/userroles.phtml b/app/design/adminhtml/default/default/template/api/userroles.phtml index 01015f60..4724e297 100644 --- a/app/design/adminhtml/default/default/template/api/userroles.phtml +++ b/app/design/adminhtml/default/default/template/api/userroles.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/api/users.phtml b/app/design/adminhtml/default/default/template/api/users.phtml index b4380ae0..f4df96e9 100644 --- a/app/design/adminhtml/default/default/template/api/users.phtml +++ b/app/design/adminhtml/default/default/template/api/users.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> @@ -29,7 +29,7 @@

    __('Users') ?>

    - + diff --git a/app/design/adminhtml/default/default/template/backup/dialogs.phtml b/app/design/adminhtml/default/default/template/backup/dialogs.phtml new file mode 100644 index 00000000..3db94d4f --- /dev/null +++ b/app/design/adminhtml/default/default/template/backup/dialogs.phtml @@ -0,0 +1,164 @@ + + + + + + + + + + +getUrl('*/*/rollback'); + $backupUrl = $this->getUrl('*/*/create'); +?> + + diff --git a/app/design/adminhtml/default/default/template/backup/left.phtml b/app/design/adminhtml/default/default/template/backup/left.phtml index 11b8a140..f5482bcb 100644 --- a/app/design/adminhtml/default/default/template/backup/left.phtml +++ b/app/design/adminhtml/default/default/template/backup/left.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/backup/list.phtml b/app/design/adminhtml/default/default/template/backup/list.phtml index 4a937dc1..b8c912f5 100644 --- a/app/design/adminhtml/default/default/template/backup/list.phtml +++ b/app/design/adminhtml/default/default/template/backup/list.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> @@ -28,8 +28,13 @@ - +

    __('Backups') ?>

    getCreateButtonHtml(); ?> + getCreateSnapshotButtonHtml(); ?> + getCreateMediaBackupButtonHtml(); ?> + getCreateButtonHtml(); ?> +
    getGridHtml() ?> +getDialogsHtml() ?> diff --git a/app/design/adminhtml/default/default/template/cms/browser/content.phtml b/app/design/adminhtml/default/default/template/cms/browser/content.phtml index ed74ab01..3447515c 100644 --- a/app/design/adminhtml/default/default/template/cms/browser/content.phtml +++ b/app/design/adminhtml/default/default/template/cms/browser/content.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/cms/browser/content/files.phtml b/app/design/adminhtml/default/default/template/cms/browser/content/files.phtml index 7f90e29c..28bc51ad 100644 --- a/app/design/adminhtml/default/default/template/cms/browser/content/files.phtml +++ b/app/design/adminhtml/default/default/template/cms/browser/content/files.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/cms/browser/content/newfolder.phtml b/app/design/adminhtml/default/default/template/cms/browser/content/newfolder.phtml index f27142b5..10af59bd 100644 --- a/app/design/adminhtml/default/default/template/cms/browser/content/newfolder.phtml +++ b/app/design/adminhtml/default/default/template/cms/browser/content/newfolder.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/app/design/adminhtml/default/default/template/cms/browser/content/uploader.phtml b/app/design/adminhtml/default/default/template/cms/browser/content/uploader.phtml index c08419a5..d3b5a96f 100644 --- a/app/design/adminhtml/default/default/template/cms/browser/content/uploader.phtml +++ b/app/design/adminhtml/default/default/template/cms/browser/content/uploader.phtml @@ -20,7 +20,7 @@ * * @category design * @package default_default - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> @@ -31,9 +31,6 @@ * @see Mage_Adminhtml_Block_Cms_Wysiwyg_Images_Content_Uploader */ ?> - -helper('adminhtml/media_js')->getTranslatorScript() ?> -
    '; + exit; +} + +require_once("lib/Mage/Autoload/Simple.php"); +Mage_Autoload_Simple::register(); + +umask(0); +Maged_Controller::run(); diff --git a/downloader/lib/Mage/Archive.php b/downloader/lib/Mage/Archive.php new file mode 100644 index 00000000..70abdb5b --- /dev/null +++ b/downloader/lib/Mage/Archive.php @@ -0,0 +1,222 @@ + + */ +class Mage_Archive +{ + + /** + * Archiver is used for compress. + */ + const DEFAULT_ARCHIVER = 'gz'; + + /** + * Default packer for directory. + */ + const TAPE_ARCHIVER = 'tar'; + + /** + * Current archiver is used for compress. + * + * @var Mage_Archiver_Tar|Mage_Archiver_Gz|Mage_Archiver_Bz + */ + protected $_archiver=null; + + /** + * Accessible formats for compress. + * + * @var array + */ + protected $_formats = array( + 'tar' => 'tar', + 'gz' => 'gz', + 'gzip' => 'gz', + 'tgz' => 'tar.gz', + 'tgzip' => 'tar.gz', + 'bz' => 'bz', + 'bzip' => 'bz', + 'bzip2' => 'bz', + 'bz2' => 'bz', + 'tbz' => 'tar.bz', + 'tbzip' => 'tar.bz', + 'tbz2' => 'tar.bz', + 'tbzip2' => 'tar.bz'); + + /** + * Create object of current archiver by $extension. + * + * @param string $extension + * @return Mage_Archiver_Tar|Mage_Archiver_Gz|Mage_Archiver_Bz + */ + protected function _getArchiver($extension) + { + if(array_key_exists(strtolower($extension), $this->_formats)) { + $format = $this->_formats[$extension]; + } else { + $format = self::DEFAULT_ARCHIVER; + } + $class = 'Mage_Archive_' . ucfirst($format); + $this->_archiver = new $class(); + return $this->_archiver; + } + + /** + * Split current format to list of archivers. + * + * @param string $source + * @return array + */ + protected function _getArchivers($source) + { + $ext = pathinfo($source, PATHINFO_EXTENSION); + if(!isset($this->_formats[$ext])) { + return array(); + } + $format = $this->_formats[$ext]; + if ($format) { + $archivers = explode('.', $format); + return $archivers; + } + return array(); + } + + /** + * Pack file or directory to archivers are parsed from extension. + * + * @param string $source + * @param string $destination + * @param boolean $skipRoot skip first level parent + * @return string Path to file + */ + public function pack($source, $destination='packed.tgz', $skipRoot=false) + { + $archivers = $this->_getArchivers($destination); + $interimSource = ''; + for ($i=0; $i_getArchiver($archivers[$i])->pack($source, $packed, $skipRoot); + if ($interimSource && $i < count($archivers)) { + unlink($interimSource); + } + $interimSource = $source; + } + return $source; + } + + /** + * Unpack file from archivers are parsed from extension. + * If $tillTar == true unpack file from archivers till + * meet TAR archiver. + * + * @param string $source + * @param string $destination + * @param boolean $tillTar + * @return string Path to file + */ + public function unpack($source, $destination='.', $tillTar=false, $clearInterm = true) + { + $archivers = $this->_getArchivers($source); + $interimSource = ''; + for ($i=count($archivers)-1; $i>=0; $i--) { + if ($tillTar && $archivers[$i] == self::TAPE_ARCHIVER) { + break; + } + if ($i == 0) { + $packed = rtrim($destination, DS) . DS; + } else { + $packed = rtrim($destination, DS) . DS . '~tmp-'. microtime(true) . $archivers[$i-1] . '.' + . $archivers[$i-1]; + } + $source = $this->_getArchiver($archivers[$i])->unpack($source, $packed); + + //var_dump($packed, $source); + + if ($clearInterm && $interimSource && $i >= 0) { + unlink($interimSource); + } + $interimSource = $source; + } + return $source; + } + + /** + * Extract one file from TAR (Tape Archiver). + * + * @param string $file + * @param string $source + * @param string $destination + * @return string Path to file + */ + public function extract($file, $source, $destination='.') + { + $tarFile = $this->unpack($source, $destination, true); + $resFile = $this->_getArchiver(self::TAPE_ARCHIVER)->extract($file, $tarFile, $destination); + if (!$this->isTar($source)) { + unlink($tarFile); + } + return $resFile; + } + + /** + * Check file is archive. + * + * @param string $file + * @return boolean + */ + public function isArchive($file) + { + $archivers = $this->_getArchivers($file); + if (count($archivers)) { + return true; + } + return false; + } + + /** + * Check file is TAR. + * + * @param mixed $file + * @return boolean + */ + public function isTar($file) + { + $archivers = $this->_getArchivers($file); + if (count($archivers)==1 && $archivers[0] == self::TAPE_ARCHIVER) { + return true; + } + return false; + } +} diff --git a/downloader/lib/Mage/Archive/Abstract.php b/downloader/lib/Mage/Archive/Abstract.php new file mode 100644 index 00000000..2aece187 --- /dev/null +++ b/downloader/lib/Mage/Archive/Abstract.php @@ -0,0 +1,87 @@ + + */ +class Mage_Archive_Abstract +{ + /** + * Write data to file. If file can't be opened - throw exception + * + * @param string $destination + * @param string $data + * @return boolean + * @throws Mage_Exception + */ + protected function _writeFile($destination, $data) + { + $destination = trim($destination); + if(false === file_put_contents($destination, $data)) { + throw new Mage_Exception("Can't write to file: " . $destination); + } + return true; + } + + /** + * Read data from file. If file can't be opened, throw to exception. + * + * @param string $source + * @return string + * @throws Mage_Exception + */ + protected function _readFile($source) + { + $data = ''; + if (is_file($source) && is_readable($source)) { + $data = @file_get_contents($source); + if ($data === false) { + throw new Mage_Exception("Can't get contents from: " . $source); + } + } + return $data; + } + + /** + * Get file name from source (URI) without last extension. + * + * @param string $source + * @param bool $withExtension + * @return mixed|string + */ + public function getFilename($source, $withExtension=false) + { + $file = str_replace(dirname($source) . DS, '', $source); + if (!$withExtension) { + $file = substr($file, 0, strrpos($file, '.')); + } + return $file; + } +} diff --git a/downloader/lib/Mage/Archive/Bz.php b/downloader/lib/Mage/Archive/Bz.php new file mode 100644 index 00000000..052fdcb4 --- /dev/null +++ b/downloader/lib/Mage/Archive/Bz.php @@ -0,0 +1,89 @@ + + */ +class Mage_Archive_Bz extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + + /** + * Pack file by BZIP2 compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination) + { + $fileReader = new Mage_Archive_Helper_File($source); + $fileReader->open('r'); + + $archiveWriter = new Mage_Archive_Helper_File_Bz($destination); + $archiveWriter->open('w'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + $archiveWriter->close(); + + return $destination; + } + + /** + * Unpack file by BZIP2 compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + if (is_dir($destination)) { + $file = $this->getFilename($source); + $destination = $destination . $file; + } + + $archiveReader = new Mage_Archive_Helper_File_Bz($source); + $archiveReader->open('r'); + + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w'); + + while (!$archiveReader->eof()) { + $fileWriter->write($archiveReader->read()); + } + + return $destination; + } + +} diff --git a/downloader/lib/Mage/Archive/Gz.php b/downloader/lib/Mage/Archive/Gz.php new file mode 100644 index 00000000..43a7c0d5 --- /dev/null +++ b/downloader/lib/Mage/Archive/Gz.php @@ -0,0 +1,87 @@ + + */ +class Mage_Archive_Gz extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + /** + * Pack file by GZ compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination) + { + $fileReader = new Mage_Archive_Helper_File($source); + $fileReader->open('r'); + + $archiveWriter = new Mage_Archive_Helper_File_Gz($destination); + $archiveWriter->open('wb9'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + $archiveWriter->close(); + + return $destination; + } + + /** + * Unpack file by GZ compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + if (is_dir($destination)) { + $file = $this->getFilename($source); + $destination = $destination . $file; + } + + $archiveReader = new Mage_Archive_Helper_File_Gz($source); + $archiveReader->open('r'); + + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w'); + + while (!$archiveReader->eof()) { + $fileWriter->write($archiveReader->read()); + } + + return $destination; + } +} diff --git a/downloader/lib/Mage/Archive/Helper/File.php b/downloader/lib/Mage/Archive/Helper/File.php new file mode 100755 index 00000000..04872633 --- /dev/null +++ b/downloader/lib/Mage/Archive/Helper/File.php @@ -0,0 +1,274 @@ + +*/ +class Mage_Archive_Helper_File +{ + /** + * Full path to directory where file located + * + * @var string + */ + protected $_fileLocation; + + /** + * File name + * + * @var string + */ + protected $_fileName; + + /** + * Full path (directory + filename) to file + * + * @var string + */ + protected $_filePath; + + /** + * File permissions that will be set if file opened in write mode + * + * @var int + */ + protected $_chmod; + + /** + * File handler + * + * @var pointer + */ + protected $_fileHandler; + + /** + * Set file path via constructor + * + * @param string $filePath + */ + public function __construct($filePath) + { + $pathInfo = pathinfo($filePath); + + $this->_filePath = $filePath; + $this->_fileLocation = isset($pathInfo['dirname']) ? $pathInfo['dirname'] : ''; + $this->_fileName = isset($pathInfo['basename']) ? $pathInfo['basename'] : ''; + } + + /** + * Close file if it's not closed before object destruction + */ + public function __destruct() + { + if ($this->_fileHandler) { + $this->_close(); + } + } + + /** + * Open file + * + * @param string $mode + * @param int $chmod + * @throws Mage_Exception + */ + public function open($mode = 'w+', $chmod = 0666) + { + if ($this->_isWritableMode($mode)) { + if (!is_writable($this->_fileLocation)) { + throw new Mage_Exception('Permission denied to write to ' . $this->_fileLocation); + } + + if (is_file($this->_filePath) && !is_writable($this->_filePath)) { + throw new Mage_Exception("Can't open file " . $this->_fileName . " for writing. Permission denied."); + } + } + + if ($this->_isReadableMode($mode) && (!is_file($this->_filePath) || !is_readable($this->_filePath))) { + if (!is_file($this->_filePath)) { + throw new Mage_Exception('File ' . $this->_filePath . ' does not exist'); + } + + if (!is_readable($this->_filePath)) { + throw new Mage_Exception('Permission denied to read file ' . $this->_filePath); + } + } + + $this->_open($mode); + + $this->_chmod = $chmod; + } + + /** + * Write data to file + * + * @param string $data + */ + public function write($data) + { + $this->_checkFileOpened(); + $this->_write($data); + } + + /** + * Read data from file + * + * @param int $length + * @return string|boolean + */ + public function read($length = 4096) + { + $data = false; + $this->_checkFileOpened(); + if ($length > 0) { + $data = $this->_read($length); + } + + return $data; + } + + /** + * Check whether end of file reached + * + * @return boolean + */ + public function eof() + { + $this->_checkFileOpened(); + return $this->_eof(); + } + + /** + * Close file + */ + public function close() + { + $this->_checkFileOpened(); + $this->_close(); + $this->_fileHandler = false; + @chmod($this->_filePath, $this->_chmod); + } + + /** + * Implementation of file opening + * + * @param string $mode + * @throws Mage_Exception + */ + protected function _open($mode) + { + $this->_fileHandler = @fopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * Implementation of writing data to file + * + * @param string $data + * @throws Mage_Exception + */ + protected function _write($data) + { + $result = @fwrite($this->_fileHandler, $data); + + if (false === $result) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * Implementation of file reading + * + * @param int $length + * @throws Mage_Exception + */ + protected function _read($length) + { + $result = fread($this->_fileHandler, $length); + + if (false === $result) { + throw new Mage_Exception('Failed to read data from ' . $this->_filePath); + } + + return $result; + } + + /** + * Implementation of EOF indicator + * + * @return boolean + */ + protected function _eof() + { + return feof($this->_fileHandler); + } + + /** + * Implementation of file closing + */ + protected function _close() + { + fclose($this->_fileHandler); + } + + /** + * Check whether requested mode is writable mode + * + * @param string $mode + */ + protected function _isWritableMode($mode) + { + return preg_match('/(^[waxc])|(\+$)/', $mode); + } + + /** + * Check whether requested mode is readable mode + * + * @param string $mode + */ + protected function _isReadableMode($mode) { + return !$this->_isWritableMode($mode); + } + + /** + * Check whether file is opened + * + * @throws Mage_Exception + */ + protected function _checkFileOpened() + { + if (!$this->_fileHandler) { + throw new Mage_Exception('File not opened'); + } + } +} diff --git a/downloader/lib/Mage/Archive/Helper/File/Bz.php b/downloader/lib/Mage/Archive/Helper/File/Bz.php new file mode 100755 index 00000000..dbddaaeb --- /dev/null +++ b/downloader/lib/Mage/Archive/Helper/File/Bz.php @@ -0,0 +1,92 @@ + +*/ +class Mage_Archive_Helper_File_Bz extends Mage_Archive_Helper_File +{ + /** + * Open bz archive file + * + * @throws Mage_Exception + * @param string $mode + */ + protected function _open($mode) + { + $this->_fileHandler = @bzopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * Write data to bz archive + * + * @throws Mage_Exception + * @param $data + */ + protected function _write($data) + { + $result = @bzwrite($this->_fileHandler, $data); + + if (false === $result) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * Read data from bz archive + * + * @throws Mage_Exception + * @param int $length + * @return string + */ + protected function _read($length) + { + $data = bzread($this->_fileHandler, $length); + + if (false === $data) { + throw new Mage_Exception('Failed to read data from ' . $this->_filePath); + } + + return $data; + } + + /** + * Close bz archive + */ + protected function _close() + { + bzclose($this->_fileHandler); + } +} + diff --git a/downloader/lib/Mage/Archive/Helper/File/Gz.php b/downloader/lib/Mage/Archive/Helper/File/Gz.php new file mode 100755 index 00000000..9bf01228 --- /dev/null +++ b/downloader/lib/Mage/Archive/Helper/File/Gz.php @@ -0,0 +1,83 @@ + +*/ +class Mage_Archive_Helper_File_Gz extends Mage_Archive_Helper_File +{ + /** + * @see Mage_Archive_Helper_File::_open() + */ + protected function _open($mode) + { + $this->_fileHandler = @gzopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * @see Mage_Archive_Helper_File::_write() + */ + protected function _write($data) + { + $result = @gzwrite($this->_fileHandler, $data); + + if (empty($result) && !empty($data)) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * @see Mage_Archive_Helper_File::_read() + */ + protected function _read($length) + { + return gzread($this->_fileHandler, $length); + } + + /** + * @see Mage_Archive_Helper_File::_eof() + */ + protected function _eof() + { + return gzeof($this->_fileHandler); + } + + /** + * @see Mage_Archive_Helper_File::_close() + */ + protected function _close() + { + gzclose($this->_fileHandler); + } +} diff --git a/downloader/lib/Mage/Archive/Interface.php b/downloader/lib/Mage/Archive/Interface.php new file mode 100644 index 00000000..8b3fe7c8 --- /dev/null +++ b/downloader/lib/Mage/Archive/Interface.php @@ -0,0 +1,53 @@ + + */ +interface Mage_Archive_Interface +{ + /** + * Pack file or directory. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination); + + /** + * Unpack file or directory. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination); +} diff --git a/downloader/lib/Mage/Archive/Tar.php b/downloader/lib/Mage/Archive/Tar.php new file mode 100644 index 00000000..e36fec58 --- /dev/null +++ b/downloader/lib/Mage/Archive/Tar.php @@ -0,0 +1,688 @@ + + */ +class Mage_Archive_Tar extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + /** + * Tar block size + * + * @const int + */ + const TAR_BLOCK_SIZE = 512; + + /** + * Keep file or directory for packing. + * + * @var string + */ + protected $_currentFile; + + /** + * Keep path to file or directory for packing. + * + * @var mixed + */ + protected $_currentPath; + + /** + * Skip first level parent directory. Example: + * use test/fip.php instead test/test/fip.php; + * + * @var mixed + */ + protected $_skipRoot; + + /** + * Tarball data writer + * + * @var Mage_Archive_Helper_File + */ + protected $_writer; + + /** + * Tarball data reader + * + * @var Mage_Archive_Helper_File + */ + protected $_reader; + + /** + * Path to file where tarball should be placed + * + * @var string + */ + protected $_destinationFilePath; + + /** + * Initialize tarball writer + * + * @return Mage_Archive_Tar + */ + protected function _initWriter() + { + $this->_writer = new Mage_Archive_Helper_File($this->_destinationFilePath); + $this->_writer->open('w'); + + return $this; + } + + /** + * Returns string that is used for tar's header parsing + * + * @return string + */ + protected static final function _getFormatParseHeader() + { + return 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/' + . 'a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer'; + } + + /** + * Destroy tarball writer + * + * @return Mage_Archive_Tar + */ + protected function _destroyWriter() + { + if ($this->_writer instanceof Mage_Archive_Helper_File) { + $this->_writer->close(); + $this->_writer = null; + } + + return $this; + } + + /** + * Get tarball writer + * + * @return Mage_Archive_Helper_File + */ + protected function _getWriter() + { + if (!$this->_writer) { + $this->_initWriter(); + } + + return $this->_writer; + } + + /** + * Initialize tarball reader + * + * @return Mage_Archive_Tar + */ + protected function _initReader() + { + $this->_reader = new Mage_Archive_Helper_File($this->_getCurrentFile()); + $this->_reader->open('r'); + + return $this; + } + + /** + * Destroy tarball reader + * + * @return Mage_Archive_Tar + */ + protected function _destroyReader() + { + if ($this->_reader instanceof Mage_Archive_Helper_File) { + $this->_reader->close(); + $this->_reader = null; + } + + return $this; + } + + /** + * Get tarball reader + * + * @return Mage_Archive_Helper_File + */ + protected function _getReader() + { + if (!$this->_reader) { + $this->_initReader(); + } + + return $this->_reader; + } + + /** + * Set option that define ability skip first catalog level. + * + * @param mixed $skipRoot + * @return Mage_Archive_Tar + */ + protected function _setSkipRoot($skipRoot) + { + $this->_skipRoot = $skipRoot; + return $this; + } + + /** + * Set file which is packing. + * + * @param string $file + * @return Mage_Archive_Tar + */ + protected function _setCurrentFile($file) + { + $this->_currentFile = $file .((!is_link($file) && is_dir($file) && substr($file, -1) != DS) ? DS : ''); + return $this; + } + + /** + * Set path to file where tarball should be placed + * + * @param string $destinationFilePath + * @return Mage_Archive_Tar + */ + protected function _setDestinationFilePath($destinationFilePath) + { + $this->_destinationFilePath = $destinationFilePath; + return $this; + } + + /** + * Retrieve file which is packing. + * + * @return string + */ + protected function _getCurrentFile() + { + return $this->_currentFile; + } + + /** + * Set path to file which is packing. + * + * @param string $path + * @return Mage_Archive_Tar + */ + protected function _setCurrentPath($path) + { + if ($this->_skipRoot && is_dir($path)) { + $this->_currentPath = $path.(substr($path, -1)!=DS?DS:''); + } else { + $this->_currentPath = dirname($path) . DS; + } + return $this; + } + + /** + * Retrieve path to file which is packing. + * + * @return string + */ + protected function _getCurrentPath() + { + return $this->_currentPath; + } + + /** + * Walk through directory and add to tar file or directory. + * Result is packed string on TAR format. + * + * @deprecated after 1.7.0.0 + * @param boolean $skipRoot + * @return string + */ + protected function _packToTar($skipRoot=false) + { + $file = $this->_getCurrentFile(); + $header = ''; + $data = ''; + if (!$skipRoot) { + $header = $this->_composeHeader(); + $data = $this->_readFile($file); + $data = str_pad($data, floor(((is_dir($file) ? 0 : filesize($file)) + 512 - 1) / 512) * 512, "\0"); + } + $sub = ''; + if (is_dir($file)) { + $treeDir = scandir($file); + if (empty($treeDir)) { + throw new Mage_Exception('Can\'t scan dir: ' . $file); + } + array_shift($treeDir); /* remove './'*/ + array_shift($treeDir); /* remove '../'*/ + foreach ($treeDir as $item) { + $sub .= $this->_setCurrentFile($file.$item)->_packToTar(false); + } + } + $tarData = $header . $data . $sub; + $tarData = str_pad($tarData, floor((strlen($tarData) - 1) / 1536) * 1536, "\0"); + return $tarData; + } + + /** + * Recursively walk through file tree and create tarball + * + * @param boolean $skipRoot + * @param boolean $finalize + * @throws Mage_Exception + */ + protected function _createTar($skipRoot = false, $finalize = false) + { + if (!$skipRoot) { + $this->_packAndWriteCurrentFile(); + } + + $file = $this->_getCurrentFile(); + + if (is_dir($file)) { + $dirFiles = scandir($file); + + if (false === $dirFiles) { + throw new Mage_Exception('Can\'t scan dir: ' . $file); + } + + array_shift($dirFiles); /* remove './'*/ + array_shift($dirFiles); /* remove '../'*/ + + foreach ($dirFiles as $item) { + $this->_setCurrentFile($file . $item)->_createTar(); + } + } + + if ($finalize) { + $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12)); + } + } + + /** + * Write current file to tarball + */ + protected function _packAndWriteCurrentFile() + { + $archiveWriter = $this->_getWriter(); + $archiveWriter->write($this->_composeHeader()); + + $currentFile = $this->_getCurrentFile(); + + $fileSize = 0; + + if (is_file($currentFile) && !is_link($currentFile)) { + $fileReader = new Mage_Archive_Helper_File($currentFile); + $fileReader->open('r'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + + $fileSize = filesize($currentFile); + } + + $appendZerosCount = (self::TAR_BLOCK_SIZE - $fileSize % self::TAR_BLOCK_SIZE) % self::TAR_BLOCK_SIZE; + $archiveWriter->write(str_repeat("\0", $appendZerosCount)); + } + + /** + * Compose header for current file in TAR format. + * If length of file's name greater 100 characters, + * method breaks header into two pieces. First contains + * header and data with long name. Second contain only header. + * + * @param boolean $long + * @return string + */ + protected function _composeHeader($long = false) + { + $file = $this->_getCurrentFile(); + $path = $this->_getCurrentPath(); + $infoFile = stat($file); + $nameFile = str_replace($path, '', $file); + $nameFile = str_replace('\\', '/', $nameFile); + $packedHeader = ''; + $longHeader = ''; + if (!$long && strlen($nameFile)>100) { + $longHeader = $this->_composeHeader(true); + $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0"); + } + $header = array(); + $header['100-name'] = $long?'././@LongLink':substr($nameFile, 0, 100); + $header['8-mode'] = $long ? ' ' + : str_pad(substr(sprintf("%07o", $infoFile['mode']),-4), 6, '0', STR_PAD_LEFT); + $header['8-uid'] = $long || $infoFile['uid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['uid']); + $header['8-gid'] = $long || $infoFile['gid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['gid']); + $header['12-size'] = $long ? sprintf("%011o", strlen($nameFile)) : sprintf("%011o", is_dir($file) + ? 0 : filesize($file)); + $header['12-mtime'] = $long?'00000000000':sprintf("%011o", $infoFile['mtime']); + $header['8-check'] = sprintf('% 8s', ''); + $header['1-type'] = $long ? 'L' : (is_link($file) ? 2 : (is_dir($file) ? 5 : 0)); + $header['100-symlink'] = is_link($file) ? readlink($file) : ''; + $header['6-magic'] = 'ustar '; + $header['2-version'] = ' '; + $a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($file)):array('name'=>''); + $header['32-uname'] = $a['name']; + $a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($file)):array('name'=>''); + $header['32-gname'] = $a['name']; + $header['8-devmajor'] = ''; + $header['8-devminor'] = ''; + $header['155-prefix'] = ''; + $header['12-closer'] = ''; + + $packedHeader = ''; + foreach ($header as $key=>$element) { + $length = explode('-', $key); + $packedHeader .= pack('a' . $length[0], $element); + } + + $checksum = 0; + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($packedHeader, $i, 1)); + } + $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum)."\0", 148, 8); + + return $longHeader . $packedHeader; + } + + /** + * Read TAR string from file, and unpacked it. + * Create files and directories information about discribed + * in the string. + * + * @param string $destination path to file is unpacked + * @return array list of files + * @throws Mage_Exception + */ + protected function _unpackCurrentTar($destination) + { + $archiveReader = $this->_getReader(); + $list = array(); + + while (!$archiveReader->eof()) { + $header = $this->_extractFileHeader(); + + if (!$header) { + continue; + } + + $currentFile = $destination . $header['name']; + $dirname = dirname($currentFile); + + if (in_array($header['type'], array("0",chr(0), ''))) { + + if(!file_exists($dirname)) { + $mkdirResult = @mkdir($dirname, 0777, true); + + if (false === $mkdirResult) { + throw new Mage_Exception('Failed to create directory ' . $dirname); + } + } + + $this->_extractAndWriteFile($header, $currentFile); + $list[] = $currentFile; + + } elseif ($header['type'] == '5') { + + if(!file_exists($dirname)) { + $mkdirResult = @mkdir($currentFile, $header['mode'], true); + + if (false === $mkdirResult) { + throw new Mage_Exception('Failed to create directory ' . $currentFile); + } + } + $list[] = $currentFile . DS; + } elseif ($header['type'] == '2') { + + $symlinkResult = @symlink($header['symlink'], $currentFile); + + if (false === $symlinkResult) { + throw new Mage_Exception('Failed to create symlink ' . $currentFile . ' to ' . $header['symlink']); + } + } + } + + return $list; + } + + /** + * Get header from TAR string and unpacked it by format. + * + * @deprecated after 1.7.0.0 + * @param resource $pointer + * @return string + */ + protected function _parseHeader(&$pointer) + { + $firstLine = fread($pointer, 512); + + if (strlen($firstLine)<512){ + return false; + } + + $fmt = self::_getFormatParseHeader(); + $header = unpack ($fmt, $firstLine); + + $header['mode']=$header['mode']+0; + $header['uid']=octdec($header['uid']); + $header['gid']=octdec($header['gid']); + $header['size']=octdec($header['size']); + $header['mtime']=octdec($header['mtime']); + $header['checksum']=octdec($header['checksum']); + + if ($header['type'] == "5") { + $header['size'] = 0; + } + + $checksum = 0; + $firstLine = substr_replace($firstLine, ' ', 148, 8); + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($firstLine, $i, 1)); + } + + $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); + + $checksumOk = $header['checksum'] == $checksum; + if (isset($header['name']) && $checksumOk) { + if ($header['name'] == '././@LongLink' && $header['type'] == 'L') { + $realName = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); + $headerMain = $this->_parseHeader($pointer); + $headerMain['name'] = $realName; + return $headerMain; + } else { + if ($header['size']>0) { + $header['data'] = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); + } else { + $header['data'] = ''; + } + return $header; + } + } + return false; + } + + /** + * Read and decode file header information from tarball + * + * @return array|boolean + */ + protected function _extractFileHeader() + { + $archiveReader = $this->_getReader(); + + $headerBlock = $archiveReader->read(self::TAR_BLOCK_SIZE); + + if (strlen($headerBlock) < self::TAR_BLOCK_SIZE) { + return false; + } + + $header = unpack(self::_getFormatParseHeader(), $headerBlock); + + $header['mode'] = octdec($header['mode']); + $header['uid'] = octdec($header['uid']); + $header['gid'] = octdec($header['gid']); + $header['size'] = octdec($header['size']); + $header['mtime'] = octdec($header['mtime']); + $header['checksum'] = octdec($header['checksum']); + + if ($header['type'] == "5") { + $header['size'] = 0; + } + + $checksum = 0; + $headerBlock = substr_replace($headerBlock, ' ', 148, 8); + + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($headerBlock, $i, 1)); + } + + $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); + + $checksumOk = $header['checksum'] == $checksum; + if (isset($header['name']) && $checksumOk) { + + if (!($header['name'] == '././@LongLink' && $header['type'] == 'L')) { + return $header; + } + + $realNameBlockSize = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) + * self::TAR_BLOCK_SIZE; + $realNameBlock = $archiveReader->read($realNameBlockSize); + $realName = substr($realNameBlock, 0, $header['size']); + + $headerMain = $this->_extractFileHeader(); + $headerMain['name'] = $realName; + return $headerMain; + } + + return false; + } + + /** + * Extract next file from tarball by its $header information and save it to $destination + * + * @param array $fileHeader + * @param string $destination + */ + protected function _extractAndWriteFile($fileHeader, $destination) + { + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w', $fileHeader['mode']); + + $archiveReader = $this->_getReader(); + + $filesize = $fileHeader['size']; + $bytesExtracted = 0; + + while ($filesize > $bytesExtracted && !$archiveReader->eof()) { + $block = $archiveReader->read(self::TAR_BLOCK_SIZE); + $nonExtractedBytesCount = $filesize - $bytesExtracted; + + $data = substr($block, 0, $nonExtractedBytesCount); + $fileWriter->write($data); + + $bytesExtracted += strlen($block); + } + } + + /** + * Pack file to TAR (Tape Archiver). + * + * @param string $source + * @param string $destination + * @param boolean $skipRoot + * @return string + */ + public function pack($source, $destination, $skipRoot = false) + { + $this->_setSkipRoot($skipRoot); + $source = realpath($source); + $tarData = $this->_setCurrentPath($source) + ->_setDestinationFilePath($destination) + ->_setCurrentFile($source); + + $this->_initWriter(); + $this->_createTar($skipRoot, true); + $this->_destroyWriter(); + + return $destination; + } + + /** + * Unpack file from TAR (Tape Archiver). + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + $this->_setCurrentFile($source) + ->_setCurrentPath($source); + + $this->_initReader(); + $this->_unpackCurrentTar($destination); + $this->_destroyReader(); + + return $destination; + } + + /** + * Extract one file from TAR (Tape Archiver). + * + * @param string $file + * @param string $source + * @param string $destination + * @return string + */ + public function extract($file, $source, $destination) + { + $this->_setCurrentFile($source); + $this->_initReader(); + + $archiveReader = $this->_getReader(); + $extractedFile = ''; + + while (!$archiveReader->eof()) { + $header = $this->_extractFileHeader(); + if ($header['name'] == $file) { + $extractedFile = $destination . basename($header['name']); + $this->_extractAndWriteFile($header, $extractedFile); + break; + } + + if ($header['type'] != 5){ + $skipBytes = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) + * self::TAR_BLOCK_SIZE; + $archiveReader->read($skipBytes); + } + } + + $this->_destroyReader(); + return $extractedFile; + } +} diff --git a/downloader/lib/Mage/Autoload/Simple.php b/downloader/lib/Mage/Autoload/Simple.php new file mode 100644 index 00000000..260b57a3 --- /dev/null +++ b/downloader/lib/Mage/Autoload/Simple.php @@ -0,0 +1,52 @@ + + */ +class Mage_Backup +{ + /** + * List of supported a backup types + * + * @var array + */ + static protected $_allowedBackupTypes = array('db', 'snapshot', 'filesystem', 'media', 'nomedia'); + + /** + * get Backup Instance By File Name + * + * @param string $type + * @return Mage_Backup_Interface + */ + static public function getBackupInstance($type) + { + $class = 'Mage_Backup_' . ucfirst($type); + + if (!in_array($type, self::$_allowedBackupTypes) || !class_exists($class, true)){ + throw new Mage_Exception('Current implementation not supported this type (' . $type . ') of backup.'); + } + + return new $class(); + } +} diff --git a/downloader/lib/Mage/Backup/Abstract.php b/downloader/lib/Mage/Backup/Abstract.php new file mode 100755 index 00000000..340819f7 --- /dev/null +++ b/downloader/lib/Mage/Backup/Abstract.php @@ -0,0 +1,318 @@ + + */ +abstract class Mage_Backup_Abstract implements Mage_Backup_Interface +{ + /** + * Backup name + * + * @var string + */ + protected $_name; + + /** + * Backup creation date + * + * @var int + */ + protected $_time; + + /** + * Backup file extension + * + * @var string + */ + protected $_backupExtension; + + /** + * Resource model + * + * @var object + */ + protected $_resourceModel; + + /** + * Magento's root directory + * + * @var string + */ + protected $_rootDir; + + /** + * Path to directory where backups stored + * + * @var string + */ + protected $_backupsDir; + + /** + * Is last operation completed successfully + * + * @var bool + */ + protected $_lastOperationSucceed = false; + + /** + * Last failed operation error message + * + * @var string + */ + protected $_lastErrorMessage; + + + /** + * Set Backup Extension + * + * @param string $backupExtension + * @return Mage_Backup_Interface + */ + public function setBackupExtension($backupExtension) + { + $this->_backupExtension = $backupExtension; + return $this; + } + + /** + * Get Backup Extension + * + * @return string + */ + public function getBackupExtension() + { + return $this->_backupExtension; + } + + /** + * Set Resource Model + * + * @param object $resourceModel + * @return Mage_Backup_Interface + */ + public function setResourceModel($resourceModel) + { + $this->_resourceModel = $resourceModel; + return $this; + } + + /** + * Get Resource Model + * + * @return object + */ + public function getResourceModel() + { + return $this->_resourceModel; + } + + /** + * Set Time + * + * @param int $time + * @return Mage_Backup_Interface + */ + public function setTime($time) + { + $this->_time = $time; + return $this; + } + + /** + * Get Time + * + * @return int + */ + public function getTime() + { + return $this->_time; + } + + /** + * Set root directory of Magento installation + * + * @param string $rootDir + * @throws Mage_Exception + * @return Mage_Backup_Interface + */ + public function setRootDir($rootDir) + { + if (!is_dir($rootDir)) { + throw new Mage_Exception('Bad root directory'); + } + + $this->_rootDir = $rootDir; + return $this; + } + + /** + * Get Magento's root directory + * @return string + */ + public function getRootDir() + { + return $this->_rootDir; + } + + /** + * Set path to directory where backups stored + * + * @param string $backupsDir + * @return Mage_Backup_Interface + */ + public function setBackupsDir($backupsDir) + { + $this->_backupsDir = $backupsDir; + return $this; + } + + /** + * Get path to directory where backups stored + * + * @return string + */ + public function getBackupsDir() + { + return $this->_backupsDir; + } + + /** + * Get path to backup + * + * @return string + */ + public function getBackupPath() + { + return $this->getBackupsDir() . DS . $this->getBackupFilename(); + } + + /** + * Get backup file name + * + * @return string + */ + public function getBackupFilename() + { + $filename = $this->getTime() . '_' . $this->getType(); + + $name = $this->getName(); + + if (!empty($name)) { + $filename .= '_' . $name; + } + + $filename .= '.' . $this->getBackupExtension(); + + return $filename; + } + + /** + * Check whether last operation completed successfully + * + * @return bool + */ + public function getIsSuccess() + { + return $this->_lastOperationSucceed; + } + + /** + * Get last error message + * + * @return string + */ + public function getErrorMessage() + { + return $this->_lastErrorMessage; + } + + /** + * Set error message + * + * @param string $errorMessage + * @return string + */ + public function setErrorMessage($errorMessage) + { + $this->_lastErrorMessage = $errorMessage; + } + + /** + * Set backup name + * + * @param string $name + * @param bool $applyFilter + * @return Mage_Backup_Interface + */ + public function setName($name, $applyFilter = true) + { + if ($applyFilter) { + $name = $this->_filterName($name); + } + $this->_name = $name; + return $this; + } + + /** + * Get backup name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Get backup display name + * + * @return string + */ + public function getDisplayName() + { + return str_replace('_', ' ', $this->_name); + } + + /** + * Removes disallowed characters and replaces spaces with underscores + * + * @param string $name + * @return string + */ + protected function _filterName($name) + { + $name = trim(preg_replace('/[^\da-zA-Z ]/', '', $name)); + $name = preg_replace('/\s{2,}/', ' ', $name); + $name = str_replace(' ', '_', $name); + + return $name; + } +} diff --git a/downloader/lib/Mage/Backup/Archive/Tar.php b/downloader/lib/Mage/Backup/Archive/Tar.php new file mode 100644 index 00000000..07e6c34a --- /dev/null +++ b/downloader/lib/Mage/Backup/Archive/Tar.php @@ -0,0 +1,82 @@ + + */ +class Mage_Backup_Archive_Tar extends Mage_Archive_Tar +{ + /** + * Filenames or filename parts that are used for filtering files + * + * @var array() + */ + protected $_skipFiles = array(); + + /** + * Overridden Mage_Archive_Tar::_createTar method that does the same actions as it's parent but filters + * files using Mage_Backup_Filesystem_Iterator_Filter + * + * @see Mage_Archive_Tar::_createTar() + * @param bool $skipRoot + * @param bool $finalize + */ + protected function _createTar($skipRoot = false, $finalize = false) + { + $path = $this->_getCurrentFile(); + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_skipFiles); + + foreach ($iterator as $item) { + $this->_setCurrentFile($item->getPathname()); + $this->_packAndWriteCurrentFile(); + } + + if ($finalize) { + $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12)); + } + } + + /** + * Set files that shouldn't be added to tarball + * + * @param array $skipFiles + * @return Mage_Backup_Archive_Tar + */ + public function setSkipFiles(array $skipFiles) + { + $this->_skipFiles = $skipFiles; + return $this; + } +} diff --git a/downloader/lib/Mage/Backup/Db.php b/downloader/lib/Mage/Backup/Db.php new file mode 100755 index 00000000..5585ef3e --- /dev/null +++ b/downloader/lib/Mage/Backup/Db.php @@ -0,0 +1,119 @@ + + */ +class Mage_Backup_Db extends Mage_Backup_Abstract +{ + /** + * Implements Rollback functionality for Db + * + * @return bool + */ + public function rollback() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $archiveManager = new Mage_Archive(); + $source = $archiveManager->unpack($this->getBackupPath(), $this->getBackupsDir()); + + $file = new Mage_Backup_Filesystem_Iterator_File($source); + foreach ($file as $statement) { + $this->getResourceModel()->runCommand($statement); + } + @unlink($source); + + $this->_lastOperationSucceed = true; + + return true; + } + + /** + * Checks whether the line is last in sql command + * + * @param $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0){ + $lastSymbolIndex = $lineLength-1; + if ($cleanLine[$lastSymbolIndex] == ';'){ + $returnResult = true; + } + } + + return $returnResult; + } + + /** + * Implements Create Backup functionality for Db + * + * @return bool + */ + public function create() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $backup = Mage::getModel('backup/backup') + ->setTime($this->getTime()) + ->setType($this->getType()) + ->setPath($this->getBackupsDir()) + ->setName($this->getName()); + + $backupDb = Mage::getModel('backup/db'); + $backupDb->createBackup($backup); + + $this->_lastOperationSucceed = true; + + return true; + } + + /** + * Get Backup Type + * + * @return string + */ + public function getType() + { + return 'db'; + } +} diff --git a/downloader/lib/Mage/Backup/Exception.php b/downloader/lib/Mage/Backup/Exception.php new file mode 100755 index 00000000..c92f1346 --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception extends Mage_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Exception/CantLoadSnapshot.php b/downloader/lib/Mage/Backup/Exception/CantLoadSnapshot.php new file mode 100755 index 00000000..98ed8f6f --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception/CantLoadSnapshot.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_CantLoadSnapshot extends Mage_Backup_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Exception/FtpConnectionFailed.php b/downloader/lib/Mage/Backup/Exception/FtpConnectionFailed.php new file mode 100755 index 00000000..cd6aa9b6 --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception/FtpConnectionFailed.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_FtpConnectionFailed extends Mage_Backup_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Exception/FtpValidationFailed.php b/downloader/lib/Mage/Backup/Exception/FtpValidationFailed.php new file mode 100755 index 00000000..acc4c9e7 --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception/FtpValidationFailed.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_FtpValidationFailed extends Mage_Backup_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php b/downloader/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php new file mode 100755 index 00000000..21e56a19 --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_NotEnoughFreeSpace extends Mage_Backup_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Exception/NotEnoughPermissions.php b/downloader/lib/Mage/Backup/Exception/NotEnoughPermissions.php new file mode 100755 index 00000000..001d1a90 --- /dev/null +++ b/downloader/lib/Mage/Backup/Exception/NotEnoughPermissions.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_NotEnoughPermissions extends Mage_Backup_Exception +{ +} diff --git a/downloader/lib/Mage/Backup/Filesystem.php b/downloader/lib/Mage/Backup/Filesystem.php new file mode 100755 index 00000000..a92c0c2f --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem.php @@ -0,0 +1,284 @@ + + */ +class Mage_Backup_Filesystem extends Mage_Backup_Abstract +{ + /** + * Paths that ignored when creating or rolling back snapshot + * + * @var array + */ + protected $_ignorePaths = array(); + + /** + * Whether use ftp account for rollback procedure + * + * @var bool + */ + protected $_useFtp = false; + + /** + * Ftp host + * + * @var string + */ + protected $_ftpHost; + + /** + * Ftp username + * + * @var string + */ + protected $_ftpUser; + + /** + * Password to ftp account + * + * @var string + */ + protected $_ftpPass; + + /** + * Ftp path to Magento installation + * + * @var string + */ + protected $_ftpPath; + + /** + * Implementation Rollback functionality for Filesystem + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_lastOperationSucceed = false; + + set_time_limit(0); + ignore_user_abort(true); + + $rollbackWorker = $this->_useFtp ? new Mage_Backup_Filesystem_Rollback_Ftp($this) + : new Mage_Backup_Filesystem_Rollback_Fs($this); + $rollbackWorker->run(); + + $this->_lastOperationSucceed = true; + } + + /** + * Implementation Create Backup functionality for Filesystem + * + * @throws Mage_Exception + * @return boolean + */ + public function create() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $this->_checkBackupsDir(); + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $filesInfo = $fsHelper->getInfo( + $this->getRootDir(), + Mage_Backup_Filesystem_Helper::INFO_READABLE | Mage_Backup_Filesystem_Helper::INFO_SIZE, + $this->getIgnorePaths() + ); + + if (!$filesInfo['readable']) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Not enough permissions to read files for backup'); + } + + $freeSpace = disk_free_space($this->getBackupsDir()); + + if (2 * $filesInfo['size'] > $freeSpace) { + throw new Mage_Backup_Exception_NotEnoughFreeSpace('Not enough free space to create backup'); + } + + $tarTmpPath = $this->_getTarTmpPath(); + + $tarPacker = new Mage_Backup_Archive_Tar(); + $tarPacker->setSkipFiles($this->getIgnorePaths()) + ->pack($this->getRootDir(), $tarTmpPath, true); + + if (!is_file($tarTmpPath) || filesize($tarTmpPath) == 0) { + throw new Mage_Exception('Failed to create backup'); + } + + $backupPath = $this->getBackupPath(); + + $gzPacker = new Mage_Archive_Gz(); + $gzPacker->pack($tarTmpPath, $backupPath); + + if (!is_file($backupPath) || filesize($backupPath) == 0) { + throw new Mage_Exception('Failed to create backup'); + } + + @unlink($tarTmpPath); + + $this->_lastOperationSucceed = true; + } + + /** + * Force class to use ftp for rollback procedure + * + * @param string $host + * @param string $username + * @param string $password + * @param string $path + * @return Mage_Backup_Filesystem + */ + public function setUseFtp($host, $username, $password, $path) + { + $this->_useFtp = true; + $this->_ftpHost = $host; + $this->_ftpUser = $username; + $this->_ftpPass = $password; + $this->_ftpPath = $path; + return $this; + } + + /** + * Get backup type + * + * @see Mage_Backup_Interface::getType() + * @return string + */ + public function getType() + { + return 'filesystem'; + } + + /** + * Add path that should be ignoring when creating or rolling back backup + * + * @param string|array $paths + * @return Mage_Backup_Filesystem + */ + public function addIgnorePaths($paths) + { + if (is_string($paths)) { + if (!in_array($paths, $this->_ignorePaths)) { + $this->_ignorePaths[] = $paths; + } + } + else if (is_array($paths)) { + foreach ($paths as $path) { + $this->addIgnorePaths($path); + } + } + + return $this; + } + + /** + * Get paths that should be ignored while creating or rolling back backup procedure + * + * @return array + */ + public function getIgnorePaths() + { + return $this->_ignorePaths; + } + + /** + * Set directory where backups saved and add it to ignore paths + * + * @see Mage_Backup_Abstract::setBackupsDir() + * @param string $backupsDir + * @return Mage_Backup_Filesystem + */ + public function setBackupsDir($backupsDir) + { + parent::setBackupsDir($backupsDir); + $this->addIgnorePaths($backupsDir); + return $this; + } + + /** + * getter for $_ftpPath variable + * + * @return string + */ + public function getFtpPath() + { + return $this->_ftpPath; + } + + /** + * Get ftp connection string + * + * @return string + */ + public function getFtpConnectString() + { + return 'ftp://' . $this->_ftpUser . ':' . $this->_ftpPass . '@' . $this->_ftpHost . $this->_ftpPath; + } + + /** + * Check backups directory existance and whether it's writeable + * + * @throws Mage_Exception + */ + protected function _checkBackupsDir() + { + $backupsDir = $this->getBackupsDir(); + + if (!is_dir($backupsDir)) { + $backupsDirParentDirectory = basename($backupsDir); + + if (!is_writeable($backupsDirParentDirectory)) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Cant create backups directory'); + } + + mkdir($backupsDir); + chmod($backupsDir, 0777); + } + + if (!is_writable($backupsDir)) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Backups directory is not writeable'); + } + } + + /** + * Generate tmp name for tarball + */ + protected function _getTarTmpPath() + { + $tmpName = '~tmp-'. microtime(true) . '.tar'; + return $this->getBackupsDir() . DS . $tmpName; + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Helper.php b/downloader/lib/Mage/Backup/Filesystem/Helper.php new file mode 100755 index 00000000..c62c4a0a --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Helper.php @@ -0,0 +1,137 @@ + + */ +class Mage_Backup_Filesystem_Helper +{ + /** + * Constant can be used in getInfo() function as second parameter. + * Check whether directory and all files/sub directories are writable + * + * @const int + */ + const INFO_WRITABLE = 1; + + /** + * Constant can be used in getInfo() function as second parameter. + * Check whether directory and all files/sub directories are readable + * + * @const int + */ + const INFO_READABLE = 2; + + /** + * Constant can be used in getInfo() function as second parameter. + * Get directory size + * + * @const int + */ + const INFO_SIZE = 4; + + /** + * Constant can be used in getInfo() function as second parameter. + * Combination of INFO_WRITABLE, INFO_READABLE, INFO_SIZE + * + * @const int + */ + const INFO_ALL = 7; + + /** + * Recursively delete $path + * + * @param string $path + * @param array $skipPaths + * @param bool $removeRoot + * @throws Mage_Exception + */ + public function rm($path, $skipPaths = array(), $removeRoot = false) + { + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $skipPaths); + + foreach ($iterator as $item) { + $item->isDir() ? @rmdir($item->__toString()) : @unlink($item->__toString()); + } + + if ($removeRoot && is_dir($path)) { + @rmdir($path); + } + } + + /** + * Get information (readable, writable, size) about $path + * + * @param string $path + * @param int $infoOptions + * @param array $skipFiles + */ + public function getInfo($path, $infoOptions = self::INFO_ALL, $skipFiles = array()) + { + $info = array(); + if ($infoOptions & self::INFO_READABLE) { + $info['readable'] = true; + } + + if ($infoOptions & self::INFO_WRITABLE) { + $info['writable'] = true; + } + + if ($infoOptions & self::INFO_SIZE) { + $info['size'] = 0; + } + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $skipFiles); + + foreach ($iterator as $item) { + if (($infoOptions & self::INFO_WRITABLE) && !$item->isWritable()) { + $info['writable'] = false; + } + + if (($infoOptions & self::INFO_READABLE) && !$item->isReadable()) { + $info['readable'] = false; + } + + if ($infoOptions & self::INFO_SIZE && !$item->isDir()) { + $info['size'] += $item->getSize(); + } + } + + return $info; + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php b/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php new file mode 100644 index 00000000..cddde360 --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Iterator/File.php @@ -0,0 +1,112 @@ + + */ +class Mage_Backup_Filesystem_Iterator_File extends SplFileObject +{ + /** + * The statement that was last read during iteration + * + * @var string + */ + protected $_currentStatement = ''; + + /** + * Return current sql statement + * + * @return string + */ + public function current() + { + return $this->_currentStatement; + } + + /** + * Iterate to next sql statement in file + */ + public function next() + { + $this->_currentStatement = ''; + while (!$this->eof()) { + $line = $this->fgets(); + if (strlen(trim($line))) { + $this->_currentStatement .= $line; + if ($this->_isLineLastInCommand($line)) { + break; + } + } + } + } + + /** + * Return to first statement + */ + public function rewind() + { + parent::rewind(); + $this->next(); + } + + /** + * Check whether provided string is comment + * + * @param string $line + * @return bool + */ + protected function _isComment($line) + { + return $line[0] == '#' || substr($line, 0, 2) == '--'; + } + + /** + * Check is line a last in sql command + * + * @param string $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0) { + $lastSymbolIndex = $lineLength - 1; + if ($cleanLine[$lastSymbolIndex] == ';') { + $returnResult = true; + } + } + + return $returnResult; + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php b/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php new file mode 100755 index 00000000..4b1b413d --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Iterator/Filter.php @@ -0,0 +1,77 @@ + + */ +class Mage_Backup_Filesystem_Iterator_Filter extends FilterIterator +{ + /** + * Array that is used for filtering + * + * @var array + */ + protected $_filters; + + /** + * Constructor + * + * @param Iterator $iterator + * @param array $filters list of files to skip + */ + public function __construct(Iterator $iterator, array $filters) + { + parent::__construct($iterator); + $this->_filters = $filters; + } + + /** + * Check whether the current element of the iterator is acceptable + * + * @return bool + */ + public function accept() + { + $current = $this->current()->__toString(); + $currentFilename = $this->current()->getFilename(); + + if ($currentFilename == '.' || $currentFilename == '..') { + return false; + } + + foreach ($this->_filters as $filter) { + if (false !== strpos($current, $filter)) { + return false; + } + } + + return true; + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Rollback/Abstract.php b/downloader/lib/Mage/Backup/Filesystem/Rollback/Abstract.php new file mode 100755 index 00000000..134b5fdb --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Rollback/Abstract.php @@ -0,0 +1,57 @@ + + */ +abstract class Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Snapshot object + * + * @var Mage_Backup_Filesystem + */ + protected $_snapshot; + + /** + * Default worker constructor + * + * @param Mage_Backup_Filesystem $snapshotObject + */ + public function __construct(Mage_Backup_Filesystem $snapshotObject) + { + $this->_snapshot = $snapshotObject; + } + + /** + * Main worker's function that makes files rollback + */ + abstract public function run(); +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Rollback/Fs.php b/downloader/lib/Mage/Backup/Filesystem/Rollback/Fs.php new file mode 100755 index 00000000..a98b89e6 --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Rollback/Fs.php @@ -0,0 +1,78 @@ + + */ +class Mage_Backup_Filesystem_Rollback_Fs extends Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Files rollback implementation via local filesystem + * + * @see Mage_Backup_Filesystem_Rollback_Abstract::run() + * @throws Mage_Exception + */ + public function run() + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + if (!is_file($snapshotPath) || !is_readable($snapshotPath)) { + throw new Mage_Backup_Exception_CantLoadSnapshot('Cant load snapshot archive'); + } + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $filesInfo = $fsHelper->getInfo( + $this->_snapshot->getRootDir(), + Mage_Backup_Filesystem_Helper::INFO_WRITABLE, + $this->_snapshot->getIgnorePaths() + ); + + if (!$filesInfo['writable']) { + throw new Mage_Backup_Exception_NotEnoughPermissions( + 'Unable to make rollback because not all files are writable' + ); + } + + $archiver = new Mage_Archive(); + + /** + * we need these fake initializations because all magento's files in filesystem will be deleted and autoloader + * wont be able to load classes that we need for unpacking + */ + new Mage_Archive_Tar(); + new Mage_Archive_Gz(); + new Mage_Archive_Helper_File(''); + new Mage_Archive_Helper_File_Gz(''); + + $fsHelper->rm($this->_snapshot->getRootDir(), $this->_snapshot->getIgnorePaths()); + $archiver->unpack($snapshotPath, $this->_snapshot->getRootDir()); + } +} diff --git a/downloader/lib/Mage/Backup/Filesystem/Rollback/Ftp.php b/downloader/lib/Mage/Backup/Filesystem/Rollback/Ftp.php new file mode 100755 index 00000000..5a180674 --- /dev/null +++ b/downloader/lib/Mage/Backup/Filesystem/Rollback/Ftp.php @@ -0,0 +1,194 @@ + + */ +class Mage_Backup_Filesystem_Rollback_Ftp extends Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Ftp client + * + * @var Mage_System_Ftp + */ + protected $_ftpClient; + + /** + * Files rollback implementation via ftp + * + * @see Mage_Backup_Filesystem_Rollback_Abstract::run() + * @throws Mage_Exception + */ + public function run() + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + if (!is_file($snapshotPath) || !is_readable($snapshotPath)) { + throw new Mage_Backup_Exception_CantLoadSnapshot('Cant load snapshot archive'); + } + + $this->_initFtpClient(); + $this->_validateFtp(); + + $tmpDir = $this->_createTmpDir(); + $this->_unpackSnapshot($tmpDir); + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $this->_cleanupFtp(); + $this->_uploadBackupToFtp($tmpDir); + + $fsHelper->rm($tmpDir, array(), true); + } + + /** + * Initialize ftp client and connect to ftp + * + * @throws Mage_Backup_Exception_FtpConnectionFailed + */ + protected function _initFtpClient() + { + try { + $this->_ftpClient = new Mage_System_Ftp(); + $this->_ftpClient->connect($this->_snapshot->getFtpConnectString()); + } catch (Exception $e) { + throw new Mage_Backup_Exception_FtpConnectionFailed($e->getMessage()); + } + } + + /** + * Perform ftp validation. Check whether ftp account provided points to current magento installation + * + * @throws Mage_Exception + */ + protected function _validateFtp() + { + $validationFilename = '~validation-' . microtime(true) . '.tmp'; + $validationFilePath = $this->_snapshot->getBackupsDir() . DS . $validationFilename; + + $fh = @fopen($validationFilePath, 'w'); + @fclose($fh); + + if (!is_file($validationFilePath)) { + throw new Mage_Exception('Unable to validate ftp account'); + } + + $rootDir = $this->_snapshot->getRootDir(); + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($rootDir, '', $validationFilePath); + + $fileExistsOnFtp = $this->_ftpClient->fileExists($ftpPath); + @unlink($validationFilePath); + + if (!$fileExistsOnFtp) { + throw new Mage_Backup_Exception_FtpValidationFailed('Failed to validate ftp account'); + } + } + + /** + * Unpack snapshot + * + * @param string $tmpDir + */ + protected function _unpackSnapshot($tmpDir) + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + $archiver = new Mage_Archive(); + $archiver->unpack($snapshotPath, $tmpDir); + } + + /** + * @throws Mage_Exception + * @return string + */ + protected function _createTmpDir() + { + $tmpDir = $this->_snapshot->getBackupsDir() . DS . '~tmp-' . microtime(true); + + $result = @mkdir($tmpDir); + + if (false === $result) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Failed to create directory ' . $tmpDir); + } + + return $tmpDir; + } + + /** + * Delete magento and all files from ftp + */ + protected function _cleanupFtp() + { + $rootDir = $this->_snapshot->getRootDir(); + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($rootDir), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_snapshot->getIgnorePaths()); + + foreach ($iterator as $item) { + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($rootDir, '', $item->__toString()); + $ftpPath = str_replace(DS, '/', $ftpPath); + + $this->_ftpClient->delete($ftpPath); + } + } + + /** + * Perform files rollback + * + * @param string $tmpDir + * @throws Mage_Exception + */ + protected function _uploadBackupToFtp($tmpDir) + { + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($tmpDir), RecursiveIteratorIterator::SELF_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_snapshot->getIgnorePaths()); + + foreach ($filesystemIterator as $item) { + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($tmpDir, '', $item->__toString()); + $ftpPath = str_replace(DS, '/', $ftpPath); + + if ($item->isDir()) { + $this->_ftpClient->mkdirRecursive($ftpPath); + } else { + $result = $this->_ftpClient->put($ftpPath, $item->__toString()); + if (false === $result) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Failed to upload file ' + . $item->__toString() . ' to ftp'); + } + } + } + } +} diff --git a/downloader/lib/Mage/Backup/Interface.php b/downloader/lib/Mage/Backup/Interface.php new file mode 100755 index 00000000..46cf6eea --- /dev/null +++ b/downloader/lib/Mage/Backup/Interface.php @@ -0,0 +1,88 @@ + + */ +interface Mage_Backup_Interface +{ + /** + * Create Backup + * + * @return boolean + */ + public function create(); + + /** + * Rollback Backup + * + * @return boolean + */ + public function rollback(); + + /** + * Set Backup Extension + * + * @param string $backupExtension + * @return Mage_Backup_Interface + */ + public function setBackupExtension($backupExtension); + + /** + * Set Resource Model + * + * @param object $resourceModel + * @return Mage_Backup_Interface + */ + public function setResourceModel($resourceModel); + + /** + * Set Time + * + * @param int $time + * @return Mage_Backup_Interface + */ + public function setTime($time); + + /** + * Get Backup Type + * + * @return string + */ + public function getType(); + + /** + * Set path to directory where backups stored + * + * @param string $backupsDir + * @return Mage_Backup_Interface + */ + public function setBackupsDir($backupsDir); +} diff --git a/downloader/lib/Mage/Backup/Media.php b/downloader/lib/Mage/Backup/Media.php new file mode 100644 index 00000000..a6014d44 --- /dev/null +++ b/downloader/lib/Mage/Backup/Media.php @@ -0,0 +1,99 @@ + + */ +class Mage_Backup_Media extends Mage_Backup_Snapshot +{ + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_prepareIgnoreList(); + return parent::rollback(); + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_prepareIgnoreList(); + return parent::create(); + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'media'; + } + + /** + * Add all folders and files except media and db backup to ignore list + * + * @return Mage_Backup_Media + */ + protected function _prepareIgnoreList() + { + $iterator = new DirectoryIterator($this->getRootDir()); + + foreach ($iterator as $item) { + $filename = $item->getFilename(); + if (!in_array($filename, array('media', 'var'))) { + $this->addIgnorePaths($item->getPathname()); + } + } + + $iterator = new DirectoryIterator($this->getRootDir() . DS . 'var'); + $dbBackupFilename = $this->_getDbBackupManager()->getBackupFilename(); + + foreach ($iterator as $item) { + $filename = $item->getFilename(); + if ($filename != $dbBackupFilename) { + $this->addIgnorePaths($item->getPathname()); + } + } + + return $this; + } +} diff --git a/downloader/lib/Mage/Backup/Nomedia.php b/downloader/lib/Mage/Backup/Nomedia.php new file mode 100644 index 00000000..0e379d1a --- /dev/null +++ b/downloader/lib/Mage/Backup/Nomedia.php @@ -0,0 +1,82 @@ + + */ +class Mage_Backup_Nomedia extends Mage_Backup_Snapshot +{ + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_prepareIgnoreList(); + return parent::rollback(); + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_prepareIgnoreList(); + return parent::create(); + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'nomedia'; + } + + /** + * Add media folder to ignore list + * + * @return Mage_Backup_Media + */ + protected function _prepareIgnoreList() + { + $this->addIgnorePaths($this->getRootDir() . DS . 'media'); + + return $this; + } +} diff --git a/downloader/lib/Mage/Backup/Snapshot.php b/downloader/lib/Mage/Backup/Snapshot.php new file mode 100755 index 00000000..7c2fbd75 --- /dev/null +++ b/downloader/lib/Mage/Backup/Snapshot.php @@ -0,0 +1,140 @@ + + */ +class Mage_Backup_Snapshot extends Mage_Backup_Filesystem +{ + /** + * Database backup manager + * + * @var Mage_Backup_Db + */ + protected $_dbBackupManager; + + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $result = parent::rollback(); + + $this->_lastOperationSucceed = false; + + try { + $this->_getDbBackupManager()->rollback(); + } catch (Exception $e) { + $this->_removeDbBackup(); + throw $e; + } + + $this->_removeDbBackup(); + $this->_lastOperationSucceed = true; + + return $result; + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_getDbBackupManager()->create(); + + try { + $result = parent::create(); + } catch (Exception $e) { + $this->_removeDbBackup(); + throw $e; + } + + $this->_lastOperationSucceed = false; + $this->_removeDbBackup(); + $this->_lastOperationSucceed = true; + + return $result; + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'snapshot'; + } + + /** + * Create Db Instance + * + * @return Mage_Backup_Interface + */ + protected function _createDbBackupInstance() + { + return Mage_Backup::getBackupInstance(Mage_Backup_Helper_Data::TYPE_DB) + ->setBackupExtension(Mage::helper('backup')->getExtensionByType(Mage_Backup_Helper_Data::TYPE_DB)) + ->setTime($this->getTime()) + ->setBackupsDir(Mage::getBaseDir("var")) + ->setResourceModel($this->getResourceModel()); + } + + /** + * Get database backup manager + * + * @return Mage_Backup_Db + */ + protected function _getDbBackupManager() + { + if (is_null($this->_dbBackupManager)) { + $this->_dbBackupManager = $this->_createDbBackupInstance(); + } + + return $this->_dbBackupManager; + } + + /** + * Remove Db backup after added it to the snapshot + * + * @return Mage_Backup_Snapshot + */ + protected function _removeDbBackup(){ + @unlink($this->_getDbBackupManager()->getBackupPath()); + return $this; + } +} diff --git a/downloader/lib/Mage/Connect/Channel/Generator.php b/downloader/lib/Mage/Connect/Channel/Generator.php new file mode 100644 index 00000000..8870dc04 --- /dev/null +++ b/downloader/lib/Mage/Connect/Channel/Generator.php @@ -0,0 +1,63 @@ +_file = $file; + } + return $this; + } + + public function getFile() + { + return $this->_file; + } + + public function getGenerator() + { + if (is_null($this->_generator)) { + $this->_generator = new Mage_Xml_Generator(); + } + return $this->_generator; + } + + /** + * @param array $content + */ + public function save($content) + { + $xmlContent = $this->getGenerator() + ->arrayToXml($content) + ->save($this->getFile()); + return $this; + } +} diff --git a/downloader/lib/Mage/Connect/Channel/Parser.php b/downloader/lib/Mage/Connect/Channel/Parser.php new file mode 100644 index 00000000..faa86fc9 --- /dev/null +++ b/downloader/lib/Mage/Connect/Channel/Parser.php @@ -0,0 +1,25 @@ + '', + 'uri' => '', + 'summary' => '', + ); + + public function rewind() { + reset($this->properties); + } + + public function valid() { + return current($this->properties) !== false; + } + + public function key() { + return key($this->properties); + } + + public function current() { + return current($this->properties); + } + + public function next() { + next($this->properties); + } + + public function __get($var) + { + if (isset($this->properties[$var])) { + return $this->properties[$var]; + } + return null; + } + + public function __set($var, $value) + { + if (is_string($value)) { + $value = trim($value); + } + if (isset($this->properties[$var])) { + if ($value === null) { + $value = ''; + } + $this->properties[$var] = $value; + } + } + + public function toArray() + { + return array('channel' => $this->properties); + } + + public function fromArray(array $arr) + { + foreach($arr as $k=>$v) { + $this->$k = $v; + } + } + + + private function validator() + { + if(is_null($this->_validator)) { + $this->_validator = new Mage_Connect_Validator(); + } + return $this->_validator; + } + + /** + Stub for validation result + */ + public function validate() + { + $v = $this->validator(); + if(!$v->validatePackageName($this->name)) { + return false; + } + return true; + } + +} diff --git a/downloader/lib/Mage/Connect/Command.php b/downloader/lib/Mage/Connect/Command.php new file mode 100644 index 00000000..191fb2e6 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command.php @@ -0,0 +1,443 @@ + + */ +class Mage_Connect_Command +{ + /** + * All commands list + * @var array + */ + protected static $_commandsAll = array(); + + /** + * Commands list hash (key=class) + * @var array + */ + protected static $_commandsByClass = array(); + + /** + * Frontend object + * @var Mage_Connect_Fro + */ + protected static $_frontend = null; + + /** + * Connect Config instance + * @var Mage_Connect_Config + */ + protected static $_config = null; + /** + * Validator instance + * + * @var Mage_Connect_Validator + */ + protected static $_validator = null; + + /** + * Rest instance + * + * @var Mage_Connect_Rest + */ + protected static $_rest = null; + + /** + * Cache config instance + * + * @var Mage_Connect_Singleconfig + */ + protected static $_sconfig = null; + + /** + * Called class name + * + * @var string + */ + protected $_class; + + /** + * Packager instance + * + * @var Mage_Connect_Packager + */ + protected static $_packager = null; + + protected static $_return = array(); + + /** + * Constructor + */ + public function __construct() + { + $class = $this->_class = get_class($this); + if(__CLASS__ == $class) { + throw new Exception("You shouldn't instantiate {$class} directly!"); + } + $this->commandsInfo = self::$_commandsByClass[$class]; + } + + /** + * Get command info (static) + * + * @param string $name command name + * @return array|boolean + */ + public static function commandInfo($name) + { + $name = strtolower($name); + if(!isset(self::$_commandsAll[$name])) { + return false; + } + return self::$_commandsAll[$name]; + } + + /** + * Get command info for current command object + * + * @param string $name + * @return array|boolean + */ + public function getCommandInfo($name) + { + if(!isset(self::$_commandsByClass[$this->_class][$name])) { + return false; + } + return self::$_commandsByClass[$this->_class][$name]; + } + + /** + * Run command + * + * @param string $command + * @param string $options + * @param string $params + * @throws Exception if there's no needed method + * @return mixed + */ + public function run($command, $options, $params) + { + $data = $this->getCommandInfo($command); + $method = $data['function']; + if(! method_exists($this, $method)) { + throw new Exception("$method does't exist in class ".$this->_class); + } + return $this->$method($command, $options, $params); + } + + /** + * Static functions + */ + + /** + * Static + * @param $commandName + * @return unknown_type + */ + public static function getInstance($commandName) + { + if(!isset(self::$_commandsAll[$commandName])) { + throw new UnexpectedValueException("Cannot find command $commandName"); + } + $currentCommand = self::$_commandsAll[$commandName]; + return new $currentCommand['class'](); + } + + /** + * Cache config setter + * + * @static + * @param Mage_Connect_Singleconfig $obj + * @return null + */ + public static function setSconfig($obj) + { + self::$_sconfig = $obj; + } + + /** + * Cache config getter + * + * @return Mage_Connect_Singleconfig + */ + public function getSconfig() + { + return self::$_sconfig; + } + + /** + * Sets frontend object for all commands + * + * @param Mage_Connect_Frontend $obj + * @return null + */ + public static function setFrontendObject($obj) + { + self::$_frontend = $obj; + } + + /** + * Set config object for all commands + * + * @param Mage_Connect_Config $obj + * @return null + */ + public static function setConfigObject($obj) + { + self::$_config = $obj; + } + + /** + * Non-static getter for config + * + * @return Mage_Connect_Config + */ + public function config() + { + return self::$_config; + } + + /** + * Non-static getter for UI + * + * @return Mage_Connect_Frontend + */ + public function ui() + { + return self::$_frontend; + } + + /** + * Get validator object + * + * @return Mage_Connect_Validator + */ + public function validator() + { + if(is_null(self::$_validator)) { + self::$_validator = new Mage_Connect_Validator(); + } + return self::$_validator; + } + + /** + * Get rest object + * + * @return Mage_Connect_Rest + */ + public function rest() + { + if(is_null(self::$_rest)) { + self::$_rest = new Mage_Connect_Rest(self::config()->protocol); + } + return self::$_rest; + } + + /** + * Get commands list sorted + * + * @return array + */ + public static function getCommands() + { + if(!count(self::$_commandsAll)) { + self::registerCommands(); + } + ksort(self::$_commandsAll); + return self::$_commandsAll; + } + + /** + * Get Getopt args from command definitions + * and parse them + * + * @param $command + * @return array + */ + public static function getGetoptArgs($command) + { + $commandInfo = self::commandInfo($command); + $short_args = ''; + $long_args = array(); + if (empty($commandInfo) || empty($commandInfo['options'])) { + return; + } + reset($commandInfo['options']); + while (list($option, $info) = each($commandInfo['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + } else { + $larg = '='; + $sarg = ':'; + } + } + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + $long_args[] = $option . $larg; + } + return array($short_args, $long_args); + } + + /** + * Try to register commands automatically + * + * @return null + */ + public static function registerCommands() + { + $pathCommands = dirname(__FILE__).DIRECTORY_SEPARATOR.basename(__FILE__, ".php"); + $f = new DirectoryIterator($pathCommands); + foreach($f as $file) { + /** @var $file DirectoryIterator */ + if (! $file->isFile()) { + continue; + } + $pattern = preg_match("/(.*)_Header\.php/imsu", $file->getFilename(), $matches); + if(! $pattern) { + continue; + } + include($file->getPathname()); + if(! isset($commands)) { + continue; + } + $class = __CLASS__."_".$matches[1]; + foreach ($commands as $k=>$v) { + $commands[$k]['class'] = $class; + self::$_commandsAll[$k] = $commands[$k]; + } + self::$_commandsByClass[$class] = $commands; + } + } + + /** + * Add Error message + * + * @param string $command + * @param string $message + * @return null + */ + public function doError($command, $message) + { + return $this->ui()->doError($command, $message); + } + + /** + * Set command return + * + * @param string $key + * @param mixed $val + * @return null + */ + public static function setReturn($key, $val) + { + self::$_return[$key] = $val; + } + + /** + * Get command return + * + * @param $key + * @param $clear + * @return mixed + */ + public static function getReturn($key, $clear = true) + { + if(isset(self::$_return[$key])) { + $out = self::$_return[$key]; + if($clear) { + unset(self::$_return[$key]); + } + return $out; + } + return null; + } + + /** + * Cleanup command params from empty strings + * + * @param array $params by reference + */ + public function cleanupParams(array & $params) + { + $newParams = array(); + if(!count($params)) { + return; + } + foreach($params as $v) { + if(is_string($v)) { + $v = trim($v); + if(!strlen($v)) { + continue; + } + } + $newParams[] = $v; + } + $params = $newParams; + } + + /** + * Splits first command argument: channel/package + * to two arguments if found in top of array + * + * @param array $params + */ + public function splitPackageArgs(array & $params) + { + if(!count($params) || !isset($params[0])) { + return; + } + if($this->validator()->validateUrl($params[0])) { + return; + } + if(preg_match("@([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)@ims", $params[0], $subs)) { + $params[0] = $subs[2]; + array_unshift($params, $subs[1]); + } + } + + + /** + * Get packager instance + * + * @return Mage_Connect_Packager + */ + public function getPackager() + { + if(!self::$_packager) { + self::$_packager = new Mage_Connect_Packager(); + } + return self::$_packager; + } +} diff --git a/downloader/lib/Mage/Connect/Command/Channels.php b/downloader/lib/Mage/Connect/Command/Channels.php new file mode 100644 index 00000000..1ed6edfe --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Channels.php @@ -0,0 +1,189 @@ +getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $data = $cache->getData(); + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $data = $cache->getData(); + } + $out = array($command => array('data'=>$data, 'title'=>$title, 'title_aliases'=>$aliasT)); + $this->ui()->output($out); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * channel-delete callback method + * @param string $command + * @param array $options + * @param array $params + */ + public function doDelete($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 1) { + throw new Exception("Parameters count should be equal to 1"); + } + $packager = $this->getPackager(); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->deleteChannel($params[0]); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $config = $this->config(); + $cache = $this->getSconfig(); + $cache->deleteChannel($params[0]); + } + $this->ui()->output("Successfully deleted"); + + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Channel-add callback + * @param string $command + * @param array $options + * @param array $params + */ + public function doAdd($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 1) { + throw new Exception("Parameters count should be equal to 1"); + } + $url = $params[0]; + $rest = $this->rest(); + $rest->setChannel($url); + $data = $rest->getChannelInfo(); + $data->url = $url; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->addChannel($data->name, $url); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $cache->addChannel($data->name, $url); + } + + $this->ui()->output("Successfully added: ".$url); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Get information about given channel callback + * @param string $command + * @param array $options + * @param array $params + */ + public function doInfo($command, $options, $params) + { + + } + + /** + * channel-alias + * @param $command + * @param $options + * @param $params + * @return unknown_type + */ + public function doAlias($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 2) { + throw new Exception("Parameters count should be equal to 2"); + } + + $packager = $this->getPackager(); + $chanUrl = $params[0]; + $alias = $params[1]; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->addChannelAlias($chanUrl, $alias); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $cache->addChannelAlias($chanUrl, $alias); + } + $this->ui()->output("Successfully added: ".$alias); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + public function doLogin($command, $options, $params) + { + + } + + public function doLogout($command, $options, $params) + { + + } + +} diff --git a/downloader/lib/Mage/Connect/Command/Channels_Header.php b/downloader/lib/Mage/Connect/Command/Channels_Header.php new file mode 100644 index 00000000..69d03141 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Channels_Header.php @@ -0,0 +1,104 @@ + array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => ' +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => ' +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => ' +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +Logs out from a remote channel server. If is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); diff --git a/downloader/lib/Mage/Connect/Command/Config.php b/downloader/lib/Mage/Connect/Command/Config.php new file mode 100644 index 00000000..2f4972c8 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Config.php @@ -0,0 +1,216 @@ + + */ +class Mage_Connect_Command_Config extends Mage_Connect_Command +{ + /** + * Parameters constants + */ + const PARAM_KEY = 0; + const PARAM_VAL = 1; + + /** + * Show config variable + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigShow($command, $options, $params) + { + $this->cleanupParams($params); + + try { + $values = array(); + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + foreach( $config as $k=>$v ) { + $values[$k] = $v; + } + if($ftp) { + @unlink($config->getFilename()); + } + $data = array($command => array('data'=>$values)); + $this->ui()->output($data); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Set config variable + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigSet($command, $options, $params) + { + $this->cleanupParams($params); + + try { + if(count($params) < 2) { + throw new Exception("Parameters count should be >= 2"); + } + $key = strtolower($params[self::PARAM_KEY]); + $val = strval($params[self::PARAM_VAL]); + $packager = $this->getPackager(); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if(!$ftp) { + $config = $this->config(); + $ftp=$config->remote_config; + } + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } + + if(!$config->hasKey($key)) { + throw new Exception ("No such config variable: {$key}!"); + } + if(!$config->validate($key, $val)) { + $possible = $this->config()->possible($key); + $type = $this->config()->type($key); + $errString = "Invalid value specified for $key!"; + throw new Exception($errString); + } + if($ftp) { + $packager->writeToRemoteConfig($config, $ftpObj); + } + $this->config()->$key = $val; + $this->ui()->output('Success'); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Get config var + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigGet($command, $options, $params) + { + $this->cleanupParams($params); + + try { + if(count($params) < 1) { + throw new Exception("Parameters count should be >= 1"); + } + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + $key = strtolower($params[self::PARAM_KEY]); + if(!$config->hasKey($key)) { + throw new Exception("No such config variable '{$key}'!"); + } + if($ftp) { + @unlink($config->getFilename()); + } + $this->ui()->output($config->$key); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Config help + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigHelp($command, $options, $params) + { + try { + $this->cleanupParams($params); + if(count($params) < 1) { + throw new Exception( "Parameters count should be >= 1"); + } + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + + $key = strtolower($params[self::PARAM_KEY]); + if(!$this->config()->hasKey($key)) { + throw new Exception("No such config variable '{$key}'!"); + } + + $possible = $config->possible($key); + $type = $config->type($key); + $doc = $config->doc($key); + if($ftp) { + @unlink($config->getFilename()); + } + $data = array(); + $data[$command]['data'] = array( + 'name' => array('Variable name', $key), + 'type' => array('Value type', $type), + 'possible' => array('Possible values', $possible), + 'doc' => $doc, + ); + $this->ui()->output($data); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } +} diff --git a/downloader/lib/Mage/Connect/Command/Config_Header.php b/downloader/lib/Mage/Connect/Command/Config_Header.php new file mode 100644 index 00000000..c763068d --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Config_Header.php @@ -0,0 +1,100 @@ + array( + 'summary' => 'Show All Settings', + 'function' => 'doConfigShow', + 'shortcut' => 'csh', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => '[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +', +), + 'config-get' => array( + 'summary' => 'Show One Setting', + 'function' => 'doConfigGet', + 'shortcut' => 'cg', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => ' [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +', +), + 'config-set' => array( + 'summary' => 'Change Setting', + 'function' => 'doConfigSet', + 'shortcut' => 'cs', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => ' [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +', +), + 'config-help' => array( + 'summary' => 'Show Information About Setting', + 'function' => 'doConfigHelp', + 'shortcut' => 'ch', + 'options' => array(), + 'doc' => '[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +', +), +); diff --git a/downloader/lib/Mage/Connect/Command/Install.php b/downloader/lib/Mage/Connect/Command/Install.php new file mode 100644 index 00000000..bfaf88ac --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Install.php @@ -0,0 +1,546 @@ +cleanupParams($params); + + $installFileMode = $command === 'install-file'; + + /** @var $ftpObj Mage_Connect_Ftp */ + $ftpObj=null; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + /** @var $packager Mage_Connect_Packager */ + $packager = $this->getPackager(); + /** @var $cache Mage_Connect_Singleconfig */ + /** @var $config Mage_Connect_Config */ + if ($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + try { + $forceMode = isset($options['force']); + $upgradeAllMode = $command == 'upgrade-all'; + $upgradeMode = $command == 'upgrade' || $command == 'upgrade-all'; + $noFilesInstall = isset($options['nofiles']); + $withDepsMode = !isset($options['nodeps']); + $ignoreModifiedMode = true || !isset($options['ignorelocalmodification']); + $clearInstallMode = $command == 'install' && !$forceMode; + $installAll = isset($options['install_all']); + $channelAuth = isset($options['auth'])?$options['auth']:array(); + + $rest = $this->rest(); + if (empty($config->magento_root)) { + $config->magento_root=dirname(dirname($_SERVER['SCRIPT_FILENAME'])); + } + chdir($config->magento_root); + $dirCache = DIRECTORY_SEPARATOR . $config->downloader_path . DIRECTORY_SEPARATOR + . Mage_Connect_Config::DEFAULT_CACHE_PATH; + $dirTmp = DIRECTORY_SEPARATOR . Mage_Connect_Package_Reader::PATH_TO_TEMPORARY_DIRECTORY; + $dirMedia = DIRECTORY_SEPARATOR . 'media'; + $isWritable = true; + if ($ftp) { + $cwd=$ftpObj->getcwd(); + $ftpObj->mkdirRecursive($cwd . $dirCache,0777); + $ftpObj->chdir($cwd); + $ftpObj->mkdirRecursive($cwd . $dirTmp,0777); + $ftpObj->chdir($cwd); + $ftpObj->mkdirRecursive($cwd . $dirMedia,0777); + $ftpObj->chdir($cwd); + $err = "Please check for sufficient ftp write file permissions."; + } else { + @mkdir($config->magento_root . $dirCache,0777,true); + @mkdir($config->magento_root . $dirTmp,0777,true); + @mkdir($config->magento_root . $dirMedia,0777,true); + $isWritable = is_writable($config->magento_root) + && is_writable($config->magento_root . DIRECTORY_SEPARATOR . $config->downloader_path) + && is_writable($config->magento_root . $dirCache) + && is_writable($config->magento_root . $dirTmp) + && is_writable($config->magento_root . $dirMedia); + $err = "Please check for sufficient write file permissions."; + } + $isWritable = $isWritable && is_writable($config->magento_root . $dirMedia) + && is_writable($config->magento_root . $dirCache) + && is_writable($config->magento_root . $dirTmp); + if (!$isWritable) { + $this->doError($command, $err); + throw new Exception( + 'Your Magento folder does not have sufficient write permissions, which downloader requires.' + ); + } + if (!empty($channelAuth)) { + $rest->getLoader()->setCredentials($channelAuth['username'], $channelAuth['password']); + } + + if ($installFileMode) { + if (count($params) < 1) { + throw new Exception("Argument should be: filename"); + } + $filename = $params[0]; + if (!@file_exists($filename)) { + throw new Exception("File '{$filename}' not found"); + } + if (!@is_readable($filename)) { + throw new Exception("File '{$filename}' is not readable"); + } + + $package = new Mage_Connect_Package($filename); + $package->setConfig($config); + $package->validate(); + $errors = $package->getErrors(); + if (count($errors)) { + throw new Exception("Package file is invalid\n" . implode("\n", $errors)); + } + + $pChan = $package->getChannel(); + $pName = $package->getName(); + $pVer = $package->getVersion(); + + if (!($cache->isChannelName($pChan) || $cache->isChannelAlias($pChan))) { + throw new Exception("The '{$pChan}' channel is not installed. Please use the MAGE shell " + . "script to install the '{$pChan}' channel."); + } + + $conflicts = $cache->hasConflicts($pChan, $pName, $pVer); + + if (false !== $conflicts) { + $conflicts = implode(", ",$conflicts); + if ($forceMode) { + $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: " . $conflicts); + } else { + throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: " . $conflicts); + } + } + + $conflicts = $package->checkPhpDependencies(); + if (true !== $conflicts) { + $conflicts = implode(",",$conflicts); + $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: " . $conflicts; + if ($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + $conflicts = $package->checkPhpVersion(); + if (true !== $conflicts) { + $err = "Package {$pChan}/{$pName} {$pVer}: " . $conflicts; + if ($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + if (!$noFilesInstall) { + if ($ftp) { + $packager->processInstallPackageFtp($package, $filename, $config, $ftpObj); + } else { + $packager->processInstallPackage($package, $filename, $config); + } + } + $cache->addPackage($package); + $installedDeps = array(); + $installedDepsAssoc = array(); + $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer); + $installedDeps[] = array($pChan, $pName, $pVer); + + $title = isset($options['title']) ? $options['title'] : "Package installed: "; + $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc, 'title'=>$title)); + + if ($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + + $this->ui()->output($out); + return $out[$command]['data']; + } + + if (!$upgradeAllMode) { + if (count($params) < 2) { + throw new Exception("Argument should be: channelName packageName"); + } + $channel = $params[0]; + $package = $params[1]; + $argVersionMax = isset($params[2]) ? $params[2]: false; + $argVersionMin = isset($params[3]) ? $params[3]: false; + + $cache->checkChannel($channel, $config, $rest); + $channelName = $cache->chanName($channel); + $this->ui()->output("Checking dependencies of packages"); + $packagesToInstall = $packager->getDependenciesList($channelName, $package, $cache, $config, + $argVersionMax, $argVersionMin, $withDepsMode, false, $rest + ); + /* + * process 'failed' results + */ + if (count($packagesToInstall['failed'])) { + $showError=!count($packagesToInstall['result']); + foreach ($packagesToInstall['failed'] as $failed) { + $msg="Package {$failed['channel']}/{$failed['name']} failed: " . $failed['reason']; + if ($showError) { + $this->doError($command, $msg); + } else { + $this->ui()->output($msg); + } + } + } + $packagesToInstall = $packagesToInstall['result']; + } else { + if (empty($params[0])) { + $channels = $cache->getChannelNames(); + } else { + $channel = $params[0]; + if (!$cache->isChannel($channel)) { + throw new Exception("'{$channel}' is not existant channel name / valid uri"); + } + $channels = $cache->chanName($channel); + } + $packagesToInstall = array(); + $neededToUpgrade = $packager->getUpgradesList($channels, $cache, $config, $rest); + foreach ($neededToUpgrade as $chan=>$packages) { + foreach ($packages as $name=>$data) { + $versionTo = $data['to']; + $tmp = $packager->getDependenciesList($chan, $name, $cache, $config, $versionTo, $versionTo, + $withDepsMode, false, $rest + ); + if (count($tmp['result'])) { + $packagesToInstall = array_merge($packagesToInstall, $tmp['result']); + } + } + } + } + + /** + * Make installation + */ + $installedDeps = array(); + $installedDepsAssoc = array(); + + foreach ($packagesToInstall as $package) { + try { + $pName = $package['name']; + $pChan = $package['channel']; + $pVer = $package['downloaded_version']; + $pInstallState = $package['install_state']; + $rest->setChannel($cache->chanUrl($pChan)); + + /** + * Skip existing packages + */ + if ($upgradeMode && $cache->hasPackage($pChan, $pName, $pVer, $pVer) + || ('already_installed' == $pInstallState && !$forceMode) + ) { + $this->ui()->output("Already installed: {$pChan}/{$pName} {$pVer}, skipping"); + continue; + } + + if ('incompartible' == $pInstallState) { + $this->ui()->output( + "Package incompartible with installed Magento: {$pChan}/{$pName} {$pVer}, skipping" + ); + continue; + } + + $conflicts = $cache->hasConflicts($pChan, $pName, $pVer); + + if (false !== $conflicts) { + $conflicts = implode(", ",$conflicts); + if ($forceMode) { + $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: " . $conflicts); + } else { + throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: " . $conflicts); + } + } + + /** + * Modifications + */ + if (($upgradeMode || ($pInstallState == 'upgrade')) && !$ignoreModifiedMode) { + if ($ftp) { + $modifications = $packager->getRemoteModifiedFiles($pChan, $pName, $cache, $config, $ftp); + } else { + $modifications = $packager->getLocalModifiedFiles($pChan, $pName, $cache, $config); + } + if (count($modifications) > 0) { + $this->ui()->output('Changed locally: '); + foreach ($modifications as $row) { + if (!$ftp) { + $this->ui()->output($config->magento_root . DS . $row); + } else { + $this->ui()->output($row); + } + } + } + } + + if ($ftp) { + $cwd=$ftpObj->getcwd(); + $dir=$cwd . DIRECTORY_SEPARATOR .$config->downloader_path . DIRECTORY_SEPARATOR + . Mage_Connect_Config::DEFAULT_CACHE_PATH . DIRECTORY_SEPARATOR . trim( $pChan, "\\/"); + $ftpObj->mkdirRecursive($dir,0777); + $ftpObj->chdir($cwd); + } else { + $dir = $config->getChannelCacheDir($pChan); + @mkdir($dir, 0777, true); + } + $dir = $config->getChannelCacheDir($pChan); + $packageFileName = $pName . "-" . $pVer . ".tgz"; + $file = $dir . DIRECTORY_SEPARATOR . $packageFileName; + if (!@file_exists($file)) { + $this->ui()->output("Starting to download $packageFileName ..."); + $rest->downloadPackageFileOfRelease($pName, $pVer, $file); + $this->ui()->output(sprintf("...done: %s bytes", number_format(filesize($file)))); + } + + /** + * Remove old version package before install new + */ + if ($cache->hasPackage($pChan, $pName)) { + if ($ftp) { + $packager->processUninstallPackageFtp($pChan, $pName, $cache, $ftpObj); + } else { + $packager->processUninstallPackage($pChan, $pName, $cache, $config); + } + $cache->deletePackage($pChan, $pName); + } + + $package = new Mage_Connect_Package($file); + if ($clearInstallMode && $pInstallState != 'upgrade' && !$installAll) { + $this->validator()->validateContents($package->getContents(), $config); + $errors = $this->validator()->getErrors(); + if (count($errors)) { + throw new Exception("Package '{$pName}' is invalid\n" . implode("\n", $errors)); + } + } + + $conflicts = $package->checkPhpDependencies(); + if (true !== $conflicts) { + $conflicts = implode(",",$conflicts); + $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: " . $conflicts; + if ($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + $conflicts = $package->checkPhpVersion(); + if (true !== $conflicts) { + $err = "Package {$pChan}/{$pName} {$pVer}: " . $conflicts; + if ($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + if (!$noFilesInstall) { + $this->ui()->output("Installing package {$pChan}/{$pName} {$pVer}"); + if ($ftp) { + $packager->processInstallPackageFtp($package, $file, $config, $ftpObj); + } else { + $packager->processInstallPackage($package, $file, $config); + } + $this->ui()->output("Package {$pChan}/{$pName} {$pVer} installed successfully"); + } + $cache->addPackage($package); + + $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer); + $installedDeps[] = array($pChan, $pName, $pVer); + + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + $title = isset($options['title']) ? $options['title'] : "Package installed: "; + $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc, 'title'=>$title)); + + if ($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + + $this->ui()->output($out); + return $out[$command]['data']; + } catch (Exception $e) { + if ($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Upgrade action callback + * + * @param string $command + * @param array $options + * @param array $params + * @return array|null + */ + public function doUpgrade($command, $options, $params) + { + $options['title'] = "Package upgraded: "; + return $this->doInstall($command, $options, $params); + } + + /** + * Updgrade action callback + * + * @param string $command + * @param array $options + * @param array $params + * @return array|null + */ + public function doUpgradeAll($command, $options, $params) + { + $options['title'] = "Package upgraded: "; + return $this->doInstall($command, $options, $params); + } + + /** + * Uninstall package callback + * + * @param string $command + * @param array $options + * @param array $params + * @return array|null + */ + public function doUninstall($command, $options, $params) + { + $this->cleanupParams($params); + + try { + if (count($params) != 2) { + throw new Exception("Argument count should be = 2"); + } + + $channel = $params[0]; + $package = $params[1]; + /** @var $packager Mage_Connect_Packager */ + $packager = $this->getPackager(); + $withDepsMode = !isset($options['nodeps'])? false : (boolean)$options['nodeps']; + $forceMode = isset($options['force']); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + /** @var $cache Mage_Connect_Singleconfig */ + /** @var $config Mage_Connect_Config */ + /** @var $ftpObj Mage_Connect_Ftp */ + if ($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + $channel = $cache->chanName($channel); + if (!$cache->hasPackage($channel, $package)) { + throw new Exception("Package is not installed"); + } + + $deletedPackages = array(); + $list = $packager->getUninstallList($channel, $package, $cache, $config, $withDepsMode); + foreach ($list['list'] as $packageData) { + try { + $reqd = $cache->requiredByOtherPackages( + $packageData['channel'], + $packageData['name'], + $list['list'] + ); + if (count($reqd)) { + $errMessage = "{$packageData['channel']}/{$packageData['name']} " + . "{$packageData['version']} is required by: "; + $t = array(); + foreach ($reqd as $r) { + $t[] = $r['channel'] . "/" . $r['name'] . " " . $r['version']; + } + $errMessage .= implode(", ", $t); + if ($forceMode) { + $this->ui()->output("Warning: " . $errMessage); + } else { + throw new Exception($errMessage); + } + } + } catch(Exception $e) { + if ($forceMode) { + $this->doError($command, $e->getMessage()); + } else { + throw new Exception($e->getMessage()); + } + } + } + foreach ($list['list'] as $packageData) { + try { + list($chan, $pack) = array($packageData['channel'], $packageData['name']); + $packageName = $packageData['channel'] . "/" . $packageData['name']; + $this->ui()->output("Starting to uninstall $packageName "); + if ($ftp) { + $packager->processUninstallPackageFtp($chan, $pack, $cache, $ftpObj); + } else { + $packager->processUninstallPackage($chan, $pack, $cache, $config); + } + $cache->deletePackage($chan, $pack); + $deletedPackages[] = array($chan, $pack); + $this->ui()->output("Package {$packageName} uninstalled"); + } catch(Exception $e) { + if ($forceMode) { + $this->doError($command, $e->getMessage()); + } else { + throw new Exception($e->getMessage()); + } + } + } + if ($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + $out = array($command=>array('data'=>$deletedPackages, 'title'=>'Package deleted: ')); + $this->ui()->output($out); + return $out[$command]['data']; + } catch (Exception $e) { + return $this->doError($command, $e->getMessage()); + } + } +} diff --git a/downloader/lib/Mage/Connect/Command/Install_Header.php b/downloader/lib/Mage/Connect/Command/Install_Header.php new file mode 100644 index 00000000..12e004d2 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Install_Header.php @@ -0,0 +1,237 @@ + array( + 'summary' => 'Install Package Archive File', + 'function' => 'doInstall', + 'shortcut' => 'if', + 'options' => array(), + 'doc' => '', + ), + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + 'ftp=' => array( + 'shortopt' => 'r=', + 'doc' => 'Remote side FTP connect string', + ), + ), + 'doc' => '[channel/] ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doUpgrade', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/] ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + ); diff --git a/downloader/lib/Mage/Connect/Command/Package.php b/downloader/lib/Mage/Connect/Command/Package.php new file mode 100644 index 00000000..0f0efe3f --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Package.php @@ -0,0 +1,204 @@ +cleanupParams($params); + + if(count($params) < 1) { + return $this->doError($command, "Parameters count should be >= 1"); + } + + $file = strtolower($params[0]); + $file = realpath($file); + + if(!file_exists($file)) { + return $this->doError($command, "File {$params[0]} doesn't exist"); + } + + try { + $packager = new Mage_Connect_Package($file); + $res = $packager->validate(); + if(!$res) { + $this->doError($command, implode("\n", $packager->getErrors())); + return; + } + $packager->save(dirname($file)); + $this->ui()->output('Done building package'); + } catch (Exception $e) { + $this->doError( $command, $e->getMessage() ); + } + } + + /** + * Display/get installation information for package + * @param string $command + * @param array $options + * @param array $params + * @return void/array + */ + public function doPackagePrepare($command, $options, $params) + { + $this->cleanupParams($params); + $channelAuth = array(); + if (isset($options['auth'])) { + $channelAuth = $options['auth']; + $options['auth'] = null; + } + try { + + if(count($params) < 2) { + return $this->doError($command, "Argument count should be >= 2"); + } + + $channel = $params[0]; + $package = $params[1]; + + $argVersionMin = isset($params[3]) ? $params[3] : false; + $argVersionMax = isset($params[2]) ? $params[2] : false; + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + $packager = $this->getPackager(); + if ($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + $rest = new Mage_Connect_Rest($config->protocol); + if(!empty($channelAuth)){ + $rest->getLoader()->setCredentials($channelAuth['username'], $channelAuth['password']); + } + + $cache->checkChannel($channel, $config, $rest); + + $data = $packager->getDependenciesList($channel, $package, $cache, $config, + $argVersionMax, $argVersionMin, true, false, $rest + ); + + $result = array(); + foreach ($data['result'] as $_package) { + $_result['channel'] = $_package['channel']; + $_result['name'] = $_package['name']; + $_result['version'] = $_package['downloaded_version']; + $_result['stability'] = $_package['stability']; + $_result['install_state'] = $_package['install_state']; + $_result['message'] = $_package['message']; + $result[] = $_result; + } + if (!count($data['result']) && isset($data['failed']) && !empty($data['failed'])) { + foreach ($data['failed'] as $_package) { + $reason = $_package['channel'] . '/' . $_package['name'] . ': ' . $_package['reason']; + $this->doError($command, $reason); + } + } + + $this->ui()->output(array($command=> array('data'=>$result, 'title'=>"Package installation information for {$params[1]}: "))); + + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Display/get dependencies + * @param string $command + * @param array $options + * @param array $params + * @return void/array + */ + public function doPackageDependencies($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) < 2) { + return $this->doError($command, "Argument count should be >= 2"); + } + + $channel = $params[0]; + $package = $params[1]; + + $argVersionMin = isset($params[3]) ? $params[3] : false; + $argVersionMax = isset($params[2]) ? $params[2] : false; + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + $packager = $this->getPackager(); + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + $data = $packager->getDependenciesList($channel, $package, $cache, $config, $argVersionMax, $argVersionMin); + $this->ui()->output(array($command=> array('data'=>$data['deps'], 'title'=>"Package deps for {$params[1]}: "))); + + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + public function doConvert($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) < 1) { + throw new Exception("Arguments should be: source.tgz [target.tgz]"); + } + $sourceFile = $params[0]; + $converter = new Mage_Connect_Converter(); + $targetFile = isset($params[1]) ? $params[1] : false; + $result = $converter->convertPearToMage($sourceFile, $targetFile); + $this->ui()->output("Saved to: ".$result); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + + } + +} diff --git a/downloader/lib/Mage/Connect/Command/Package_Header.php b/downloader/lib/Mage/Connect/Command/Package_Header.php new file mode 100644 index 00000000..6c0a05d2 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Package_Header.php @@ -0,0 +1,76 @@ + array( + 'summary' => 'Build Package', + 'function' => 'doPackage', + 'shortcut' => 'p', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +' + ), + 'package-dependencies' => array( + 'summary' => 'Show package dependencies', + 'function' => 'doPackageDependencies', + 'shortcut' => 'pd', + 'options' => array(), + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'package-prepare' => array( + 'summary' => 'Show installation information of package', + 'function' => 'doPackagePrepare', + 'shortcut' => 'pp', + 'options' => array(), + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'convert' => array( + 'summary' => 'Convert old magento PEAR package to new format', + 'function' => 'doConvert', + 'shortcut' => 'conv', + 'options' => array(), + 'doc' => '' + ), + ); diff --git a/downloader/lib/Mage/Connect/Command/Registry.php b/downloader/lib/Mage/Connect/Command/Registry.php new file mode 100644 index 00000000..2577c1c5 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Registry.php @@ -0,0 +1,364 @@ +cleanupParams($params); + try { + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + } else { + $cache = $this->getSconfig(); + } + if(!empty($params[0])) { + $chanName = $conf->chanName($params[0]); + $data = $cache->getInstalledPackages($chanName); + } else { + $data = $cache->getInstalledPackages(); + } + if($ftp) { + @unlink($cache->getFilename()); + } + $this->ui()->output(array($command=>array('data'=>$data, 'channel-title'=>"Installed package for channel '%s' :"))); + } catch (Exception $e) { + if($ftp) { + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + + } + + /** + * list-files callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doFileList($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + try { + $channel = false; + if(count($params) < 2) { + throw new Exception("Argument count should be = 2"); + } + $channel = $params[0]; + $package = $params[1]; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $confif = $this->config(); + } + if(!$cache->hasPackage($channel, $package)) { + return $this->ui()->output("No package found: {$channel}/{$package}"); + } + + $p = $cache->getPackageObject($channel, $package); + $contents = $p->getContents(); + if($ftp) { + $ftpObj->close(); + } + if(!count($contents)) { + return $this->ui()->output("No contents for package {$package}"); + } + $title = ("Contents of '{$package}': "); + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + + $this->ui()->output(array($command=>array('data'=>$contents, 'title'=>$title))); + + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + + } + + /** + * Installed package info + * info command callback + * @param string $command + * @param array $options + * @param array $params + * @return + */ + public function doInfo($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + + $cache = null; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + try { + $channel = false; + if(count($params) < 2) { + throw new Exception("Argument count should be = 2"); + } + $channel = $params[0]; + $package = $params[1]; + $packager = $this->getPackager(); + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + } else { + $cache = $this->getSconfig(); + } + + if(!$cache->isChannel($channel)) { + throw new Exception("'{$channel}' is not a valid installed channel name/uri"); + } + $channelUri = $cache->chanUrl($channel); + $rest = $this->rest(); + $rest->setChannel($channelUri); + $releases = $rest->getReleases($package); + if(false === $releases) { + throw new Exception("No information found about {$channel}/{$package}"); + } + $data = array($command => array('releases'=>$releases)); + if($ftp) { + @unlink($cache->getFilename()); + } + $this->ui()->output($data); + } catch (Exception $e) { + if ($ftp && isset($cache)) { + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + } + + /** + * Synchronize manually installed package info with local cache + * + * @param string $command + * @param array $options + * @param array $params + */ + public function doSync($command, $options, $params) + { + $this->cleanupParams($params); + try { + $packager = $this->getPackager(); + $cache = null; + $config = null; + $ftpObj = null; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $config = $this->config(); + $cache = $this->getSconfig(); + } + if ($this->_checkPearData($config)) { + $this->doSyncPear($command, $options, $params); + } + + $packageDir = $config->magento_root . DS . Mage_Connect_Package::PACKAGE_XML_DIR; + if (is_dir($packageDir)) { + $entries = scandir($packageDir); + foreach ((array)$entries as $entry) { + $path = $packageDir. DS .$entry; + $info = pathinfo($path); + if ($entry == '.' || $entry == '..' || is_dir($path) || $info['extension'] != 'xml') { + continue; + } + + if (is_readable($path)) { + $data = file_get_contents($path); + if ($data === false) { + continue; + } + + $package = new Mage_Connect_Package($data); + $name = $package->getName(); + $channel = $package->getChannel(); + $version = $package->getVersion(); + if (!$cache->isChannel($channel) && $channel == $config->root_channel) { + $cache->addChannel($channel, $config->root_channel_uri); + } + if (!$cache->hasPackage($channel, $name, $version, $version)) { + $cache->addPackage($package); + $this->ui()->output("Successfully added: {$channel}/{$name}-{$version}"); + } + } + } + if ($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + } + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Synchronize packages installed earlier (by pear installer) with local cache + * + * @param string $command + * @param array $options + * @param array $params + */ + public function doSyncPear($command, $options, $params) + { + $this->cleanupParams($params); + try { + $packager = $this->getPackager(); + $cache = null; + $config = null; + $ftpObj = null; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $config = $this->config(); + $cache = $this->getSconfig(); + } + + $pkglist = array(); + if (!$this->_checkPearData($config)) { + return $pkglist; + } + + $pearStorage = $config->magento_root . DS . $config->downloader_path . DS . self::PACKAGE_PEAR_DIR; + $channels = array( + '.channel.connect.magentocommerce.com_community', + '.channel.connect.magentocommerce.com_core' + ); + foreach ($channels as $channel) { + $channelDirectory = $pearStorage . DS . $channel; + if (!file_exists($channelDirectory) || !is_dir($channelDirectory)) { + continue; + } + + $dp = opendir($channelDirectory); + if (!$dp) { + continue; + } + + while ($ent = readdir($dp)) { + if ($ent{0} == '.' || substr($ent, -4) != '.reg') { + continue; + } + $pkglist[] = array('file'=>$ent, 'channel'=>$channel); + } + closedir($dp); + } + + $package = new Mage_Connect_Package(); + foreach ($pkglist as $pkg) { + $pkgFilename = $pearStorage . DS . $pkg['channel'] . DS . $pkg['file']; + if (!file_exists($pkgFilename)) { + continue; + } + $data = file_get_contents($pkgFilename); + $data = unserialize($data); + + $package->importDataV1x($data); + $name = $package->getName(); + $channel = $package->getChannel(); + $version = $package->getVersion(); + if (!$cache->isChannel($channel) && $channel == $config->root_channel) { + $cache->addChannel($channel, $config->root_channel_uri); + } + if (!$cache->hasPackage($channel, $name, $version, $version)) { + $cache->addPackage($package); + + if($ftp) { + $localXml = tempnam(sys_get_temp_dir(),'package'); + @file_put_contents($localXml, $package->getPackageXml()); + + if (is_file($localXml)) { + $ftpDir = $ftpObj->getcwd(); + $remoteXmlPath = $ftpDir . '/' . Mage_Connect_Package::PACKAGE_XML_DIR; + $remoteXml = $package->getReleaseFilename() . '.xml'; + $ftpObj->mkdirRecursive($remoteXmlPath); + $ftpObj->upload($remoteXml, $localXml, 0777, 0666); + $ftpObj->chdir($ftpDir); + } + } else { + $destDir = rtrim($config->magento_root, "\\/") . DS . Mage_Connect_Package::PACKAGE_XML_DIR; + $destFile = $package->getReleaseFilename() . '.xml'; + $dest = $destDir . DS . $destFile; + + @mkdir($destDir, 0777, true); + @file_put_contents($dest, $package->getPackageXml()); + @chmod($dest, 0666); + } + + $this->ui()->output("Successfully added: {$channel}/{$name}-{$version}"); + } + + } + + $config->sync_pear = true; + if($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + + return true; + } + + /** + * Check is need to sync old pear data + * + * @param Mage_Connect_Config $config + * @return boolean + */ + protected function _checkPearData($config) { + $pearStorage = $config->magento_root . DS . $config->downloader_path . DS . self::PACKAGE_PEAR_DIR; + return (!$config->sync_pear) && file_exists($pearStorage) && is_dir($pearStorage); + } + +} diff --git a/downloader/lib/Mage/Connect/Command/Registry_Header.php b/downloader/lib/Mage/Connect/Command/Registry_Header.php new file mode 100644 index 00000000..4b77ee17 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Registry_Header.php @@ -0,0 +1,84 @@ + array( + 'summary' => 'List Installed Packages In The Default Channel', + 'function' => 'doList', + 'shortcut' => 'l', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + ), + 'doc' => ' +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +', + ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'info' => array( + 'summary' => 'Display information about a package', + 'function' => 'doInfo', + 'shortcut' => 'in', + 'options' => array(), + 'doc' => ' +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.' + ), + 'sync' => array( + 'summary' => 'Synchronize Manually Installed Packages', + 'function' => 'doSync', + 'shortcut' => 'snc', + 'options' => array(), + 'doc' => ' +Synchronize manually installed package info with local cache.' + ), + 'sync-pear' => array( + 'summary' => 'Synchronize already Installed Packages by pear', + 'function' => 'doSyncPear', + 'shortcut' => 'sncp', + 'options' => array(), + 'doc' => ' +Synchronize already Installed Packages by pear.' + ) + ); diff --git a/downloader/lib/Mage/Connect/Command/Remote.php b/downloader/lib/Mage/Connect/Command/Remote.php new file mode 100644 index 00000000..7d8c879c --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Remote.php @@ -0,0 +1,231 @@ +cleanupParams($params); + try { + $packager = new Mage_Connect_Packager(); + $channelAuth = isset($options['auth'])?$options['auth']:array(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + if(!empty($params[0])) { + $channels = $params[0]; + $cache->getChannel($channels); + } else { + $channels = $cache->getChannelNames(); + } + $rest = $this->rest(); + if(!empty($channelAuth)){ + $rest->getLoader()->setCredentials($channelAuth['username'], $channelAuth['password']); + } + $ups = $packager->getUpgradesList($channels, $cache, $config, $rest); + + if(count($ups)) { + $data = array($command => array('data'=>$ups)); + } else { + $data = "No upgrades available"; + } + $this->ui()->output($data); + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + + /** + * List available + * @param $command + * @param $options + * @param $params + * @return unknown_type + */ + + public function doListAvailable($command, $options, $params) + { + $this->cleanupParams($params); + + try { + $packager = new Mage_Connect_Packager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + if(!empty($params[0])) { + $channels = array($params[0]); + $cache->getChannel($channels[0]); + } else { + $channels = $cache->getChannelNames(); + } + + + + $packs = array(); + foreach ($channels as $channel) { + try { + $chan = $cache->getChannel($channel); + $uri = $cache->chanUrl($channel); + + $rest = $this->rest(); + $rest->setChannel($uri); + + $packages = $rest->getPackages(); + if(!count($packages)) { + $this->ui()->output("Channel '{$channel}' has no packages"); + continue; + } + $packs[$channel]['title'] = "Packages for channel '".$channel."':"; + foreach($packages as $p) { + $packageName = $p['n']; + $releases = array(); + foreach($p['r'] as $k=>$r) { + $releases[$r] = $rest->shortStateToLong($k); + } + $packs[$channel]['packages'][$packageName]['releases'] = $releases; + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + $dataOut = array(); + $dataOut[$command]= array('data'=>$packs); + $this->ui()->output($dataOut); + + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + + } + + /** + * Download command callback + * + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doDownload($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + try { + if(count($params) < 2) { + throw new Exception("Arguments should be: channel Package"); + } + + $channel = $params[0]; + $package = $params[1]; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + $chan = $cache->getChannel($channel); + $uri = $cache->chanUrl($channel); + + $rest = $this->rest(); + $rest->setChannel($uri); + $c = $rest->getReleases($package); + if(!count($c)) { + throw new Exception("No releases found for package"); + } + $version = $cache->detectVersionFromRestArray($c); + $dir = $config->getChannelCacheDir($channel); + $file = $dir.DIRECTORY_SEPARATOR.$package."-".$version.".tgz"; + $rest->downloadPackageFileOfRelease($package, $version, $file); + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->ui()->output("Saved to: ". $file); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + } + + /** + * Clear cache command callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doClearCache($command, $options, $params) + { + $this->cleanupParams($params); + try { + $packager = new Mage_Connect_Packager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + $cache->clear(); + $packager->writeToRemoteCache($cache, $ftpObj); + } else { + $cache = $this->getSconfig(); + $cache->clear(); + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + + + + +} diff --git a/downloader/lib/Mage/Connect/Command/Remote_Header.php b/downloader/lib/Mage/Connect/Command/Remote_Header.php new file mode 100644 index 00000000..79793670 --- /dev/null +++ b/downloader/lib/Mage/Connect/Command/Remote_Header.php @@ -0,0 +1,88 @@ + array( + 'summary' => 'List Available Upgrades', + 'function' => 'doListUpgrades', + 'shortcut' => 'lu', + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' + ), + 'list-available' => array( + 'summary' => 'List Available Packages', + 'function' => 'doListAvailable', + 'shortcut' => 'la', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'download' => array( + 'summary' => 'Download Package', + 'function' => 'doDownload', + 'shortcut' => 'd', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'download an uncompressed (.tar) file', + ), + ), + 'doc' => '... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', + ), + 'clear-cache' => array( + 'summary' => 'Clear Web Services Cache', + 'function' => 'doClearCache', + 'shortcut' => 'cc', + 'options' => array(), + 'doc' => ' +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. +', + ), + ); diff --git a/downloader/lib/Mage/Connect/Config.php b/downloader/lib/Mage/Connect/Config.php new file mode 100644 index 00000000..c964de14 --- /dev/null +++ b/downloader/lib/Mage/Connect/Config.php @@ -0,0 +1,617 @@ + + */ +class Mage_Connect_Config implements Iterator +{ + /** + * Config file name + * + * @var string + */ + protected $_configFile; + + /** + * Config loaded from file + * + * @var bool + */ + protected $_configLoaded; + + /** + * Save file even if it not modified + * + * @var bool + */ + protected $_forceSave = false; + + /** + * Stores last error message + * + * @var string + */ + protected $_configError = ''; + + /** + * Header string + */ + const HEADER = "::ConnectConfig::v::1.0::"; + + /** + * Default paths + */ + const DEFAULT_DOWNLOADER_PATH = "downloader"; + const DEFAULT_CACHE_PATH = ".cache"; + + /** + * Array of default properties + * @var array + */ + protected $defaultProperties = array(); + + /** + * Array of properties + * + * @var array + */ + protected $properties = array(); + + /** + * Constructor loads the data from config file + * @param string $configFile + */ + public function __construct($configFile = "connect.cfg") + { + $this->initProperties(); + $this->_configFile = $configFile; + $this->load(); + } + + /** + * Initialise Properties and Default Properties + * + * @return void + */ + protected function initProperties() + { + $this->defaultProperties = array ( + 'php_ini' => array( + 'type' => 'file', + 'value' => '', + 'prompt' => 'location of php.ini', + 'doc' => "It's a location of PHP.ini to use blah", + 'possible' => '/path/php.ini', + ), + 'protocol' => array( + 'type' => 'set', + 'value' => 'http', + 'prompt' => 'preffered protocol', + 'doc' => 'preffered protocol', + 'rules' => array('http', 'ftp') + ), + 'preferred_state' => array( + 'type' => 'set', + 'value' => 'stable', + 'prompt' => 'preferred package state', + 'doc' => 'preferred package state', + 'rules' => array('beta','alpha','stable','devel') + ), + 'use_custom_permissions_mode' => array ( + 'type' => 'bool', + 'value' => false, + 'prompt' => 'Use custom permissions for directory and file creation', + 'doc' => 'Use custom permissions for directory and file creation', + 'possible' => 'true, false', + ), + 'global_dir_mode' => array ( + 'type' => 'octal', + 'value' => 0777, + 'prompt' => 'directory creation mode', + 'doc' => 'directory creation mode', + 'possible' => '0777, 0666 etc.', + ), + 'global_file_mode' => array ( + 'type' => 'octal', + 'value' => 0666, + 'prompt' => 'file creation mode', + 'doc' => 'file creation mode', + 'possible' => '0777, 0666 etc.', + ), + 'downloader_path' => array( + 'type' => 'dir', + 'value' => 'downloader', + 'prompt' => 'relative path, location of magento downloader', + 'doc' => "relative path, location of magento downloader", + 'possible' => 'path', + ), + 'magento_root' => array( + 'type' => 'dir', + 'value' => '', + 'prompt' => 'location of magento root dir', + 'doc' => "Location of magento", + 'possible' => '/path', + ), + 'root_channel_uri' => array( + 'type' => 'string', + 'value' => 'connect20.magentocommerce.com/community', + 'prompt' => '', + 'doc' => "", + 'possible' => '', + ), + 'root_channel' => array( + 'type' => 'string', + 'value' => 'community', + 'prompt' => '', + 'doc' => "", + 'possible' => '', + ), + 'remote_config' => array( + 'type' => 'string', + 'value' => '', + 'prompt' => '', + 'doc' => "", + 'possible' => 'ftp://name:password@host.com:port/path/to/folder/', + ), + 'sync_pear' => array( + 'type' => 'boolean', + 'value' => false, + 'prompt' => '', + 'doc' => "", + 'possible' => '', + ) + ); + $this->properties = $this->defaultProperties; + } + + /** + * Retrieve Downloader Path + * + * @return string + */ + public function getDownloaderPath() + { + return $this->magento_root . DIRECTORY_SEPARATOR . $this->downloader_path; + } + + /** + * Retrieve Packages Cache Directory + * + * @return string + */ + public function getPackagesCacheDir() + { + return $this->getDownloaderPath() . DIRECTORY_SEPARATOR . self::DEFAULT_CACHE_PATH; + } + + /** + * Retrieve Channel Cache Directory + * + * @param string $channel + * @return string + */ + public function getChannelCacheDir($channel) + { + $channel = trim( $channel, "\\/"); + return $this->getPackagesCacheDir(). DIRECTORY_SEPARATOR . $channel; + } + + /** + * Get Config file name + * + * @return string + */ + public function getFilename() + { + return $this->_configFile; + } + + /** + * Load data from config file + * + * @return bool + */ + public function load() + { + $this->_configLoaded=false; + if (!is_file($this->_configFile)) { + if (!$this->save()) { + $this->_configError = 'Config file does not exists please save Settings'; + } else { + $this->_configLoaded=true; + return true; + } + return false; + } + + try { + $f = fopen($this->_configFile, "r"); + fseek($f, 0, SEEK_SET); + } catch (Exception $e) { + $this->_configError = "Cannot open config file {$this->_configFile} please check file permission"; + return false; + } + + clearstatcache(); + $size = filesize($this->_configFile); + if (!$size) { + $this->_configError = "Wrong config file size {$this->_configFile} please save Settings again"; + return false; + } + + $headerLen = strlen(self::HEADER); + try { + $contents = fread($f, $headerLen); + if (self::HEADER != $contents) { + $this->_configError = "Wrong configuration file {$this->_configFile} please save Settings again"; + return false; + } + + $size -= $headerLen; + $contents = fread($f, $size); + } catch (Exception $e) { + $this->_configError = "Configuration file {$this->_configFile} read error '{$e->getMessage()}'" + . " please save Settings again"; + return false; + } + $data = @unserialize($contents); + if ($data === false) { + $this->_configError = "Wrong configuration file {$this->_configFile} please save Settings again"; + return false; + } + foreach($data as $k=>$v) { + $this->$k = $v; + } + @fclose($f); + $this->_configLoaded=true; + return true; + } + + /** + * Save config file on the disk or over ftp + * + * @return bool + */ + public function store() + { + $result = false; + if ($this->_forceSave || $this->_configLoaded || strlen($this->remote_config)>0) { + $data = serialize($this->toArray()); + if (strlen($this->remote_config)>0) { + //save config over ftp + $confFile = $this->downloader_path . DIRECTORY_SEPARATOR . "connect.cfg"; + try { + $ftpObj = new Mage_Connect_Ftp(); + $ftpObj->connect($this->remote_config); + } catch (Exception $e) { + $this->_configError = 'Cannot access to deployment FTP path. ' + . 'Check deployment FTP Installation path settings.'; + return $result; + } + try { + $tempFile = tempnam(sys_get_temp_dir(),'config'); + $f = fopen($tempFile, "w+"); + fwrite($f, self::HEADER); + fwrite($f, $data); + fclose($f); + } catch (Exception $e) { + $this->_configError = 'Cannot access to temporary file storage to save Settings.' + . 'Contact your system administrator.'; + return $result; + } + try { + $result = $ftpObj->upload($confFile, $tempFile); + $ftpObj->close(); + } catch (Exception $e) { + $this->_configError = 'Cannot write file over FTP. ' + . 'Check deployment FTP Installation path settings.'; + return $result; + } + if (!$result) { + $this->_configError = ''; + } + } elseif (is_file($this->_configFile) && is_writable($this->_configFile) || is_writable(getcwd())) { + try { + $f = fopen($this->_configFile, "w+"); + fwrite($f, self::HEADER); + fwrite($f, $data); + fclose($f); + $result = true; + } catch (Exception $e) { + $result = false; + } + } + } + return $result; + } + + /** + * Validate value for configuration key + * + * @param string $key + * @param mixed $val + * @return bool + */ + public function validate($key, $val) + { + $rules = $this->extractField($key, 'rules'); + if (null === $rules) { + return true; + } elseif ( is_array($rules) ) { + return in_array($val, $rules); + } + return false; + } + + /** + * Get possible values for configuration key + * + * @param string $key + * @return null|string + */ + public function possible($key) + { + $data = $this->getKey($key); + if (! $data) { + return null; + } + if ('set' == $data['type']) { + return implode("|", $data['rules']); + } + if (!empty($data['possible'])) { + return $data['possible']; + } + return "<" . $data['type'] . ">"; + } + + /** + * Get type of key + * + * @param string $key + * @return mixed|null + */ + public function type($key) + { + return $this->extractField($key, 'type'); + } + + /** + * Get documentation information + * + * @param string $key + * @return mixed|null + */ + public function doc($key) + { + return $this->extractField($key, 'doc'); + } + + /** + * Get property of key + * + * @param $key + * @param $field + * @return mixed|null + */ + public function extractField($key, $field) + { + if (!isset($this->properties[$key][$field])) { + return null; + } + return $this->properties[$key][$field]; + } + + /** + * Check Key exists in properties array + * + * @param string $fld + * @return bool + */ + public function hasKey($fld) + { + return isset($this->properties[$fld]); + } + + /** + * Get all Key properties + * + * @param $fld + * @return null + */ + public function getKey($fld) + { + if ($this->hasKey($fld)) { + return $this->properties[$fld]; + } + return null; + } + + /** + * Set the internal pointer of the Properties array to its first element + * + * @return void + */ + public function rewind() { + reset($this->properties); + } + + /** + * Validate current property + * + * @return bool + */ + public function valid() { + return current($this->properties) !== false; + } + + /** + * Get Key of current property + * + * @return mixed + */ + public function key() { + return key($this->properties); + } + + /** + * Get current Property + * + * @return mixed + */ + public function current() { + return current($this->properties); + } + + /** + * Advance the internal array pointer of the Properties array + * + * @return void + */ + public function next() { + next($this->properties); + } + + /** + * Retrieve value of property + * + * @param string $var + * @return null + */ + public function __get($var) + { + if (isset($this->properties[$var]['value'])) { + return $this->properties[$var]['value']; + } + return null; + } + + /** + * Set value of property + * + * @param string $var + * @param mixed $value + * @return void + */ + public function __set($var, $value) + { + if (is_string($value)) { + $value = trim($value); + } + if (isset($this->properties[$var])) { + if ($value === null) { + $value = ''; + } + if ($this->properties[$var]['value'] !== $value) { + $this->properties[$var]['value'] = $value; + $this->store(); + } + } + } + + /** + * Prepare Array of class properties + * + * @param bool $withRules + * @return array + */ + public function toArray($withRules = false) + { + $out = array(); + foreach ($this as $k=>$v) { + $out[$k] = $withRules ? $v : $v['value']; + } + return $out; + } + + /** + * Return default config value by key + * + * @param string $key + * @return mixed + */ + public function getDefaultValue($key) + { + if (isset($this->defaultProperties[$key]['value'])) { + return $this->defaultProperties[$key]['value']; + } + return false; + } + + /** + * Check is config loaded + * + * @return string + */ + public function isLoaded() + { + return $this->_configLoaded; + } + + /** + * Retrieve error message + * + * @return string + */ + public function getError() + { + return $this->_configError; + } + + /** + * Save config + * + * @return string + */ + public function save() + { + $forceSave = $this->_forceSave; + $this->_forceSave = true; + + $result = $this->store(); + + $this->_forceSave = $forceSave; + + return $result; + } +} diff --git a/downloader/lib/Mage/Connect/Converter.php b/downloader/lib/Mage/Connect/Converter.php new file mode 100644 index 00000000..46c3152b --- /dev/null +++ b/downloader/lib/Mage/Connect/Converter.php @@ -0,0 +1,336 @@ + + */ + +final class Mage_Connect_Converter +{ + protected $_archiver; + + /** + * + * @return Mage_Archive + */ + public function arc() + { + if(!$this->_archiver) { + $this->_archiver = new Mage_Archive(); + } + return $this->_archiver; + } + + public function newPackage() + { + return new Mage_Connect_Package(); + } + + /** + * + * @return Pear_Package_Parser_v2 + */ + public function oldPackageReader() + { + return new Pear_Package_Parser_v2(); + } + + + public function __construct() + { + + } + + + public function convertChannelName($channel) + { + return str_replace("connect.magentocommerce.com/", "", $channel); + } + + /** + * Convert package dependencies - urls - by ref + * @param array $deps ref to array + * @return void + */ + public function convertPackageDependencies($oldDeps) + { + $out = array(); + if(empty($oldDeps['required']['package'])) { + return $out; + } + $deps = $oldDeps['required']['package']; + if(!isset($deps[0])) { + $deps = array($deps); + } + for($i=0, $c=count($deps); $i<$c; $i++) { + $deps[$i]['min_version'] = isset($deps[$i]['min']) ? $deps[$i]['min'] : false; + $deps[$i]['max_version'] = isset($deps[$i]['max']) ? $deps[$i]['max'] : false; + $deps[$i]['channel'] = $this->convertChannelName($deps[$i]['channel']); + $out[] = $deps[$i]; + } + + return $out; + } + + public function convertLicense($oldLicense) + { + if(is_scalar($oldLicense)) { + return $oldLicense; + } + return array($oldLicense['_content'], $oldLicense['attribs']['uri']); + } + + public function convertMaintainers($maintainers) + { + if(!is_array($maintainers) || !count($maintainers)) { + return array(); + } + $out = array(); + foreach($maintainers as $row) { + $out[] = array('name'=>$row['name'], 'email'=>$row['email'], 'user'=>'auto-converted'); + } + return $out; + } + + protected $fileMap = array(); + + + /** + * Conver pear package object to magento object + * @param Pear_Package_V2 $pearObject + * @return Mage_Connect_Package + */ + + public function convertPackageObject($pearObject) + { + $data = array(); + $mageObject = $this->newPackage(); + + + + $map = array ( + 'name' => null, + 'version' => array('getterArgs' => array('release') + ), + 'package_deps' => array( 'getter'=>'getDependencies', + 'converter'=>'convertPackageDependencies', + 'setter'=>'setDependencyPackages', + ), + 'stability' => array( 'getter'=>'getState', + 'getterArgs' => array('release'), + ), + 'license' => array( 'getterArgs' => array(true), + 'converter' => 'convertLicense', + 'noArrayWrap' => true, + ), + 'summary' => null, + 'description' => null, + 'notes' => null, + 'date' => null, + 'time' => null, + 'authors' => array( 'converter' => 'convertMaintainers', + 'getter' => 'getMaintainers', + ), + 'channel' => array( 'converter' => 'convertChannelName', + + ), + + ); + foreach($map as $field=>$rules) { + + if(empty($rules)) { + $rules = array('setter'=> '', 'getter'=> ''); + } + + if(empty($rules['getter'])) { + $rules['getter'] = 'get'. ucfirst($field); + } + + $useSetter = empty($rules['noSetter']); + $useGetter = empty($rules['noGetter']); + + + if(empty($rules['setter'])) { + $rules['setter'] = 'set'. ucfirst($field); + } + if(empty($rules['getterArgs'])) { + $rules['getterArgs'] = array(); + } elseif(!is_array($rules['getterArgs'])) { + throw new Exception("Invalid 'getterArgs' for '{$field}', should be array"); + } + + if($useGetter && !method_exists($pearObject, $rules['getter'])) { + $mName = get_class($pearObject)."::".$rules['getter']; + throw new Exception('No getter method exists: '.$mName); + } + + if($useSetter && !method_exists($mageObject, $rules['setter'])) { + $mName = get_class($mageObject)."::".$rules['setter']; + throw new Exception('No setter method exists: '.$mName); + } + + $useConverter = !empty($rules['converter']); + + if($useConverter && false === method_exists($this, $rules['converter'])) { + $mName = get_class($this)."::".$rules['converter']; + throw new Exception('No converter method exists: '.$mName); + } + + if($useGetter) { + $getData = call_user_func_array(array($pearObject, $rules['getter']), $rules['getterArgs']); + } else { + $getData = array(); + } + + if($useConverter) { + $args = array(); + if(!$useGetter && !$useSetter) { + $args = array($pearObject, $mageObject); + } elseif(!$useSetter) { + $args = array($mageObject, $getData); + } else { + $args = array($getData); + } + $getData = call_user_func_array(array($this, $rules['converter']), $args); + } + + $noWrap = !empty($rules['noArrayWrap']); + if($useSetter) { + $setData = call_user_func_array(array($mageObject, $rules['setter']), $noWrap ? $getData : array($getData)); + } + } + return $mageObject; + } + + /** + * Convert PEAR package to Magento package + * @param string $sourceFile path to PEAR .tgz + * @param string|false $destFile path to newly-created Magento .tgz, false to specify auto + * @return bool + */ + public function convertPearToMage($sourceFile, $destFile = false) + { + try { + if(!file_exists($sourceFile)) { + throw new Exception("File doesn't exist: {$sourceFile}"); + } + $arc = $this->arc(); + $tempDir = "tmp-".basename($sourceFile).uniqid(); + $outDir = "out-".basename($sourceFile).uniqid(); + $outDir = rtrim($outDir, "\\/"); + Mage_System_Dirs::mkdirStrict($outDir); + Mage_System_Dirs::mkdirStrict($tempDir); + + $result = $arc->unpack($sourceFile, $tempDir); + if(!$result) { + throw new Exception("'{$sourceFile}' was not unpacked"); + } + + $result = rtrim($result, "\\/"); + $packageXml = $result . DS . "package.xml"; + if(!file_exists($packageXml)) { + throw new Exception("No package.xml found inside '{$sourceFile}'"); + } + + $reader = $this->oldPackageReader(); + $data = file_get_contents($packageXml); + + $pearObject = $reader->parsePackage($data, $packageXml); + $mageObject = $this->convertPackageObject($pearObject); + if(!$mageObject->validate()) { + throw new Exception("Package validation failed.\n". implode("\n", $mageObject->getErrors())); + } + + /** + * Calculate destination file if false + */ + if(false === $destFile) { + $pathinfo = pathinfo($sourceFile); + $destFile = $pathinfo['dirname'] . DS .$pathinfo['filename'].'-converted'; + if(isset($pathinfo['extension'])) { + $destFile .= ".".$pathinfo['extension']; + } + } + + $target = new Mage_Connect_Package_Target("target.xml"); + $targets = $target->getTargets(); + $mageObject->setTarget($target); + $validRoles = array_keys($targets); + $data = $pearObject->getFilelist(); + $pathSource = dirname($pearObject->getPackageFile()).DS.$pearObject->getName()."-".$pearObject->getVersion(); + + $filesToDo = array(); + foreach($data as $file =>$row) { + $name = $row['name']; + $role = $row['role']; + if(!in_array($role, $validRoles)) { + $role = 'mage'; + } + $baseName = ltrim($targets[$role], "\\/."); + $baseName = rtrim($baseName, "\\/"); + $sourceFile = $pathSource.DS.$name; + $targetFile = $outDir . DS . $baseName . DS. $name; + if(file_exists($sourceFile)) { + Mage_System_Dirs::mkdirStrict(dirname($targetFile)); + $copy = @copy($sourceFile, $targetFile); + if(false === $copy) { + throw new Exception("Cannot copy '{$sourceFile}' to '{$targetFile}'"); + } + } + $filesToDo[] = array ('name'=> $name, 'role'=>$role); + } + $cwd = getcwd(); + @chdir($outDir); + foreach($filesToDo as $fileToDo) { + $mageObject->addContent($fileToDo['name'], $fileToDo['role']); + } + $mageObject->save(getcwd()); + @chdir($cwd); + $filename = $outDir. DS . $mageObject->getReleaseFilename().".tgz"; + if(@file_exists($targetArchive)) { + @unlink($targetArchive); + } + Mage_System_Dirs::mkdirStrict(dirname($destFile)); + $copy = @copy($filename, $destFile); + if(false === $copy) { + throw new Exception("Cannot copy '{$filename}' to '{$targetArchive}'"); + } + Mage_System_Dirs::rm($tempDir); + Mage_System_Dirs::rm($outDir); + + } catch (Exception $e) { + throw $e; + } + return $destFile; + } + + + +} diff --git a/downloader/lib/Mage/Connect/Frontend.php b/downloader/lib/Mage/Connect/Frontend.php new file mode 100644 index 00000000..2cd9358e --- /dev/null +++ b/downloader/lib/Mage/Connect/Frontend.php @@ -0,0 +1,267 @@ +_errors[] = $data; + } + + /** + * Get errors, clear errors list with first param + * + * @param boolean $clear + * @return array + */ + public function getErrors($clear = true) + { + if(!$clear) { + return $this->_errors; + } + $out = $this->_errors; + $this->clearErrors(); + return $out; + } + + /** + * Clear errors array + * + * @return null + */ + public function clearErrors() + { + $this->_errors = array(); + } + + /** + * Are there any errros? + * + * @return boolean + */ + public function hasErrors() + { + return count($this->_errors) != 0; + } + + /** + * Error processing + * @param string $command + * @param string $message + * @return null + */ + public function doError($command, $message) + { + $this->addError(array($command, $message)); + } + + /** + * Save capture state + * + * @return Mage_Connect_Frontend + */ + public function pushCapture() + { + array_push($this->_captureSaved, $this->_capture); + return $this; + } + + /** + * Restore capture state + * + * @return Mage_Connect_Frontend + */ + public function popCapture() + { + $this->_capture = array_pop($this->_captureSaved); + return $this; + } + + /** + * Set capture mode + * + * @param boolean $arg true by default + * @return Mage_Connect_Frontend + */ + public function setCapture($arg = true) + { + $this->_capture = $arg; + return $this; + } + + /** + * Getter for capture mode + * + * @return boolean + */ + public function isCapture() + { + return $this->_capture; + } + + /** + * Log stub + * + * @param $msg + * @return + */ + public function log($msg) + { + + } + + /** + * Ouptut method + * + * @param array $data + * @return null + */ + public function output($data) + { + + } + + /** + * Get instance of derived class + * + * @param $class CLI for example will produce Mage_Connect_Frontend_CLI + * @return object + */ + public static function getInstance($class) + { + $class = __CLASS__."_".$class; + return new $class(); + } + + /** + * Get output if capture mode set + * Clear prevoius if needed + * + * @param boolean $clearPrevious + * @return mixed + */ + public function getOutput($clearPrevious = true) + { + + } + + /** + * Save silent mode + * + * @return Mage_Connect_Frontend + */ + public function pushSilent() + { + array_push($this->_silentSaved, $this->_silent); + return $this; + } + + /** + * Restore silent mode + * + * @return Mage_Connect_Frontend + */ + public function popSilent() + { + $this->_silent = array_pop($this->_silentSaved); + return $this; + } + + /** + * Set silent mode + * + * @param boolean $value + * @return Mage_Connect_Frontend + */ + public function setSilent($value = true) + { + $this->_silent = (boolean) $value; + return $this; + } + + /** + * Is silent mode? + * + * @return boolean + */ + public function isSilent() + { + return (boolean) $this->_silent; + } + + /** + * Method for ask client about rewrite all files. + * + * @param $string + */ + public function confirm($string) + { + + } +} + diff --git a/downloader/lib/Mage/Connect/Frontend/CLI.php b/downloader/lib/Mage/Connect/Frontend/CLI.php new file mode 100644 index 00000000..1212c7f8 --- /dev/null +++ b/downloader/lib/Mage/Connect/Frontend/CLI.php @@ -0,0 +1,456 @@ + + */ + +class Mage_Connect_Frontend_CLI +extends Mage_Connect_Frontend +{ + + /** + * Collected output + * @var array + */ + protected $_output = array(); + + /** + * Output error + * @param string $command + * @param string $message + * @return void + */ + public function doError($command, $message) + { + parent::doError($command, $message); + $this->writeln("Error: "); + $this->writeln("$command: $message"); + } + + + /** + * Output config help + * @param array $data + * @return void + */ + public function outputConfigHelp($data) + { + foreach($data['data'] as $k=>$v) { + if(is_scalar($v)) { + $this->writeln($v); + } elseif(is_array($v)) { + $this->writeln(implode(": ", $v)); + } + } + } + + + /** + * Output info + * @param array $data + * @return void + */ + public function outputRemoteInfo($data) + { + if(!is_array($data['releases'])) { + return; + } + foreach ($data['releases'] as $r) { + $this->writeln(implode(" ", $r)); + } + } + + + public function detectMethodByType($type) + { + $defaultMethod = "output"; + $methodMap = array( + 'list-upgrades'=> 'outputUpgrades', + 'list-available' => 'outputChannelsPackages', + 'list-installed' => 'writeInstalledList', + 'package-dependencies' => 'outputPackageDeps', + 'package-prepare' => 'outputPackagePrepare', + 'list-files' => 'outputPackageContents', + 'config-help' => 'outputConfigHelp', + 'info' => 'outputRemoteInfo', + 'config-show' => 'outputConfig', + 'install' => 'outputInstallResult', + 'install-file' => 'outputInstallResult', + 'upgrade' => 'outputInstallResult', + 'upgrade-all' => 'outputInstallResult', + 'uninstall' => 'outputDeleted', + 'list-channels' => 'outputListChannels', + ); + if(isset($methodMap[$type])) { + return $methodMap[$type]; + } + return $defaultMethod; + } + + + public function outputDeleted($data) + { + if(!count($data['data'])) { + return; + } + $this->writeln($data['title']); + foreach($data['data'] as $row) { + $this->writeln("$row[0]/$row[1]"); + } + } + + public function outputListChannels($data) + { + $this->writeln($data['title']); + + $channels =& $data['data'][Mage_Connect_Singleconfig::K_CHAN]; + foreach($channels as $name => $v) { + $this->writeln("$name: {$v[Mage_Connect_Singleconfig::K_URI]}"); + } + $aliases =& $data['data'][Mage_Connect_Singleconfig::K_CHAN_ALIAS]; + if(count($aliases)) { + $this->writeln(); + $this->writeln($data['title_aliases']); + foreach($aliases as $k=>$v) { + $this->writeln("$k => $v"); + } + } + + } + + /** + * Output install result + * @param array $data + * @return void + */ + public function outputInstallResult($data) + { + if(isset($data['title'])) { + $title = trim($data['title'])." "; + } else { + $title = ''; + } + foreach($data['assoc'] as $row) { + $this->printf("%s%s/%s %s\n", $title, $row['channel'], $row['name'], $row['version']); + } + } + + /** + * Ouptut package contents + * @param array $data + * @return void + */ + public function outputPackageContents($data) + { + $this->writeln($data['title']); + foreach($data['data'] as $file) { + $this->writeln($file); + } + } + + /** + * Output package dependencies + * @param $data + * @return void + */ + public function outputPackageDeps($data) + { + $title = $data['title']; + $this->writeln($title); + foreach($data['data'] as $package) { + $this->printf("%-20s %-20s %-20s %-20s\n", $package['channel'], $package['name'], $package['min'], $package['max']); + } + } + + /** + * Output package prepare + * @param $data + * @return void + */ + public function outputPackagePrepare($data) + { + $title = $data['title']; + $this->writeln($title); + foreach($data['data'] as $package) { + $this->printf("%-20s %-20s %-20s %-20s\n", $package['channel'], $package['name'], $package['version'], $package['install_state']); + } + } + + /** + * Ouptut channel packages + * @param $data + * @return unknown_type + */ + public function outputChannelsPackages($data) + { + foreach($data['data'] as $channelInfo) { + $title =& $channelInfo['title']; + $packages =& $channelInfo['packages']; + $this->writeln($title); + foreach($packages as $name=>$package) { + $releases =& $package['releases']; + $tmp = array(); + foreach($releases as $ver=>$state) { + $tmp[] = "$ver $state"; + } + $tmp = implode(',',$tmp); + $this->writeln($name.": ".$tmp); + } + } + } + + + /** + * Make output + * + * @param array $data + * @return void + */ + + public function output($data) + { + $capture = $this->isCapture(); + if($capture) { + $this->_output[] = $data; + return; + } + + if(is_array($data)) { + foreach($data as $type=>$params) { + $method = $this->detectMethodByType($type); + if($method) { + $this->$method($params); + } else { + $this->writeln(__METHOD__." handler not found for {$type}"); + } + } + } else { + $this->writeln($data); + } + } + + + /** + * Detailed package info + * @param Mage_Connect_Package $package + * @return void + */ + public function outputPackage($package) + { + $fields = array( + 'Name'=>'name', + 'Version'=>'version', + 'Stability'=>'stability', + 'Description' => 'description', + 'Date' => 'date', + 'Authors' => 'authors', + ); + + foreach($fields as $title => $fld) { + $method = "get".ucfirst($fld); + $data = $package->$method(); + if(empty($data)) { + continue; + } + $this->write($title.": "); + if(is_array($data)) { + $this->write(print_r($data,true)); + } else { + $this->write($data); + } + $this->writeln(''); + } + } + + + /** + * Write channels list + * @param array $data + * @return void + */ + public function writeChannelsList($data) + { + $this->writeln("Channels available: "); + $this->writeln("==================="); + $out = $data['byName']; + ksort($out); + foreach($out as $k=>$v) { + $this->printf ("%-20s %-20s\n", $k, $v); + } + } + + /** + * Write installed list + * @param array $data + * @return void + */ + public function writeInstalledList($data) + { + $totalCount = 0; + foreach($data['data'] as $channel=>$packages) { + $title = sprintf($data['channel-title'], $channel); + $c = count($packages); + $totalCount += $c; + if(!$c) { + continue; + } + $this->writeln($title); + foreach($packages as $name=>$row) { + $this->printf("%-20s %-20s\n", $name, $row['version']." ".$row['stability']); + } + } + if($totalCount === 0) { + $this->writeln("No installed packages"); + } + } + + /** + * Output commands list + * @param array $data + * @return void + */ + public function outputCommandList($data) + { + $this->writeln("Connect commands available:"); + $this->writeln("==========================="); + foreach ($data as $k=>$v) { + $this->printf ("%-20s %-20s\n", $k, $v['summary']); + } + } + + /** + * Output config + * @param array $data + * @return void + */ + public function outputConfig($data) + { + foreach($data['data'] as $name=>$row) { + $value = $row['value'] === '' ? "" : strval($row['value']); + $this->printf("%-30s %-20s %-20s\n", $row['prompt'], $name, $value); + } + } + + /** + * Output config variable + * @param string $key + * @param string $value + * @return void + */ + public function outputConfigVariable($key, $value) + { + if($value === '') { + $value = ''; + } + $this->writeln("Config variable '{$key}': {$value}"); + } + + /** + * Write data and "\n" afterwards + * @param string $data + * @return void + */ + public function writeln($data = '') + { + $this->write($data."\n"); + } + + + /** + * get output, clear if needed + * + * @param bool $clearPrevoius optional, true by default + * @return array + */ + public function getOutput($clearPrevious = true) + { + $out = $this->_output; + if($clearPrevious) { + $this->_output = array(); + } + return $out; + } + + /** + * Write data to console + * @param string $data + * @return void + */ + public function write($data) + { + if($this->isSilent()) { + return; + } + print $data; + } + + /** + * Output printf-stlye formatted string and args + * @return void + */ + public function printf() + { + $args = func_get_args(); + $this->write(call_user_func_array('sprintf', $args)); + } + + /** + * Readline from console + * @return string + */ + public function readln() + { + $out = ""; + $key = fgetc(STDIN); + while ($key!="\n") { + $out.= $key; + $key = fread(STDIN, 1); + } + return $out; + } + + /** + * Output upgrades + * @param array $data + * @return void + */ + public function outputUpgrades($data) + { + foreach($data['data'] as $chan => $packages) { + $this->writeln("Updates for ".$chan.": "); + foreach($packages as $name => $data) { + $this->writeln(" $name: {$data['from']} => {$data['to']}"); + } + } + } + +} + diff --git a/downloader/lib/Mage/Connect/Ftp.php b/downloader/lib/Mage/Connect/Ftp.php new file mode 100644 index 00000000..65d3700e --- /dev/null +++ b/downloader/lib/Mage/Connect/Ftp.php @@ -0,0 +1,533 @@ + + */ +class Mage_Connect_Ftp +{ + /** + * Connection object + * + * @var resource + */ + protected $_conn = false; + + /** + * Check connected, throw exception if not + * + * @throws Exception + * @return null + */ + protected function checkConnected() + { + if(!$this->_conn) { + throw new Exception(__CLASS__." - no connection established with server"); + } + } + + /** + * ftp_mkdir wrapper + * + * @param string $name + * @return string + */ + public function mdkir($name) + { + $this->checkConnected(); + return @ftp_mkdir($this->_conn, $name); + } + + /** + * Make dir recursive + * + * @param string $path + * @param int $mode + */ + public function mkdirRecursive($path, $mode = 0777) + { + $this->checkConnected(); + $dir = explode('/', $path); + $path= ""; + $ret = true; + for ($i=0; $i < count($dir); $i++) { + $path .= "/" .$dir[$i]; + if(!@ftp_chdir($this->_conn, $path)) { + @ftp_chdir($this->_conn,"/"); + if(!@ftp_mkdir($this->_conn,$path)) { + $ret=false; + break; + } else { + @ftp_chmod($this->_conn, $mode, $path); + } + } + } + return $ret; + } + + /** + * Try to login to server + * + * @param string $login + * @param string $password + * @throws Exception on invalid login credentials + * @return boolean + */ + public function login($login = "anonymous", $password = "test@gmail.com") + { + $this->checkConnected(); + $res = @ftp_login($this->_conn, $login, $password); + if(!$res) { + throw new Exception("Invalid login credentials"); + } + return $res; + } + + /** + * Validate connection string + * + * @param string $string + * @throws Exception + * @return string + */ + public function validateConnectionString($string) + { + if (empty($string)) { + throw new Exception("Connection string is empty"); + } + $data = @parse_url($string); + if(false === $data) { + throw new Exception("Connection string invalid: '{$string}'"); + } + if($data['scheme'] != 'ftp') { + throw new Exception("Support for scheme '{$data['scheme']}' unsupported"); + } + return $data; + } + + /** + * Connect to server using connect string + * Connection string: ftp://user:pass@server:port/path + * user,pass,port,path are optional parts + * + * @param string $string + * @param int $timeout + * @return null + */ + public function connect($string, $timeout = 90) + { + $params = $this->validateConnectionString($string); + $port = isset($params['port']) ? intval($params['port']) : 21; + + $this->_conn = @ftp_connect($params['host'], $port, $timeout); + + if(!$this->_conn) { + throw new Exception("Cannot connect to host: {$params['host']}"); + } + ftp_pasv($this->_conn, true); + if(isset($params['user']) && isset($params['pass'])) { + $this->login($params['user'], $params['pass']); + } else { + $this->login(); + } + if(isset($params['path'])) { + if(!$this->chdir($params['path'])) { + throw new Exception ("Cannot chdir after login to: {$params['path']}"); + } + } + } + + /** + * ftp_fput wrapper + * + * @param string $remoteFile + * @param resource $handle + * @param int $mode FTP_BINARY | FTP_ASCII + * @param int $startPos + * @return boolean + */ + public function fput($remoteFile, $handle, $mode = FTP_BINARY, $startPos = 0) + { + $this->checkConnected(); + return @ftp_fput($this->_conn, $remoteFile, $handle, $mode, $startPos); + } + + /** + * ftp_put wrapper + * + * @param string $remoteFile + * @param string $localFile + * @param int $mode FTP_BINARY | FTP_ASCII + * @param int $startPos + * @return boolean + */ + public function put($remoteFile, $localFile, $mode = FTP_BINARY, $startPos = 0) + { + $this->checkConnected(); + return @ftp_put($this->_conn, $remoteFile, $localFile, $mode, $startPos); + } + + /** + * Get current working directory + * + * @return string + */ + public function getcwd() + { + $d = $this->raw("pwd"); + $data = explode(" ", $d[0], 3); + if(empty($data[1])) { + return false; + } + if(intval($data[0]) != 257) { + return false; + } + $out = trim($data[1], '"'); + if($out !== "/") { + $out = rtrim($out, "/"); + } + return $out; + } + + /** + * ftp_raw wrapper + * + * @param string $cmd + * @return array + */ + public function raw($cmd) + { + $this->checkConnected(); + return @ftp_raw($this->_conn, $cmd); + } + + /** + * Upload local file to remote server. + * Can be used for relative and absoulte remote paths + * Relative: use chdir before calling this + * + * @param string $remote + * @param string $local + * @param int $dirMode + * @param int $fileMode + * @return boolean + */ + public function upload($remote, $local, $dirMode = 0777, $fileMode=0) + { + $this->checkConnected(); + + if(!file_exists($local)) { + throw new Exception("Local file doesn't exist: {$local}"); + } + if(!is_readable($local)) { + throw new Exception("Local file is not readable: {$local}"); + } + if(is_dir($local)) { + throw new Exception("Directory given instead of file: {$local}"); + } + + $globalPathMode = substr($remote, 0, 1) == "/"; + $dirname = dirname($remote); + $cwd = $this->getcwd(); + if(false === $cwd) { + throw new Exception("Server returns something awful on PWD command"); + } + + if(!$globalPathMode) { + $dirname = $cwd."/".$dirname; + $remote = $cwd."/".$remote; + } + $res = $this->mkdirRecursive($dirname, $dirMode); + $this->chdir($cwd); + + if(!$res) { + return false; + } + $res = $this->put($remote, $local); + + if(!$res) { + return false; + } + + if($fileMode){ + $res=$this->chmod($fileMode, $remote); + } + return (boolean)$res; + } + + /** + * Download remote file to local machine + * + * @param string $remote + * @param string $local + * @param int $ftpMode FTP_BINARY|FTP_ASCII + * @return boolean + */ + public function download($remote, $local, $ftpMode = FTP_BINARY) + { + $this->checkConnected(); + return $this->get($local, $remote, $ftpMode); + } + + /** + * ftp_pasv wrapper + * + * @param boolean $pasv + * @return boolean + */ + public function pasv($pasv) + { + $this->checkConnected(); + return @ftp_pasv($this->_conn, (boolean) $pasv); + } + + /** + * Close FTP connection + * + * @return null + */ + public function close() + { + if($this->_conn) { + @ftp_close($this->_conn); + } + } + + /** + * ftp_chmod wrapper + * + * @param $mode + * @param $remoteFile + * @return boolean + */ + public function chmod($mode, $remoteFile) + { + $this->checkConnected(); + return @ftp_chmod($this->_conn, $mode, $remoteFile); + } + + /** + * ftp_chdir wrapper + * + * @param string $dir + * @return boolean + */ + public function chdir($dir) + { + $this->checkConnected(); + return @ftp_chdir($this->_conn, $dir); + } + + /** + * ftp_cdup wrapper + * + * @return boolean + */ + public function cdup() + { + $this->checkConnected(); + return @ftp_cdup($this->_conn); + } + + /** + * ftp_get wrapper + * + * @param string $localFile + * @param string $remoteFile + * @param int $fileMode FTP_BINARY | FTP_ASCII + * @param int $resumeOffset + * @return boolean + */ + public function get($localFile, $remoteFile, $fileMode = FTP_BINARY, $resumeOffset = 0) + { + $remoteFile = $this->correctFilePath($remoteFile); + $this->checkConnected(); + return @ftp_get($this->_conn, $localFile, $remoteFile, $fileMode, $resumeOffset); + } + + /** + * ftp_nlist wrapper + * + * @param string $dir + * @return boolean + */ + public function nlist($dir = "/") + { + $this->checkConnected(); + $dir = $this->correctFilePath($dir); + return @ftp_nlist($this->_conn, $dir); + } + + /** + * ftp_rawlist wrapper + * + * @param string $dir + * @param boolean $recursive + * @return array + */ + public function rawlist( $dir = "/", $recursive = false ) + { + $this->checkConnected(); + $dir = $this->correctFilePath($dir); + return @ftp_rawlist($this->_conn, $dir, $recursive); + } + + /** + * Convert byte count to float KB/MB format + * + * @param int $bytes + * @return string + */ + public static function byteconvert($bytes) + { + $symbol = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); + $exp = floor( log($bytes) / log(1024) ); + return sprintf( '%.2f ' . $symbol[ $exp ], ($bytes / pow(1024, floor($exp))) ); + } + + /** + * Chmod string "-rwxrwxrwx" to "777" converter + * + * @param string $chmod + * @return string + */ + public static function chmodnum($chmod) + { + $trans = array('-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'); + $chmod = substr(strtr($chmod, $trans), 1); + $array = str_split($chmod, 3); + return array_sum(str_split($array[0])) . array_sum(str_split($array[1])) . array_sum(str_split($array[2])); + } + + /** + * Checks file exists + * + * @param string $path + * @param boolean $excludeIfIsDir + * @return boolean + */ + public function fileExists($path, $excludeIfIsDir = true) + { + $path = $this->correctFilePath($path); + $globalPathMode = substr($path, 0, 1) == "/"; + + $file = basename($path); + $dir = $globalPathMode ? dirname($path) : $this->getcwd()."/".($path==basename($path)?'':$path); + $data = $this->ls($dir); + foreach($data as $row) { + if($file == basename($row['name'])) { + if($excludeIfIsDir && $row['dir']) { + continue; + } + return true; + } + } + return false; + } + + /** + * Get directory contents in PHP array + * + * @param string $dir + * @param boolean $recursive + * @return array + */ + public function ls($dir = "/", $recursive = false) + { + $dir= $this->correctFilePath($dir); + $rawfiles = (array) $this->rawlist($dir, $recursive); + $structure = array(); + $arraypointer = &$structure; + foreach ($rawfiles as $rawfile) { + if ($rawfile[0] == '/') { + $paths = array_slice(explode('/', str_replace(':', '', $rawfile)), 1); + $arraypointer = &$structure; + foreach ($paths as $path) { + foreach ($arraypointer as $i => $file) { + if ($file['name'] == $path) { + $arraypointer = &$arraypointer[ $i ]['children']; + break; + } + } + } + } elseif(!empty($rawfile)) { + $info = preg_split("/[\s]+/", $rawfile, 9); + $arraypointer[] = array( + 'name' => $info[8], + 'dir' => $info[0]{0} == 'd', + 'size' => (int) $info[4], + 'chmod' => self::chmodnum($info[0]), + 'rawdata' => $info, + 'raw' => $rawfile + ); + } + } + return $structure; + } + + /** + * Correct file path + * + * @param $str + * @return string + */ + public function correctFilePath($str) + { + $str = str_replace("\\", "/", $str); + $str = preg_replace("/^.\//", "", $str); + return $str; + } + + /** + * Delete file + * + * @param string $file + * @return boolean + */ + public function delete($file) + { + $this->checkConnected(); + $file = $this->correctFilePath($file); + return @ftp_delete($this->_conn, $file); + } + + /** + * Remove directory + * + * @param string $dir + * @return boolean + */ + public function rmdir($dir) + { + $this->checkConnected(); + $dir = $this->correctFilePath($dir); + return @ftp_rmdir($this->_conn, $dir); + } +} diff --git a/downloader/lib/Mage/Connect/Loader.php b/downloader/lib/Mage/Connect/Loader.php new file mode 100644 index 00000000..78ebc35f --- /dev/null +++ b/downloader/lib/Mage/Connect/Loader.php @@ -0,0 +1,50 @@ + + */ +class Mage_Connect_Loader +{ + /** + * Factory for HTTP client + * + * @param string|false $protocol 'curl'/'socket' or false for auto-detect + * @return Mage_HTTP_IClient|Mage_Connect_Loader_Ftp + */ + public static function getInstance($protocol='') + { + if ($protocol == 'ftp') { + return new Mage_Connect_Loader_Ftp(); + } else { + return Mage_HTTP_Client::getInstance(); + } + } +} diff --git a/downloader/lib/Mage/Connect/Loader/Ftp.php b/downloader/lib/Mage/Connect/Loader/Ftp.php new file mode 100644 index 00000000..d74ae20f --- /dev/null +++ b/downloader/lib/Mage/Connect/Loader/Ftp.php @@ -0,0 +1,155 @@ + + */ +class Mage_Connect_Loader_Ftp +{ + + const TEMPORARY_DIR = '../var/package/tmp'; + + const FTP_USER = 'anonymous'; + + const FTP_PASS = 'test@gmail.com'; + + /** + * Object of Ftp + * + * @var Mage_Connect_Ftp + */ + protected $_ftp = null; + + /** + * User name + * @var string + */ + protected $_ftpUser = ''; + + /** + * User password + * @var string + */ + protected $_ftpPassword = ''; + + /** + * Response body + * @var string + */ + protected $_responseBody = ''; + + /** + * Response status + * @var int + */ + protected $_responseStatus = 0; + + /** + * Constructor + */ + public function __construct() + { + $this->_ftp = new Mage_Connect_Ftp(); + $this->_ftpUser = self::FTP_USER; + $this->_ftpPassword = self::FTP_PASS; + } + + public function getFtp() + { + return $this->_ftp; + } + + /** + * Retrieve file from URI + * + * @param mixed $uri + * @return bool + */ + public function get($uri) + { + $remoteFile = basename($uri); + $uri = dirname($uri); + $uri = str_replace('http://', '', $uri); + $uri = str_replace('https://', '', $uri); + $uri = str_replace('ftp://', '', $uri); + $uri = $this->_ftpUser.":".$this->_ftpPassword."@".$uri; + $this->getFtp()->connect("ftp://".$uri); + $this->getFtp()->pasv(true); + $tmpDir = self::TEMPORARY_DIR . DS; + if (!is_dir($tmpDir)) { + $tmpDir = sys_get_temp_dir(); + } + if (substr($tmpDir, -1) != DS) { + $tmpDir .= DS; + } + $localFile = $tmpDir . time() . ".xml"; + + if ($this->getFtp()->get($localFile, $remoteFile)) { + $this->_responseBody = file_get_contents($localFile); + $this->_responseStatus = 200; + } + @unlink($localFile); + $this->getFtp()->close(); + return $this; + } + + /** + * Get response status code + * + * @return string + */ + public function getStatus() + { + return $this->_responseStatus; + } + + /** + * put your comment there... + * + * @return string + */ + public function getBody() + { + return $this->_responseBody; + } + + /** + * Set login credentials for ftp auth. + * @param string $ftpLogin Ftp User account name + * @param string $ftpPassword User password + * @return string + */ + public function setCredentials($ftpLogin, $ftpPassword) + { + $this->_ftpUser = $ftpLogin; + $this->_ftpPassword = $ftpPassword; + } + +} diff --git a/downloader/lib/Mage/Connect/Package.php b/downloader/lib/Mage/Connect/Package.php new file mode 100644 index 00000000..d7858761 --- /dev/null +++ b/downloader/lib/Mage/Connect/Package.php @@ -0,0 +1,1499 @@ + + */ +class Mage_Connect_Package +{ + + const PACKAGE_XML_DIR = 'var/package'; + + /** + * Contain SimpleXMLElement for composing document. + * + * @var SimpleXMLElement + */ + protected $_packageXml; + + /** + * Internal cache + * + * @var array + */ + protected $_authors; + + /** + * Internal cache + * + * @var array + */ + protected $_contents; + + /** + * Internal cache + * + * @var array + */ + protected $_hashContents; + + /** + * Internal cache + * + * @var array + */ + protected $_compatible; + + /** + * Internal cache + * + * @var array + */ + protected $_dependencyPhpExtensions; + + /** + * Internal cache + * + * @var array + */ + protected $_dependencyPackages; + + /** + * A helper object that can read from a package archive + * + * @var Mage_Connect_Package_Reader + */ + protected $_reader; + + /** + * A helper object that can create and write to a package archive + * + * @var Mage_Connect_Package_Writer + */ + protected $_writer; + + /** + * Validator object + * + * @var Mage_Connect_Validator + */ + protected $_validator = null; + + /** + * Validation errors + * + * @var array + */ + protected $_validationErrors = array(); + + /** + * Object with target + * + * @var Mage_Connect_Package_Target + */ + protected $_target = null; + + /** + * Config object + * + * @var Mage_Connect_Config + */ + protected $_config = null; + + /** + * Creates a package object (empty, or from existing archive, or from package definition xml) + * + * @param null|string|resource $source + */ + public function __construct($source=null) + { + libxml_use_internal_errors(true); + + if (is_string($source)) { + // check what's in the string (a package definition or a package filename) + if (0 === strpos($source, "_init($source); + } elseif (is_file($source) && is_readable($source)) { + // package archive filename + $this->_loadFile($source); + } else { + throw new Mage_Exception('Invalid package source'); + } + } elseif (is_resource($source)) { + $this->_loadResource($source); + } elseif (is_null($source)) { + $this->_init(); + } else { + throw new Mage_Exception('Invalid package source'); + } + } + + /** + * Initializes an empty package object + * + * @param null|string $definition optional package definition xml + * @return Mage_Connect_Package + */ + protected function _init($definition=null) + { + + if (!is_null($definition)) { + $this->_packageXml = simplexml_load_string($definition); + } else { + $packageXmlStub = << + + + + + + + + + + + + +
    -
    -
    - - - + + + + + + + diff --git a/downloader/template/header.phtml b/downloader/template/header.phtml index 55d936e2..e48fab01 100644 --- a/downloader/template/header.phtml +++ b/downloader/template/header.phtml @@ -1,73 +1,73 @@ - - - - - <?php echo $this->__('Magento Downloader') ?> - - - - - - - - - -
    - -
    -

    Magento Downloader

    -
    -
    - controller()->getAction()!='login' && !$this->get('exception')): ?> - - -
    -set('messages', $this->controller()->session()->getMessages()) ?> -template('messages.phtml') ?> + + + + + <?php echo $this->__('Magento Downloader') ?> + + + + + + + + + +
    + +
    +

    Magento Downloader

    +
    +
    + controller()->getAction()!='login' && !$this->get('exception')): ?> + + +
    +set('messages', $this->controller()->session()->getMessages()) ?> +template('messages.phtml') ?> diff --git a/downloader/template/index.phtml b/downloader/template/index.phtml index da4f86d8..fc7b1ea5 100755 --- a/downloader/template/index.phtml +++ b/downloader/template/index.phtml @@ -1,36 +1,36 @@ - -template('header.phtml') ?> - -

    Start Here

    -

    Here is some explanation about PEAR. Suspendisse sapien urna, facilisis sed, pharetra ut, blandit nec, nulla. Vivamus ac dui. Morbi justo ipsum, bibendum sed, egestas sed, pharetra ut, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer quis sapien vel sem semper luctus. Vestibulum tristique venenatis velit. Ut urna nisl, dignissim vitae, cursus eget, pulvinar scelerisque, lectus. In ac leo id libero consequat dignissim. Sed aliquam est a pede. Phasellus ut turpis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed magna leo, volutpat eget, pretium tristique, varius vitae, tellus. Sed odio ante, sagittis id, euismod eu, bibendum quis, purus. Proin consectetuer elementum nunc. Mauris turpis. In sed eros eu enim sagittis viverra. Integer adipiscing vestibulum tortor. Proin ante. Vivamus euismod, tortor id condimentum condimentum, arcu odio posuere est, eu eleifend nunc nulla sed nibh. Proin pretium sapien vitae erat.
    Change your PEAR settings

    - -

    Proceed with complete Magento PEAR Download/Upgrade

    -

    Proceed to individual PEAR packages management

    - -template('footer.phtml') ?> - + +template('header.phtml') ?> + +

    Start Here

    +

    Here is some explanation about PEAR. Suspendisse sapien urna, facilisis sed, pharetra ut, blandit nec, nulla. Vivamus ac dui. Morbi justo ipsum, bibendum sed, egestas sed, pharetra ut, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer quis sapien vel sem semper luctus. Vestibulum tristique venenatis velit. Ut urna nisl, dignissim vitae, cursus eget, pulvinar scelerisque, lectus. In ac leo id libero consequat dignissim. Sed aliquam est a pede. Phasellus ut turpis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed magna leo, volutpat eget, pretium tristique, varius vitae, tellus. Sed odio ante, sagittis id, euismod eu, bibendum quis, purus. Proin consectetuer elementum nunc. Mauris turpis. In sed eros eu enim sagittis viverra. Integer adipiscing vestibulum tortor. Proin ante. Vivamus euismod, tortor id condimentum condimentum, arcu odio posuere est, eu eleifend nunc nulla sed nibh. Proin pretium sapien vitae erat.
    Change your PEAR settings

    + +

    Proceed with complete Magento PEAR Download/Upgrade

    +

    Proceed to individual PEAR packages management

    + +template('footer.phtml') ?> + diff --git a/downloader/template/install/download.phtml b/downloader/template/install/download.phtml index deff7748..2099c14b 100644 --- a/downloader/template/install/download.phtml +++ b/downloader/template/install/download.phtml @@ -18,10 +18,10 @@ * versions in the future. If you wish to customize Magento for your * needs please refer to http://www.magentocommerce.com for more information. * - * @category Varien - * @package Varien_Object - * @copyright Copyright (c) 2008 Irubin Consulting Inc. DBA Varien (http://www.varien.com) - * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + * @category design + * @package default + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> template('install/header.phtml') ?> @@ -30,69 +30,123 @@

    Welcome to Magento's Installation Wizard!

    -
    controller()->isDownloaded()): ?>

    You are now ready to continue the installation process by downloading the most up-to-date copy of the Magento software.

    -
    -
    + get('channel_notice')); ?> + +

    There's an indication that Magento files already have been downloaded.

    If you feel that you have arrived to this page by mistake, please continue installation:




    Alternatively, you could proceed with Re-Downloading all packages

    - -
    + get('channel_notice')); ?> + +
    - - - - - -
    Use custom permissions: - -
    - get('use_custom_permissions_mode')=='0'):?>style="display:none;"> - - - - - - - - - - - - -
    Folders: - -
    Files: - -
    Executable files: - -
    + + + + + + get('channel_protocol_fields')); ?> + + + + + + + + +
    Magento Connect Channel Protocol: + +
    Magento Version Stability: + +
    Use Custom Permissions: + +
    + get('use_custom_permissions_mode')=='0'):?>style="display:none;" > + + + + + + + + +
    Folders: + +
    Files: + +
    + + + + + +
    Deployment Type: + +
    + + + + + + + + + + + + + + + + + + + + +
    Host: + +
    User: + +
    Password: + +
    Installation Path: + +
    controller()->isDownloaded()): ?> - +
    -

    -template('pear/iframe.phtml') ?> +template('connect/iframe.phtml') ?> - - diff --git a/errors/default/report.phtml b/errors/default/report.phtml index 9e0a754c..dac1dcd4 100644 --- a/errors/default/report.phtml +++ b/errors/default/report.phtml @@ -20,7 +20,7 @@ * * @category Mage * @package Errors - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ ?> diff --git a/errors/design.xml b/errors/design.xml new file mode 100644 index 00000000..70b7f11c --- /dev/null +++ b/errors/design.xml @@ -0,0 +1,30 @@ + + + + default + diff --git a/errors/local.xml.sample b/errors/local.xml.sample index 6862ab4a..253bee51 100644 --- a/errors/local.xml.sample +++ b/errors/local.xml.sample @@ -21,7 +21,7 @@ * * @category Mage * @package Errors - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ --> diff --git a/errors/processor.php b/errors/processor.php index de281c5c..578904ed 100644 --- a/errors/processor.php +++ b/errors/processor.php @@ -20,7 +20,7 @@ * * @category Mage * @package Errors - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/errors/report.php b/errors/report.php index 8f23bc24..7ed9dd97 100644 --- a/errors/report.php +++ b/errors/report.php @@ -20,7 +20,7 @@ * * @category Mage * @package Errors - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) */ diff --git a/get.php b/get.php new file mode 100644 index 00000000..a7fe8020 --- /dev/null +++ b/get.php @@ -0,0 +1,208 @@ +

    Whoops, it looks like you have an invalid PHP version.' + . '

    Magento supports PHP 5.2.0 or newer. Find out how to install Magento using PHP-CGI as a work-around.

    '; + exit; +} +$start = microtime(true); +/** + * Error reporting + */ +error_reporting(E_ALL | E_STRICT); +ini_set('display_errors', 1); + +$ds = DIRECTORY_SEPARATOR; +$ps = PATH_SEPARATOR; +$bp = dirname(__FILE__); + +/** + * Set include path + */ + +$paths[] = $bp . $ds . 'app' . $ds . 'code' . $ds . 'local'; +$paths[] = $bp . $ds . 'app' . $ds . 'code' . $ds . 'community'; +$paths[] = $bp . $ds . 'app' . $ds . 'code' . $ds . 'core'; +$paths[] = $bp . $ds . 'lib'; + +$appPath = implode($ps, $paths); +set_include_path($appPath . $ps . get_include_path()); + +include_once 'Mage/Core/functions.php'; +include_once 'Varien/Autoload.php'; + +Varien_Autoload::register(); + +$varDirectory = $bp . $ds . Mage_Core_Model_Config_Options::VAR_DIRECTORY; + +$configCacheFile = $varDirectory . $ds . 'resource_config.json'; + +$mediaDirectory = null; +$allowedResources = array(); + +if (file_exists($configCacheFile) && is_readable($configCacheFile)) { + $config = json_decode(file_get_contents($configCacheFile), true); + + //checking update time + if (filemtime($configCacheFile) + $config['update_time'] > time()) { + $mediaDirectory = trim(str_replace($bp . $ds, '', $config['media_directory']), $ds); + $allowedResources = array_merge($allowedResources, $config['allowed_resources']); + } +} + +$request = new Zend_Controller_Request_Http(); + +$pathInfo = str_replace('..', '', ltrim($request->getPathInfo(), '/')); + +$filePath = str_replace('/', $ds, rtrim($bp, $ds) . $ds . $pathInfo); + +if ($mediaDirectory) { + if (0 !== stripos($pathInfo, $mediaDirectory . '/') || is_dir($filePath)) { + sendNotFoundPage(); + } + + $relativeFilename = str_replace($mediaDirectory . '/', '', $pathInfo); + checkResource($relativeFilename, $allowedResources); + sendFile($filePath); +} + +$mageFilename = 'app/Mage.php'; + +if (!file_exists($mageFilename)) { + echo $mageFilename . ' was not found'; +} + +require_once $mageFilename; + +umask(0); + +/* Store or website code */ +$mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : ''; + +/* Run store or run website */ +$mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store'; + +if (empty($mediaDirectory)) { + Mage::init($mageRunCode, $mageRunType); +} else { + Mage::init( + $mageRunCode, + $mageRunType, + array('cache' => array('disallow_save' => true)), + array('Mage_Core') + ); +} + +if (!$mediaDirectory) { + $config = Mage_Core_Model_File_Storage::getScriptConfig(); + $mediaDirectory = str_replace($bp . $ds, '', $config['media_directory']); + $allowedResources = array_merge($allowedResources, $config['allowed_resources']); + + $relativeFilename = str_replace($mediaDirectory . '/', '', $pathInfo); + + $fp = fopen($configCacheFile, 'w'); + if (flock($fp, LOCK_EX | LOCK_NB)) { + ftruncate($fp, 0); + fwrite($fp, json_encode($config)); + } + flock($fp, LOCK_UN); + fclose($fp); + + checkResource($relativeFilename, $allowedResources); +} + +if (0 !== stripos($pathInfo, $mediaDirectory . '/')) { + sendNotFoundPage(); +} + +try { + $databaseFileSotrage = Mage::getModel('core/file_storage_database'); + $databaseFileSotrage->loadByFilename($relativeFilename); +} catch (Exception $e) { +} +if ($databaseFileSotrage->getId()) { + $directory = dirname($filePath); + if (!is_dir($directory)) { + mkdir($directory, 0777, true); + } + + $fp = fopen($filePath, 'w'); + if (flock($fp, LOCK_EX | LOCK_NB)) { + ftruncate($fp, 0); + fwrite($fp, $databaseFileSotrage->getContent()); + } + flock($fp, LOCK_UN); + fclose($fp); +} + +sendFile($filePath); +sendNotFoundPage(); + +/** + * Send 404 + */ +function sendNotFoundPage() +{ + header('HTTP/1.0 404 Not Found'); + exit; +} + +/** + * Check resource by whitelist + * + * @param string $resource + * @param array $allowedResources + */ +function checkResource($resource, array $allowedResources) +{ + $isResourceAllowed = false; + foreach ($allowedResources as $allowedResource) { + if (0 === stripos($resource, $allowedResource)) { + $isResourceAllowed = true; + } + } + + if (!$isResourceAllowed) { + sendNotFoundPage(); + } +} +/** + * Send file to browser + * + * @param string $file + */ +function sendFile($file) +{ + if (file_exists($file) || is_readable($file)) { + $transfer = new Varien_File_Transfer_Adapter_Http(); + $transfer->send($file); + exit; + } +} diff --git a/index.php b/index.php index 92c1c587..99181c05 100644 --- a/index.php +++ b/index.php @@ -25,7 +25,12 @@ */ if (version_compare(phpversion(), '5.2.0', '<')===true) { - echo '

    Whoops, it looks like you have an invalid PHP version.

    Magento supports PHP 5.2.0 or newer. Find out how to install Magento using PHP-CGI as a work-around.

    '; + echo '
    +
    +

    +Whoops, it looks like you have an invalid PHP version.

    Magento supports PHP 5.2.0 or newer. +Find out how to install + Magento using PHP-CGI as a work-around.

    '; exit; } @@ -37,12 +42,14 @@ /** * Compilation includes configuration file */ -$compilerConfig = 'includes/config.php'; +define('MAGENTO_ROOT', getcwd()); + +$compilerConfig = MAGENTO_ROOT . '/includes/config.php'; if (file_exists($compilerConfig)) { include $compilerConfig; } -$mageFilename = 'app/Mage.php'; +$mageFilename = MAGENTO_ROOT . '/app/Mage.php'; $maintenanceFile = 'maintenance.flag'; if (!file_exists($mageFilename)) { diff --git a/install.php b/install.php index c31492cc..a96fa2c0 100644 --- a/install.php +++ b/install.php @@ -83,6 +83,7 @@ * --db_host // required, You can specify server port, ex.: localhost:3307 * // If you are not using default UNIX socket, you can specify it * // here instead of host, ex.: /var/run/mysqld/mysqld.sock + * --db_model // Database type (mysql4 by default) * --db_name // required, Database Name * --db_user // required, Database User Name * --db_pass // required, Database User Password @@ -103,6 +104,8 @@ * // Provide a complete base URL for SSL connection. * // For example: https://www.mydomain.com/magento/ * --use_secure_admin // optional, Run admin interface with SSL + * Backend interface options: + * --enable_charts // optional, Enables Charts on the backend's dashboard * Admin user personal information: * --admin_lastname // required, admin user last name * --admin_firstname // required, admin user first name @@ -149,4 +152,4 @@ } } } -exit; // dont delete this +exit(1); // don't delete this as this should notify about failed installation diff --git a/js/calendar/calendar.js b/js/calendar/calendar.js index 9b2aa86f..310d2e30 100644 --- a/js/calendar/calendar.js +++ b/js/calendar/calendar.js @@ -1,4 +1,4 @@ -/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo * ----------------------------------------------------------- * * The DHTML Calendar, version 1.0 "It is happening again" @@ -16,66 +16,66 @@ /** The Calendar object constructor. */ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { - // member variables - this.activeDiv = null; - this.currentDateEl = null; - this.getDateStatus = null; - this.getDateToolTip = null; - this.getDateText = null; - this.timeout = null; - this.onSelected = onSelected || null; - this.onClose = onClose || null; - this.dragging = false; - this.hidden = false; - this.minYear = 1970; - this.maxYear = 2050; - this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; - this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; - this.isPopup = true; - this.weekNumbers = true; - this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. - this.showsOtherMonths = false; - this.dateStr = dateStr; - this.ar_days = null; - this.showsTime = false; - this.time24 = true; - this.yearStep = 2; - this.hiliteToday = true; - this.multiple = null; - // HTML elements - this.table = null; - this.element = null; - this.tbody = null; - this.firstdayname = null; - // Combo boxes - this.monthsCombo = null; - this.yearsCombo = null; - this.hilitedMonth = null; - this.activeMonth = null; - this.hilitedYear = null; - this.activeYear = null; - // Information - this.dateClicked = false; - - // one-time initializations - if (typeof Calendar._SDN == "undefined") { - // table of short day names - if (typeof Calendar._SDN_len == "undefined") - Calendar._SDN_len = 3; - var ar = new Array(); - for (var i = 8; i > 0;) { - ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); - } - Calendar._SDN = ar; - // table of short month names - if (typeof Calendar._SMN_len == "undefined") - Calendar._SMN_len = 3; - ar = new Array(); - for (var i = 12; i > 0;) { - ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); - } - Calendar._SMN = ar; - } + // member variables + this.activeDiv = null; + this.currentDateEl = null; + this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; + this.timeout = null; + this.onSelected = onSelected || null; + this.onClose = onClose || null; + this.dragging = false; + this.hidden = false; + this.minYear = 1970; + this.maxYear = 2050; + this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"]; + this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; + this.isPopup = true; + this.weekNumbers = true; + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. + this.showsOtherMonths = false; + this.dateStr = dateStr; + this.ar_days = null; + this.showsTime = false; + this.time24 = true; + this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; + // HTML elements + this.table = null; + this.element = null; + this.tbody = null; + this.firstdayname = null; + // Combo boxes + this.monthsCombo = null; + this.yearsCombo = null; + this.hilitedMonth = null; + this.activeMonth = null; + this.hilitedYear = null; + this.activeYear = null; + // Information + this.dateClicked = false; + + // one-time initializations + if (typeof Calendar._SDN == "undefined") { + // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; + var ar = new Array(); + for (var i = 8; i > 0;) { + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); + } + Calendar._SDN = ar; + // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; + ar = new Array(); + for (var i = 12; i > 0;) { + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); + } + Calendar._SMN = ar; + } }; // ** constants @@ -85,7 +85,7 @@ Calendar._C = null; /// detect a special case of "web browser" Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && - !/opera/i.test(navigator.userAgent) ); + !/opera/i.test(navigator.userAgent) ); Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) ); @@ -166,119 +166,119 @@ Calendar.getAbsolutePos = function(element) { // variant 2 (not working) -// var SL = 0, ST = 0; -// var is_div = /^div$/i.test(el.tagName); -// if (is_div && el.scrollLeft) -// SL = el.scrollLeft; -// if (is_div && el.scrollTop) -// ST = el.scrollTop; -// var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; -// if (el.offsetParent) { -// var tmp = this.getAbsolutePos(el.offsetParent); -// r.x += tmp.x; -// r.y += tmp.y; -// } -// return r; +// var SL = 0, ST = 0; +// var is_div = /^div$/i.test(el.tagName); +// if (is_div && el.scrollLeft) +// SL = el.scrollLeft; +// if (is_div && el.scrollTop) +// ST = el.scrollTop; +// var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; +// if (el.offsetParent) { +// var tmp = this.getAbsolutePos(el.offsetParent); +// r.x += tmp.x; +// r.y += tmp.y; +// } +// return r; }; Calendar.isRelated = function (el, evt) { - var related = evt.relatedTarget; - if (!related) { - var type = evt.type; - if (type == "mouseover") { - related = evt.fromElement; - } else if (type == "mouseout") { - related = evt.toElement; - } - } - while (related) { - if (related == el) { - return true; - } - related = related.parentNode; - } - return false; + var related = evt.relatedTarget; + if (!related) { + var type = evt.type; + if (type == "mouseover") { + related = evt.fromElement; + } else if (type == "mouseout") { + related = evt.toElement; + } + } + while (related) { + if (related == el) { + return true; + } + related = related.parentNode; + } + return false; }; Calendar.removeClass = function(el, className) { - if (!(el && el.className)) { - return; - } - var cls = el.className.split(" "); - var ar = new Array(); - for (var i = cls.length; i > 0;) { - if (cls[--i] != className) { - ar[ar.length] = cls[i]; - } - } - el.className = ar.join(" "); + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); }; Calendar.addClass = function(el, className) { - Calendar.removeClass(el, className); - el.className += " " + className; + Calendar.removeClass(el, className); + el.className += " " + className; }; // FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. Calendar.getElement = function(ev) { - var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; - while (f.nodeType != 1 || /^div$/i.test(f.tagName)) - f = f.parentNode; - return f; + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; }; Calendar.getTargetElement = function(ev) { - var f = Calendar.is_ie ? window.event.srcElement : ev.target; - while (f.nodeType != 1) - f = f.parentNode; - return f; + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; }; Calendar.stopEvent = function(ev) { - ev || (ev = window.event); - if (Calendar.is_ie) { - ev.cancelBubble = true; - ev.returnValue = false; - } else { - ev.preventDefault(); - ev.stopPropagation(); - } - return false; + ev || (ev = window.event); + if (Calendar.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } + return false; }; Calendar.addEvent = function(el, evname, func) { - if (el.attachEvent) { // IE - el.attachEvent("on" + evname, func); - } else if (el.addEventListener) { // Gecko / W3C - el.addEventListener(evname, func, true); - } else { - el["on" + evname] = func; - } + if (el.attachEvent) { // IE + el.attachEvent("on" + evname, func); + } else if (el.addEventListener) { // Gecko / W3C + el.addEventListener(evname, func, true); + } else { + el["on" + evname] = func; + } }; Calendar.removeEvent = function(el, evname, func) { - if (el.detachEvent) { // IE - el.detachEvent("on" + evname, func); - } else if (el.removeEventListener) { // Gecko / W3C - el.removeEventListener(evname, func, true); - } else { - el["on" + evname] = null; - } + if (el.detachEvent) { // IE + el.detachEvent("on" + evname, func); + } else if (el.removeEventListener) { // Gecko / W3C + el.removeEventListener(evname, func, true); + } else { + el["on" + evname] = null; + } }; Calendar.createElement = function(type, parent) { - var el = null; - if (document.createElementNS) { - // use the XHTML namespace; IE won't normally get here unless - // _they_ "fix" the DOM2 implementation. - el = document.createElementNS("http://www.w3.org/1999/xhtml", type); - } else { - el = document.createElement(type); - } - if (typeof parent != "undefined") { - parent.appendChild(el); - } - return el; + var el = null; + if (document.createElementNS) { + // use the XHTML namespace; IE won't normally get here unless + // _they_ "fix" the DOM2 implementation. + el = document.createElementNS("http://www.w3.org/1999/xhtml", type); + } else { + el = document.createElement(type); + } + if (typeof parent != "undefined") { + parent.appendChild(el); + } + return el; }; // END: UTILITY FUNCTIONS @@ -287,355 +287,355 @@ Calendar.createElement = function(type, parent) { /** Internal -- adds a set of events to make some element behave like a button. */ Calendar._add_evs = function(el) { - with (Calendar) { - addEvent(el, "mouseover", dayMouseOver); - addEvent(el, "mousedown", dayMouseDown); - addEvent(el, "mouseout", dayMouseOut); - if (is_ie) { - addEvent(el, "dblclick", dayMouseDblClick); - el.setAttribute("unselectable", true); - } - } + with (Calendar) { + addEvent(el, "mouseover", dayMouseOver); + addEvent(el, "mousedown", dayMouseDown); + addEvent(el, "mouseout", dayMouseOut); + if (is_ie) { + addEvent(el, "dblclick", dayMouseDblClick); + el.setAttribute("unselectable", true); + } + } }; Calendar.findMonth = function(el) { - if (typeof el.month != "undefined") { - return el; - } else if (typeof el.parentNode.month != "undefined") { - return el.parentNode; - } - return null; + if (typeof el.month != "undefined") { + return el; + } else if (typeof el.parentNode.month != "undefined") { + return el.parentNode; + } + return null; }; Calendar.findYear = function(el) { - if (typeof el.year != "undefined") { - return el; - } else if (typeof el.parentNode.year != "undefined") { - return el.parentNode; - } - return null; + if (typeof el.year != "undefined") { + return el; + } else if (typeof el.parentNode.year != "undefined") { + return el.parentNode; + } + return null; }; Calendar.showMonthsCombo = function () { - var cal = Calendar._C; - if (!cal) { - return false; - } - var cal = cal; - var cd = cal.activeDiv; - var mc = cal.monthsCombo; - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - if (cal.activeMonth) { - Calendar.removeClass(cal.activeMonth, "active"); - } - var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; - Calendar.addClass(mon, "active"); - cal.activeMonth = mon; - var s = mc.style; - s.display = "block"; - if (cd.navtype < 0) - s.left = cd.offsetLeft + "px"; - else { - var mcw = mc.offsetWidth; - if (typeof mcw == "undefined") - // Konqueror brain-dead techniques - mcw = 50; - s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; - } - s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var mc = cal.monthsCombo; + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + if (cal.activeMonth) { + Calendar.removeClass(cal.activeMonth, "active"); + } + var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; + Calendar.addClass(mon, "active"); + cal.activeMonth = mon; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var mcw = mc.offsetWidth; + if (typeof mcw == "undefined") + // Konqueror brain-dead techniques + mcw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; }; Calendar.showYearsCombo = function (fwd) { - var cal = Calendar._C; - if (!cal) { - return false; - } - var cal = cal; - var cd = cal.activeDiv; - var yc = cal.yearsCombo; - if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - if (cal.activeYear) { - Calendar.removeClass(cal.activeYear, "active"); - } - cal.activeYear = null; - var Y = cal.date.getFullYear() + (fwd ? 1 : -1); - var yr = yc.firstChild; - var show = false; - for (var i = 12; i > 0; --i) { - if (Y >= cal.minYear && Y <= cal.maxYear) { - yr.innerHTML = Y; - yr.year = Y; - yr.style.display = "block"; - show = true; - } else { - yr.style.display = "none"; - } - yr = yr.nextSibling; - Y += fwd ? cal.yearStep : -cal.yearStep; - } - if (show) { - var s = yc.style; - s.display = "block"; - if (cd.navtype < 0) - s.left = cd.offsetLeft + "px"; - else { - var ycw = yc.offsetWidth; - if (typeof ycw == "undefined") - // Konqueror brain-dead techniques - ycw = 50; - s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; - } - s.top = (cd.offsetTop + cd.offsetHeight) + "px"; - } + var cal = Calendar._C; + if (!cal) { + return false; + } + var cal = cal; + var cd = cal.activeDiv; + var yc = cal.yearsCombo; + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + if (cal.activeYear) { + Calendar.removeClass(cal.activeYear, "active"); + } + cal.activeYear = null; + var Y = cal.date.getFullYear() + (fwd ? 1 : -1); + var yr = yc.firstChild; + var show = false; + for (var i = 12; i > 0; --i) { + if (Y >= cal.minYear && Y <= cal.maxYear) { + yr.innerHTML = Y; + yr.year = Y; + yr.style.display = "block"; + show = true; + } else { + yr.style.display = "none"; + } + yr = yr.nextSibling; + Y += fwd ? cal.yearStep : -cal.yearStep; + } + if (show) { + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else { + var ycw = yc.offsetWidth; + if (typeof ycw == "undefined") + // Konqueror brain-dead techniques + ycw = 50; + s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px"; + } + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; + } }; // event handlers Calendar.tableMouseUp = function(ev) { - var cal = Calendar._C; - if (!cal) { - return false; - } - if (cal.timeout) { - clearTimeout(cal.timeout); - } - var el = cal.activeDiv; - if (!el) { - return false; - } - var target = Calendar.getTargetElement(ev); - ev || (ev = window.event); - Calendar.removeClass(el, "active"); - if (target == el || target.parentNode == el) { - Calendar.cellClick(el, ev); - } - var mon = Calendar.findMonth(target); - var date = null; - if (mon) { - date = new CalendarDateObject(cal.date); - if (mon.month != date.getMonth()) { - date.setMonth(mon.month); - cal.setDate(date); - cal.dateClicked = false; - cal.callHandler(); - } - } else { - var year = Calendar.findYear(target); - if (year) { - date = new CalendarDateObject(cal.date); - if (year.year != date.getFullYear()) { - date.setFullYear(year.year); - cal.setDate(date); - cal.dateClicked = false; - cal.callHandler(); - } - } - } - with (Calendar) { - removeEvent(document, "mouseup", tableMouseUp); - removeEvent(document, "mouseover", tableMouseOver); - removeEvent(document, "mousemove", tableMouseOver); - cal._hideCombos(); - _C = null; - return stopEvent(ev); - } + var cal = Calendar._C; + if (!cal) { + return false; + } + if (cal.timeout) { + clearTimeout(cal.timeout); + } + var el = cal.activeDiv; + if (!el) { + return false; + } + var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); + Calendar.removeClass(el, "active"); + if (target == el || target.parentNode == el) { + Calendar.cellClick(el, ev); + } + var mon = Calendar.findMonth(target); + var date = null; + if (mon) { + date = new CalendarDateObject(cal.date); + if (mon.month != date.getMonth()) { + date.setMonth(mon.month); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } else { + var year = Calendar.findYear(target); + if (year) { + date = new CalendarDateObject(cal.date); + if (year.year != date.getFullYear()) { + date.setFullYear(year.year); + cal.setDate(date); + cal.dateClicked = false; + cal.callHandler(); + } + } + } + with (Calendar) { + removeEvent(document, "mouseup", tableMouseUp); + removeEvent(document, "mouseover", tableMouseOver); + removeEvent(document, "mousemove", tableMouseOver); + cal._hideCombos(); + _C = null; + return stopEvent(ev); + } }; Calendar.tableMouseOver = function (ev) { - var cal = Calendar._C; - if (!cal) { - return; - } - var el = cal.activeDiv; - var target = Calendar.getTargetElement(ev); - if (target == el || target.parentNode == el) { - Calendar.addClass(el, "hilite active"); - Calendar.addClass(el.parentNode, "rowhilite"); - } else { - if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) - Calendar.removeClass(el, "active"); - Calendar.removeClass(el, "hilite"); - Calendar.removeClass(el.parentNode, "rowhilite"); - } - ev || (ev = window.event); - if (el.navtype == 50 && target != el) { - var pos = Calendar.getAbsolutePos(el); - var w = el.offsetWidth; - var x = ev.clientX; - var dx; - var decrease = true; - if (x > pos.x + w) { - dx = x - pos.x - w; - decrease = false; - } else - dx = pos.x - x; - - if (dx < 0) dx = 0; - var range = el._range; - var current = el._current; - var count = Math.floor(dx / 10) % range.length; - for (var i = range.length; --i >= 0;) - if (range[i] == current) - break; - while (count-- > 0) - if (decrease) { - if (--i < 0) - i = range.length - 1; - } else if ( ++i >= range.length ) - i = 0; - var newval = range[i]; - el.innerHTML = newval; - - cal.onUpdateTime(); - } - var mon = Calendar.findMonth(target); - if (mon) { - if (mon.month != cal.date.getMonth()) { - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - Calendar.addClass(mon, "hilite"); - cal.hilitedMonth = mon; - } else if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - } else { - if (cal.hilitedMonth) { - Calendar.removeClass(cal.hilitedMonth, "hilite"); - } - var year = Calendar.findYear(target); - if (year) { - if (year.year != cal.date.getFullYear()) { - if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - Calendar.addClass(year, "hilite"); - cal.hilitedYear = year; - } else if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - } else if (cal.hilitedYear) { - Calendar.removeClass(cal.hilitedYear, "hilite"); - } - } - return Calendar.stopEvent(ev); + var cal = Calendar._C; + if (!cal) { + return; + } + var el = cal.activeDiv; + var target = Calendar.getTargetElement(ev); + if (target == el || target.parentNode == el) { + Calendar.addClass(el, "hilite active"); + Calendar.addClass(el.parentNode, "rowhilite"); + } else { + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); + Calendar.removeClass(el, "hilite"); + Calendar.removeClass(el.parentNode, "rowhilite"); + } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + + cal.onUpdateTime(); + } + var mon = Calendar.findMonth(target); + if (mon) { + if (mon.month != cal.date.getMonth()) { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + Calendar.addClass(mon, "hilite"); + cal.hilitedMonth = mon; + } else if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } + var year = Calendar.findYear(target); + if (year) { + if (year.year != cal.date.getFullYear()) { + if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + Calendar.addClass(year, "hilite"); + cal.hilitedYear = year; + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); + } + } + return Calendar.stopEvent(ev); }; Calendar.tableMouseDown = function (ev) { - if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { - return Calendar.stopEvent(ev); - } + if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) { + return Calendar.stopEvent(ev); + } }; Calendar.calDragIt = function (ev) { - var cal = Calendar._C; - if (!(cal && cal.dragging)) { - return false; - } - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posX = ev.pageX; - posY = ev.pageY; - } - cal.hideShowCovered(); - var st = cal.element.style; - st.left = (posX - cal.xOffs) + "px"; - st.top = (posY - cal.yOffs) + "px"; - return Calendar.stopEvent(ev); + var cal = Calendar._C; + if (!(cal && cal.dragging)) { + return false; + } + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posX = ev.pageX; + posY = ev.pageY; + } + cal.hideShowCovered(); + var st = cal.element.style; + st.left = (posX - cal.xOffs) + "px"; + st.top = (posY - cal.yOffs) + "px"; + return Calendar.stopEvent(ev); }; Calendar.calDragEnd = function (ev) { - var cal = Calendar._C; - if (!cal) { - return false; - } - cal.dragging = false; - with (Calendar) { - removeEvent(document, "mousemove", calDragIt); - removeEvent(document, "mouseup", calDragEnd); - tableMouseUp(ev); - } - cal.hideShowCovered(); + var cal = Calendar._C; + if (!cal) { + return false; + } + cal.dragging = false; + with (Calendar) { + removeEvent(document, "mousemove", calDragIt); + removeEvent(document, "mouseup", calDragEnd); + tableMouseUp(ev); + } + cal.hideShowCovered(); }; Calendar.dayMouseDown = function(ev) { - var el = Calendar.getElement(ev); - if (el.disabled) { - return false; - } - var cal = el.calendar; - cal.activeDiv = el; - Calendar._C = cal; - if (el.navtype != 300) with (Calendar) { - if (el.navtype == 50) { - el._current = el.innerHTML; - addEvent(document, "mousemove", tableMouseOver); - } else - addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); - addClass(el, "hilite active"); - addEvent(document, "mouseup", tableMouseUp); - } else if (cal.isPopup) { - cal._dragStart(ev); - } - if (el.navtype == -1 || el.navtype == 1) { - if (cal.timeout) clearTimeout(cal.timeout); - cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); - } else if (el.navtype == -2 || el.navtype == 2) { - if (cal.timeout) clearTimeout(cal.timeout); - cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); - } else { - cal.timeout = null; - } - return Calendar.stopEvent(ev); + var el = Calendar.getElement(ev); + if (el.disabled) { + return false; + } + var cal = el.calendar; + cal.activeDiv = el; + Calendar._C = cal; + if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) { + el._current = el.innerHTML; + addEvent(document, "mousemove", tableMouseOver); + } else + addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); + addClass(el, "hilite active"); + addEvent(document, "mouseup", tableMouseUp); + } else if (cal.isPopup) { + cal._dragStart(ev); + } + if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); + } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); + cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); + } else { + cal.timeout = null; + } + return Calendar.stopEvent(ev); }; Calendar.dayMouseDblClick = function(ev) { - Calendar.cellClick(Calendar.getElement(ev), ev || window.event); - if (Calendar.is_ie) { - document.selection.empty(); - } + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); + if (Calendar.is_ie) { + document.selection.empty(); + } }; Calendar.dayMouseOver = function(ev) { - var el = Calendar.getElement(ev); - if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { - return false; - } - if (el.ttip) { - if (el.ttip.substr(0, 1) == "_") { - el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); - } - el.calendar.tooltips.innerHTML = el.ttip; - } - if (el.navtype != 300) { - Calendar.addClass(el, "hilite"); - if (el.caldate) { - Calendar.addClass(el.parentNode, "rowhilite"); - } - } - return Calendar.stopEvent(ev); + var el = Calendar.getElement(ev); + if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) { + return false; + } + if (el.ttip) { + if (el.ttip.substr(0, 1) == "_") { + el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); + } + el.calendar.tooltips.innerHTML = el.ttip; + } + if (el.navtype != 300) { + Calendar.addClass(el, "hilite"); + if (el.caldate) { + Calendar.addClass(el.parentNode, "rowhilite"); + } + } + return Calendar.stopEvent(ev); }; Calendar.dayMouseOut = function(ev) { - with (Calendar) { - var el = getElement(ev); - if (isRelated(el, ev) || _C || el.disabled) - return false; - removeClass(el, "hilite"); - if (el.caldate) - removeClass(el.parentNode, "rowhilite"); - if (el.calendar) - el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; - return stopEvent(ev); - } + with (Calendar) { + var el = getElement(ev); + if (isRelated(el, ev) || _C || el.disabled) + return false; + removeClass(el, "hilite"); + if (el.caldate) + removeClass(el.parentNode, "rowhilite"); + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; + return stopEvent(ev); + } }; /** @@ -643,135 +643,135 @@ Calendar.dayMouseOut = function(ev) { * calendar. */ Calendar.cellClick = function(el, ev) { - var cal = el.calendar; - var closing = false; - var newdate = false; - var date = null; - if (typeof el.navtype == "undefined") { - if (cal.currentDateEl) { - Calendar.removeClass(cal.currentDateEl, "selected"); - Calendar.addClass(el, "selected"); - closing = (cal.currentDateEl == el); - if (!closing) { - cal.currentDateEl = el; - } - } - cal.date.setDateOnly(el.caldate); - date = cal.date; - var other_month = !(cal.dateClicked = !el.otherMonth); - if (!other_month && !cal.currentDateEl) - cal._toggleMultipleDate(new CalendarDateObject(date)); - else - newdate = !el.disabled; - // a date was clicked - if (other_month) - cal._init(cal.firstDayOfWeek, date); - } else { - if (el.navtype == 200) { - Calendar.removeClass(el, "hilite"); - cal.callCloseHandler(); - return; - } - date = new CalendarDateObject(cal.date); - if (el.navtype == 0) - date.setDateOnly(new CalendarDateObject()); // TODAY - // unless "today" was clicked, we assume no date was clicked so - // the selected handler will know not to close the calenar when - // in single-click mode. - // cal.dateClicked = (el.navtype == 0); - cal.dateClicked = false; - var year = date.getFullYear(); - var mon = date.getMonth(); - function setMonth(m) { - var day = date.getDate(); - var max = date.getMonthDays(m); - if (day > max) { - date.setDate(max); - } - date.setMonth(m); - }; - switch (el.navtype) { - case 400: - Calendar.removeClass(el, "hilite"); - var text = Calendar._TT["ABOUT"]; - if (typeof text != "undefined") { - text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; - } else { - // FIXME: this should be removed as soon as lang files get updated! - text = "Help and about box text is not translated into this language.\n" + - "If you know this language and you feel generous please update\n" + - "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + - "and send it back to to get it into the distribution ;-)\n\n" + - "Thank you!\n" + - "http://dynarch.com/mishoo/calendar.epl\n"; - } - alert(text); - return; - case -2: - if (year > cal.minYear) { - date.setFullYear(year - 1); - } - break; - case -1: - if (mon > 0) { - setMonth(mon - 1); - } else if (year-- > cal.minYear) { - date.setFullYear(year); - setMonth(11); - } - break; - case 1: - if (mon < 11) { - setMonth(mon + 1); - } else if (year < cal.maxYear) { - date.setFullYear(year + 1); - setMonth(0); - } - break; - case 2: - if (year < cal.maxYear) { - date.setFullYear(year + 1); - } - break; - case 100: - cal.setFirstDayOfWeek(el.fdow); - return; - case 50: - var range = el._range; - var current = el.innerHTML; - for (var i = range.length; --i >= 0;) - if (range[i] == current) - break; - if (ev && ev.shiftKey) { - if (--i < 0) - i = range.length - 1; - } else if ( ++i >= range.length ) - i = 0; - var newval = range[i]; - el.innerHTML = newval; - cal.onUpdateTime(); - return; - case 0: - // TODAY will bring us here - if ((typeof cal.getDateStatus == "function") && - cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { - return false; - } - break; - } - if (!date.equalsTo(cal.date)) { - cal.setDate(date); - newdate = true; - } else if (el.navtype == 0) - newdate = closing = true; - } - if (newdate) { - ev && cal.callHandler(); - } - if (closing) { - Calendar.removeClass(el, "hilite"); - ev && cal.callCloseHandler(); - } + var cal = el.calendar; + var closing = false; + var newdate = false; + var date = null; + if (typeof el.navtype == "undefined") { + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } + } + cal.date.setDateOnly(el.caldate); + date = cal.date; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new CalendarDateObject(date)); + else + newdate = !el.disabled; + // a date was clicked + if (other_month) + cal._init(cal.firstDayOfWeek, date); + } else { + if (el.navtype == 200) { + Calendar.removeClass(el, "hilite"); + cal.callCloseHandler(); + return; + } + date = new CalendarDateObject(cal.date); + if (el.navtype == 0) + date.setDateOnly(new CalendarDateObject()); // TODAY + // unless "today" was clicked, we assume no date was clicked so + // the selected handler will know not to close the calenar when + // in single-click mode. + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; + var year = date.getFullYear(); + var mon = date.getMonth(); + function setMonth(m) { + var day = date.getDate(); + var max = date.getMonthDays(m); + if (day > max) { + date.setDate(max); + } + date.setMonth(m); + }; + switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; + case -2: + if (year > cal.minYear) { + date.setFullYear(year - 1); + } + break; + case -1: + if (mon > 0) { + setMonth(mon - 1); + } else if (year-- > cal.minYear) { + date.setFullYear(year); + setMonth(11); + } + break; + case 1: + if (mon < 11) { + setMonth(mon + 1); + } else if (year < cal.maxYear) { + date.setFullYear(year + 1); + setMonth(0); + } + break; + case 2: + if (year < cal.maxYear) { + date.setFullYear(year + 1); + } + break; + case 100: + cal.setFirstDayOfWeek(el.fdow); + return; + case 50: + var range = el._range; + var current = el.innerHTML; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (--i < 0) + i = range.length - 1; + } else if ( ++i >= range.length ) + i = 0; + var newval = range[i]; + el.innerHTML = newval; + cal.onUpdateTime(); + return; + case 0: + // TODAY will bring us here + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { + return false; + } + break; + } + if (!date.equalsTo(cal.date)) { + cal.setDate(date); + newdate = true; + } else if (el.navtype == 0) + newdate = closing = true; + } + if (newdate) { + ev && cal.callHandler(); + } + if (closing) { + Calendar.removeClass(el, "hilite"); + ev && cal.callCloseHandler(); + } }; // END: CALENDAR STATIC FUNCTIONS @@ -785,512 +785,512 @@ Calendar.cellClick = function(el, ev) { * hidden). Some properties need to be set before calling this function. */ Calendar.prototype.create = function (_par) { - var parent = null; - if (! _par) { - // default parent is the document body, in which case we create - // a popup calendar. - parent = document.getElementsByTagName("body")[0]; - this.isPopup = true; - } else { - parent = _par; - this.isPopup = false; - } - this.date = this.dateStr ? new CalendarDateObject(this.dateStr) : new CalendarDateObject(); - - var table = Calendar.createElement("table"); - this.table = table; - table.cellSpacing = 0; - table.cellPadding = 0; - table.calendar = this; - Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); - - var div = Calendar.createElement("div"); - this.element = div; - div.className = "calendar"; - if (this.isPopup) { - div.style.position = "absolute"; - div.style.display = "none"; - } - div.appendChild(table); - - var thead = Calendar.createElement("thead", table); - var cell = null; - var row = null; - - var cal = this; - var hh = function (text, cs, navtype) { - cell = Calendar.createElement("td", row); - cell.colSpan = cs; - cell.className = "button"; - if (navtype != 0 && Math.abs(navtype) <= 2) - cell.className += " nav"; - Calendar._add_evs(cell); - cell.calendar = cal; - cell.navtype = navtype; - cell.innerHTML = "
    " + text + "
    "; - return cell; - }; - - row = Calendar.createElement("tr", thead); - var title_length = 6; - (this.isPopup) && --title_length; - (this.weekNumbers) && ++title_length; - - hh("?", 1, 400).ttip = Calendar._TT["INFO"]; - this.title = hh("", title_length, 300); - this.title.className = "title"; - if (this.isPopup) { - this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; - this.title.style.cursor = "move"; - hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; - } - - row = Calendar.createElement("tr", thead); - row.className = "headrow"; - - this._nav_py = hh("«", 1, -2); - this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; - - this._nav_pm = hh("‹", 1, -1); - this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; - - this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); - this._nav_now.ttip = Calendar._TT["GO_TODAY"]; - - this._nav_nm = hh("›", 1, 1); - this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; - - this._nav_ny = hh("»", 1, 2); - this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; - - // day names - row = Calendar.createElement("tr", thead); - row.className = "daynames"; - if (this.weekNumbers) { - cell = Calendar.createElement("td", row); - cell.className = "name wn"; - cell.innerHTML = Calendar._TT["WK"]; - } - for (var i = 7; i > 0; --i) { - cell = Calendar.createElement("td", row); - if (!i) { - cell.navtype = 100; - cell.calendar = this; - Calendar._add_evs(cell); - } - } - this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; - this._displayWeekdays(); - - var tbody = Calendar.createElement("tbody", table); - this.tbody = tbody; - - for (i = 6; i > 0; --i) { - row = Calendar.createElement("tr", tbody); - if (this.weekNumbers) { - cell = Calendar.createElement("td", row); - } - for (var j = 7; j > 0; --j) { - cell = Calendar.createElement("td", row); - cell.calendar = this; - Calendar._add_evs(cell); - } - } - - if (this.showsTime) { - row = Calendar.createElement("tr", tbody); - row.className = "time"; - - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = 2; - cell.innerHTML = Calendar._TT["TIME"] || " "; - - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = this.weekNumbers ? 4 : 3; - - (function(){ - function makeTimePart(className, init, range_start, range_end) { - var part = Calendar.createElement("span", cell); - part.className = className; - part.innerHTML = init; - part.calendar = cal; - part.ttip = Calendar._TT["TIME_PART"]; - part.navtype = 50; - part._range = []; - if (typeof range_start != "number") - part._range = range_start; - else { - for (var i = range_start; i <= range_end; ++i) { - var txt; - if (i < 10 && range_end >= 10) txt = '0' + i; - else txt = '' + i; - part._range[part._range.length] = txt; - } - } - Calendar._add_evs(part); - return part; - }; - var hrs = cal.date.getHours(); - var mins = cal.date.getMinutes(); - var t12 = !cal.time24; - var pm = (hrs > 12); - if (t12 && pm) hrs -= 12; - var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); - var span = Calendar.createElement("span", cell); - span.innerHTML = ":"; - span.className = "colon"; - var M = makeTimePart("minute", mins, 0, 59); - var AP = null; - cell = Calendar.createElement("td", row); - cell.className = "time"; - cell.colSpan = 2; - if (t12) - AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); - else - cell.innerHTML = " "; - - cal.onSetTime = function() { - var pm, hrs = this.date.getHours(), - mins = this.date.getMinutes(); - if (t12) { - pm = (hrs >= 12); - if (pm) hrs -= 12; - if (hrs == 0) hrs = 12; - AP.innerHTML = pm ? "pm" : "am"; - } - H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; - M.innerHTML = (mins < 10) ? ("0" + mins) : mins; - }; - - cal.onUpdateTime = function() { - var date = this.date; - var h = parseInt(H.innerHTML, 10); - if (t12) { - if (/pm/i.test(AP.innerHTML) && h < 12) - h += 12; - else if (/am/i.test(AP.innerHTML) && h == 12) - h = 0; - } - var d = date.getDate(); - var m = date.getMonth(); - var y = date.getFullYear(); - date.setHours(h); - date.setMinutes(parseInt(M.innerHTML, 10)); - date.setFullYear(y); - date.setMonth(m); - date.setDate(d); - this.dateClicked = false; - this.callHandler(); - }; - })(); - } else { - this.onSetTime = this.onUpdateTime = function() {}; - } - - var tfoot = Calendar.createElement("tfoot", table); - - row = Calendar.createElement("tr", tfoot); - row.className = "footrow"; - - cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); - cell.className = "ttip"; - if (this.isPopup) { - cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; - cell.style.cursor = "move"; - } - this.tooltips = cell; - - div = Calendar.createElement("div", this.element); - this.monthsCombo = div; - div.className = "combo"; - for (i = 0; i < Calendar._MN.length; ++i) { - var mn = Calendar.createElement("div"); - mn.className = Calendar.is_ie ? "label-IEfix" : "label"; - mn.month = i; - mn.innerHTML = Calendar._SMN[i]; - div.appendChild(mn); - } - - div = Calendar.createElement("div", this.element); - this.yearsCombo = div; - div.className = "combo"; - for (i = 12; i > 0; --i) { - var yr = Calendar.createElement("div"); - yr.className = Calendar.is_ie ? "label-IEfix" : "label"; - div.appendChild(yr); - } - - this._init(this.firstDayOfWeek, this.date); - parent.appendChild(this.element); + var parent = null; + if (! _par) { + // default parent is the document body, in which case we create + // a popup calendar. + parent = document.getElementsByTagName("body")[0]; + this.isPopup = true; + } else { + parent = _par; + this.isPopup = false; + } + this.date = this.dateStr ? new CalendarDateObject(this.dateStr) : new CalendarDateObject(); + + var table = Calendar.createElement("table"); + this.table = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.calendar = this; + Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown); + + var div = Calendar.createElement("div"); + this.element = div; + div.className = "calendar"; + if (this.isPopup) { + div.style.position = "absolute"; + div.style.display = "none"; + } + div.appendChild(table); + + var thead = Calendar.createElement("thead", table); + var cell = null; + var row = null; + + var cal = this; + var hh = function (text, cs, navtype) { + cell = Calendar.createElement("td", row); + cell.colSpan = cs; + cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; + Calendar._add_evs(cell); + cell.calendar = cal; + cell.navtype = navtype; + cell.innerHTML = "
    " + text + "
    "; + return cell; + }; + + row = Calendar.createElement("tr", thead); + var title_length = 6; + (this.isPopup) && --title_length; + (this.weekNumbers) && ++title_length; + + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; + this.title = hh("", title_length, 300); + this.title.className = "title"; + if (this.isPopup) { + this.title.ttip = Calendar._TT["DRAG_TO_MOVE"]; + this.title.style.cursor = "move"; + hh("×", 1, 200).ttip = Calendar._TT["CLOSE"]; + } + + row = Calendar.createElement("tr", thead); + row.className = "headrow"; + + this._nav_py = hh("«", 1, -2); + this._nav_py.ttip = Calendar._TT["PREV_YEAR"]; + + this._nav_pm = hh("‹", 1, -1); + this._nav_pm.ttip = Calendar._TT["PREV_MONTH"]; + + this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0); + this._nav_now.ttip = Calendar._TT["GO_TODAY"]; + + this._nav_nm = hh("›", 1, 1); + this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"]; + + this._nav_ny = hh("»", 1, 2); + this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"]; + + // day names + row = Calendar.createElement("tr", thead); + row.className = "daynames"; + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + cell.className = "name wn"; + cell.innerHTML = Calendar._TT["WK"]; + } + for (var i = 7; i > 0; --i) { + cell = Calendar.createElement("td", row); + if (!i) { + cell.navtype = 100; + cell.calendar = this; + Calendar._add_evs(cell); + } + } + this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild; + this._displayWeekdays(); + + var tbody = Calendar.createElement("tbody", table); + this.tbody = tbody; + + for (i = 6; i > 0; --i) { + row = Calendar.createElement("tr", tbody); + if (this.weekNumbers) { + cell = Calendar.createElement("td", row); + } + for (var j = 7; j > 0; --j) { + cell = Calendar.createElement("td", row); + cell.calendar = this; + Calendar._add_evs(cell); + } + } + + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = Calendar._TT["TIME"] || " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.innerHTML = init; + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.innerHTML = ":"; + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.innerHTML, 10); + if (t12) { + if (/pm/i.test(AP.innerHTML) && h < 12) + h += 12; + else if (/am/i.test(AP.innerHTML) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.innerHTML, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + + var tfoot = Calendar.createElement("tfoot", table); + + row = Calendar.createElement("tr", tfoot); + row.className = "footrow"; + + cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300); + cell.className = "ttip"; + if (this.isPopup) { + cell.ttip = Calendar._TT["DRAG_TO_MOVE"]; + cell.style.cursor = "move"; + } + this.tooltips = cell; + + div = Calendar.createElement("div", this.element); + this.monthsCombo = div; + div.className = "combo"; + for (i = 0; i < Calendar._MN.length; ++i) { + var mn = Calendar.createElement("div"); + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; + mn.month = i; + mn.innerHTML = Calendar._SMN[i]; + div.appendChild(mn); + } + + div = Calendar.createElement("div", this.element); + this.yearsCombo = div; + div.className = "combo"; + for (i = 12; i > 0; --i) { + var yr = Calendar.createElement("div"); + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; + div.appendChild(yr); + } + + this._init(this.firstDayOfWeek, this.date); + parent.appendChild(this.element); }; /** keyboard navigation, only for popup calendars */ Calendar._keyEvent = function(ev) { - var cal = window._dynarch_popupCalendar; - if (!cal || cal.multiple) - return false; - (Calendar.is_ie) && (ev = window.event); - var act = (Calendar.is_ie || ev.type == "keypress"), - K = ev.keyCode; - if (ev.ctrlKey) { - switch (K) { - case 37: // KEY left - act && Calendar.cellClick(cal._nav_pm); - break; - case 38: // KEY up - act && Calendar.cellClick(cal._nav_py); - break; - case 39: // KEY right - act && Calendar.cellClick(cal._nav_nm); - break; - case 40: // KEY down - act && Calendar.cellClick(cal._nav_ny); - break; - default: - return false; - } - } else switch (K) { - case 32: // KEY space (now) - Calendar.cellClick(cal._nav_now); - break; - case 27: // KEY esc - act && cal.callCloseHandler(); - break; - case 37: // KEY left - case 38: // KEY up - case 39: // KEY right - case 40: // KEY down - if (act) { - var prev, x, y, ne, el, step; - prev = K == 37 || K == 38; - step = (K == 37 || K == 39) ? 1 : 7; - function setVars() { - el = cal.currentDateEl; - var p = el.pos; - x = p & 15; - y = p >> 4; - ne = cal.ar_days[y][x]; - };setVars(); - function prevMonth() { - var date = new CalendarDateObject(cal.date); - date.setDate(date.getDate() - step); - cal.setDate(date); - }; - function nextMonth() { - var date = new CalendarDateObject(cal.date); - date.setDate(date.getDate() + step); - cal.setDate(date); - }; - while (1) { - switch (K) { - case 37: // KEY left - if (--x >= 0) - ne = cal.ar_days[y][x]; - else { - x = 6; - K = 38; - continue; - } - break; - case 38: // KEY up - if (--y >= 0) - ne = cal.ar_days[y][x]; - else { - prevMonth(); - setVars(); - } - break; - case 39: // KEY right - if (++x < 7) - ne = cal.ar_days[y][x]; - else { - x = 0; - K = 40; - continue; - } - break; - case 40: // KEY down - if (++y < cal.ar_days.length) - ne = cal.ar_days[y][x]; - else { - nextMonth(); - setVars(); - } - break; - } - break; - } - if (ne) { - if (!ne.disabled) - Calendar.cellClick(ne); - else if (prev) - prevMonth(); - else - nextMonth(); - } - } - break; - case 13: // KEY enter - if (act) - Calendar.cellClick(cal.currentDateEl, ev); - break; - default: - return false; - } - return Calendar.stopEvent(ev); + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) + return false; + (Calendar.is_ie) && (ev = window.event); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; + if (ev.ctrlKey) { + switch (K) { + case 37: // KEY left + act && Calendar.cellClick(cal._nav_pm); + break; + case 38: // KEY up + act && Calendar.cellClick(cal._nav_py); + break; + case 39: // KEY right + act && Calendar.cellClick(cal._nav_nm); + break; + case 40: // KEY down + act && Calendar.cellClick(cal._nav_ny); + break; + default: + return false; + } + } else switch (K) { + case 32: // KEY space (now) + Calendar.cellClick(cal._nav_now); + break; + case 27: // KEY esc + act && cal.callCloseHandler(); + break; + case 37: // KEY left + case 38: // KEY up + case 39: // KEY right + case 40: // KEY down + if (act) { + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new CalendarDateObject(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new CalendarDateObject(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } + break; + } + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); + } + } + break; + case 13: // KEY enter + if (act) + Calendar.cellClick(cal.currentDateEl, ev); + break; + default: + return false; + } + return Calendar.stopEvent(ev); }; /** * (RE)Initializes the calendar to the given date and firstDayOfWeek */ Calendar.prototype._init = function (firstDayOfWeek, date) { - var today = new CalendarDateObject(), - TY = today.getFullYear(), - TM = today.getMonth(), - TD = today.getDate(); - this.table.style.visibility = "hidden"; - var year = date.getFullYear(); - if (year < this.minYear) { - year = this.minYear; - date.setFullYear(year); - } else if (year > this.maxYear) { - year = this.maxYear; - date.setFullYear(year); - } - this.firstDayOfWeek = firstDayOfWeek; - this.date = new CalendarDateObject(date); - var month = date.getMonth(); - var mday = date.getDate(); - var no_days = date.getMonthDays(); - - // calendar voodoo for computing the first day that would actually be - // displayed in the calendar, even if it's from the previous month. - // WARNING: this is magic. ;-) - date.setDate(1); - var day1 = (date.getDay() - this.firstDayOfWeek) % 7; - if (day1 < 0) - day1 += 7; - date.setDate(-day1); - date.setDate(date.getDate() + 1); - - var row = this.tbody.firstChild; - var MN = Calendar._SMN[month]; - var ar_days = this.ar_days = new Array(); - var weekend = Calendar._TT["WEEKEND"]; - var dates = this.multiple ? (this.datesCells = {}) : null; - for (var i = 0; i < 6; ++i, row = row.nextSibling) { - var cell = row.firstChild; - if (this.weekNumbers) { - cell.className = "day wn"; - cell.innerHTML = date.getWeekNumber(); - cell = cell.nextSibling; - } - row.className = "daysrow"; - var hasdays = false, iday, dpos = ar_days[i] = []; - for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { - iday = date.getDate(); - var wday = date.getDay(); - cell.className = "day"; - cell.pos = i << 4 | j; - dpos[j] = cell; - var current_month = (date.getMonth() == month); - if (!current_month) { - if (this.showsOtherMonths) { - cell.className += " othermonth"; - cell.otherMonth = true; - } else { - cell.className = "emptycell"; - cell.innerHTML = " "; - cell.disabled = true; - continue; - } - } else { - cell.otherMonth = false; - hasdays = true; - } - cell.disabled = false; - cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; - if (dates) - dates[date.print("%Y%m%d")] = cell; - if (this.getDateStatus) { - var status = this.getDateStatus(date, year, month, iday); - if (this.getDateToolTip) { - var toolTip = this.getDateToolTip(date, year, month, iday); - if (toolTip) - cell.title = toolTip; - } - if (status === true) { - cell.className += " disabled"; - cell.disabled = true; - } else { - if (/disabled/i.test(status)) - cell.disabled = true; - cell.className += " " + status; - } - } - if (!cell.disabled) { - cell.caldate = new CalendarDateObject(date); - cell.ttip = "_"; - if (!this.multiple && current_month - && iday == mday && this.hiliteToday) { - cell.className += " selected"; - this.currentDateEl = cell; - } - if (date.getFullYear() == TY && - date.getMonth() == TM && - iday == TD) { - cell.className += " today"; - cell.ttip += Calendar._TT["PART_TODAY"]; - } - if (weekend.indexOf(wday.toString()) != -1) - cell.className += cell.otherMonth ? " oweekend" : " weekend"; - } - } - if (!(hasdays || this.showsOtherMonths)) - row.className = "emptyrow"; - } - this.title.innerHTML = Calendar._MN[month] + ", " + year; - this.onSetTime(); - this.table.style.visibility = "visible"; - this._initMultipleDates(); - // PROFILE - // this.tooltips.innerHTML = "Generated in " + ((new CalendarDateObject()) - today) + " ms"; + var today = new CalendarDateObject(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); + this.table.style.visibility = "hidden"; + var year = date.getFullYear(); + if (year < this.minYear) { + year = this.minYear; + date.setFullYear(year); + } else if (year > this.maxYear) { + year = this.maxYear; + date.setFullYear(year); + } + this.firstDayOfWeek = firstDayOfWeek; + this.date = new CalendarDateObject(date); + var month = date.getMonth(); + var mday = date.getDate(); + var no_days = date.getMonthDays(); + + // calendar voodoo for computing the first day that would actually be + // displayed in the calendar, even if it's from the previous month. + // WARNING: this is magic. ;-) + date.setDate(1); + var day1 = (date.getDay() - this.firstDayOfWeek) % 7; + if (day1 < 0) + day1 += 7; + date.setDate(-day1); + date.setDate(date.getDate() + 1); + + var row = this.tbody.firstChild; + var MN = Calendar._SMN[month]; + var ar_days = this.ar_days = new Array(); + var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; + for (var i = 0; i < 6; ++i, row = row.nextSibling) { + var cell = row.firstChild; + if (this.weekNumbers) { + cell.className = "day wn"; + cell.innerHTML = date.getWeekNumber(); + cell = cell.nextSibling; + } + row.className = "daysrow"; + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); + var wday = date.getDay(); + cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; + var current_month = (date.getMonth() == month); + if (!current_month) { + if (this.showsOtherMonths) { + cell.className += " othermonth"; + cell.otherMonth = true; + } else { + cell.className = "emptycell"; + cell.innerHTML = " "; + cell.disabled = true; + continue; + } + } else { + cell.otherMonth = false; + hasdays = true; + } + cell.disabled = false; + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { + var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } + if (status === true) { + cell.className += " disabled"; + cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; + } + } + if (!cell.disabled) { + cell.caldate = new CalendarDateObject(date); + cell.ttip = "_"; + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { + cell.className += " selected"; + this.currentDateEl = cell; + } + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { + cell.className += " today"; + cell.ttip += Calendar._TT["PART_TODAY"]; + } + if (weekend.indexOf(wday.toString()) != -1) + cell.className += cell.otherMonth ? " oweekend" : " weekend"; + } + } + if (!(hasdays || this.showsOtherMonths)) + row.className = "emptyrow"; + } + this.title.innerHTML = Calendar._MN[month] + ", " + year; + this.onSetTime(); + this.table.style.visibility = "visible"; + this._initMultipleDates(); + // PROFILE + // this.tooltips.innerHTML = "Generated in " + ((new CalendarDateObject()) - today) + " ms"; }; Calendar.prototype._initMultipleDates = function() { - if (this.multiple) { - for (var i in this.multiple) { - var cell = this.datesCells[i]; - var d = this.multiple[i]; - if (!d) - continue; - if (cell) - cell.className += " selected"; - } - } + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } }; Calendar.prototype._toggleMultipleDate = function(date) { - if (this.multiple) { - var ds = date.print("%Y%m%d"); - var cell = this.datesCells[ds]; - if (cell) { - var d = this.multiple[ds]; - if (!d) { - Calendar.addClass(cell, "selected"); - this.multiple[ds] = date; - } else { - Calendar.removeClass(cell, "selected"); - delete this.multiple[ds]; - } - } - } + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } }; Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { - this.getDateToolTip = unaryFunction; + this.getDateToolTip = unaryFunction; }; /** @@ -1298,9 +1298,9 @@ Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { * date is different than the currently selected one). */ Calendar.prototype.setDate = function (date) { - if (!date.equalsTo(this.date)) { - this._init(this.firstDayOfWeek, date); - } + if (!date.equalsTo(this.date)) { + this._init(this.firstDayOfWeek, date); + } }; /** @@ -1310,13 +1310,13 @@ Calendar.prototype.setDate = function (date) { * should * change. */ Calendar.prototype.refresh = function () { - this._init(this.firstDayOfWeek, this.date); + this._init(this.firstDayOfWeek, this.date); }; /** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */ Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { - this._init(firstDayOfWeek, this.date); - this._displayWeekdays(); + this._init(firstDayOfWeek, this.date); + this._displayWeekdays(); }; /** @@ -1326,36 +1326,36 @@ Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) { * the passed date will be marked as disabled. */ Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { - this.getDateStatus = unaryFunction; + this.getDateStatus = unaryFunction; }; /** Customization of allowed year range for the calendar. */ Calendar.prototype.setRange = function (a, z) { - this.minYear = a; - this.maxYear = z; + this.minYear = a; + this.maxYear = z; }; /** Calls the first user handler (selectedHandler). */ Calendar.prototype.callHandler = function () { - if (this.onSelected) { - this.onSelected(this, this.date.print(this.dateFormat)); - } + if (this.onSelected) { + this.onSelected(this, this.date.print(this.dateFormat)); + } }; /** Calls the second user handler (closeHandler). */ Calendar.prototype.callCloseHandler = function () { - if (this.onClose) { - this.onClose(this); - } - this.hideShowCovered(); + if (this.onClose) { + this.onClose(this); + } + this.hideShowCovered(); }; /** Removes the calendar object from the DOM tree and destroys it. */ Calendar.prototype.destroy = function () { - var el = this.element.parentNode; - el.removeChild(this.element); - Calendar._C = null; - window._dynarch_popupCalendar = null; + var el = this.element.parentNode; + el.removeChild(this.element); + Calendar._C = null; + window._dynarch_popupCalendar = null; }; /** @@ -1363,50 +1363,50 @@ Calendar.prototype.destroy = function () { * its parent). */ Calendar.prototype.reparent = function (new_parent) { - var el = this.element; - el.parentNode.removeChild(el); - new_parent.appendChild(el); + var el = this.element; + el.parentNode.removeChild(el); + new_parent.appendChild(el); }; // This gets called when the user presses a mouse button anywhere in the // document, if the calendar is shown. If the click was outside the open // calendar this function closes it. Calendar._checkCalendar = function(ev) { - var calendar = window._dynarch_popupCalendar; - if (!calendar) { - return false; - } - var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); - for (; el != null && el != calendar.element; el = el.parentNode); - if (el == null) { - // calls closeHandler which should hide the calendar. - window._dynarch_popupCalendar.callCloseHandler(); - return Calendar.stopEvent(ev); - } + var calendar = window._dynarch_popupCalendar; + if (!calendar) { + return false; + } + var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev); + for (; el != null && el != calendar.element; el = el.parentNode); + if (el == null) { + // calls closeHandler which should hide the calendar. + window._dynarch_popupCalendar.callCloseHandler(); + return Calendar.stopEvent(ev); + } }; /** Shows the calendar. */ Calendar.prototype.show = function () { - var rows = this.table.getElementsByTagName("tr"); - for (var i = rows.length; i > 0;) { - var row = rows[--i]; - Calendar.removeClass(row, "rowhilite"); - var cells = row.getElementsByTagName("td"); - for (var j = cells.length; j > 0;) { - var cell = cells[--j]; - Calendar.removeClass(cell, "hilite"); - Calendar.removeClass(cell, "active"); - } - } - this.element.style.display = "block"; - this.hidden = false; - if (this.isPopup) { - window._dynarch_popupCalendar = this; - Calendar.addEvent(document, "keydown", Calendar._keyEvent); - Calendar.addEvent(document, "keypress", Calendar._keyEvent); - Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); - } - this.hideShowCovered(); + var rows = this.table.getElementsByTagName("tr"); + for (var i = rows.length; i > 0;) { + var row = rows[--i]; + Calendar.removeClass(row, "rowhilite"); + var cells = row.getElementsByTagName("td"); + for (var j = cells.length; j > 0;) { + var cell = cells[--j]; + Calendar.removeClass(cell, "hilite"); + Calendar.removeClass(cell, "active"); + } + } + this.element.style.display = "block"; + this.hidden = false; + if (this.isPopup) { + window._dynarch_popupCalendar = this; + Calendar.addEvent(document, "keydown", Calendar._keyEvent); + Calendar.addEvent(document, "keypress", Calendar._keyEvent); + Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); + } + this.hideShowCovered(); }; /** @@ -1414,14 +1414,14 @@ Calendar.prototype.show = function () { * element. */ Calendar.prototype.hide = function () { - if (this.isPopup) { - Calendar.removeEvent(document, "keydown", Calendar._keyEvent); - Calendar.removeEvent(document, "keypress", Calendar._keyEvent); - Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); - } - this.element.style.display = "none"; - this.hidden = true; - this.hideShowCovered(); + if (this.isPopup) { + Calendar.removeEvent(document, "keydown", Calendar._keyEvent); + Calendar.removeEvent(document, "keypress", Calendar._keyEvent); + Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar); + } + this.element.style.display = "none"; + this.hidden = true; + this.hideShowCovered(); }; /** @@ -1430,90 +1430,90 @@ Calendar.prototype.hide = function () { * to the parent's containing rectangle). */ Calendar.prototype.showAt = function (x, y) { - var s = this.element.style; - s.left = x + "px"; - s.top = y + "px"; - this.show(); + var s = this.element.style; + s.left = x + "px"; + s.top = y + "px"; + this.show(); }; /** Shows the calendar near a given element. */ Calendar.prototype.showAtElement = function (el, opts) { - var self = this; - var p = Calendar.getAbsolutePos(el); - if (!opts || typeof opts != "string") { - this.showAt(p.x, p.y + el.offsetHeight); - return true; - } - function fixPosition(box) { - if (box.x < 0) - box.x = 0; - if (box.y < 0) - box.y = 0; - var cp = document.createElement("div"); - var s = cp.style; - s.position = "absolute"; - s.right = s.bottom = s.width = s.height = "0px"; - document.body.appendChild(cp); - var br = Calendar.getAbsolutePos(cp); - document.body.removeChild(cp); - if (Calendar.is_ie) { - br.y += document.body.scrollTop; - br.x += document.body.scrollLeft; - } else { - br.y += window.scrollY; - br.x += window.scrollX; - } - var tmp = box.x + box.width - br.x; - if (tmp > 0) box.x -= tmp; - tmp = box.y + box.height - br.y; - if (tmp > 0) box.y -= tmp; - }; - this.element.style.display = "block"; - Calendar.continuation_for_the_fucking_khtml_browser = function() { - var w = self.element.offsetWidth; - var h = self.element.offsetHeight; - self.element.style.display = "none"; - var valign = opts.substr(0, 1); - var halign = "l"; - if (opts.length > 1) { - halign = opts.substr(1, 1); - } - // vertical alignment - switch (valign) { - case "T": p.y -= h; break; - case "B": p.y += el.offsetHeight; break; - case "C": p.y += (el.offsetHeight - h) / 2; break; - case "t": p.y += el.offsetHeight - h; break; - case "b": break; // already there - } - // horizontal alignment - switch (halign) { - case "L": p.x -= w; break; - case "R": p.x += el.offsetWidth; break; - case "C": p.x += (el.offsetWidth - w) / 2; break; - case "l": p.x += el.offsetWidth - w; break; - case "r": break; // already there - } - p.width = w; - p.height = h + 40; - self.monthsCombo.style.display = "none"; - fixPosition(p); - self.showAt(p.x, p.y); - }; - if (Calendar.is_khtml) - setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); - else - Calendar.continuation_for_the_fucking_khtml_browser(); + var self = this; + var p = Calendar.getAbsolutePos(el); + if (!opts || typeof opts != "string") { + this.showAt(p.x, p.y + el.offsetHeight); + return true; + } + function fixPosition(box) { + if (box.x < 0) + box.x = 0; + if (box.y < 0) + box.y = 0; + var cp = document.createElement("div"); + var s = cp.style; + s.position = "absolute"; + s.right = s.bottom = s.width = s.height = "0px"; + document.body.appendChild(cp); + var br = Calendar.getAbsolutePos(cp); + document.body.removeChild(cp); + if (Calendar.is_ie) { + br.y += document.documentElement.scrollTop; + br.x += document.documentElement.scrollLeft; + } else { + br.y += window.scrollY; + br.x += window.scrollX; + } + var tmp = box.x + box.width - br.x; + if (tmp > 0) box.x -= tmp; + tmp = box.y + box.height - br.y; + if (tmp > 0) box.y -= tmp; + }; + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there + } + p.width = w; + p.height = h + 40; + self.monthsCombo.style.display = "none"; + fixPosition(p); + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); }; /** Customizes the date format. */ Calendar.prototype.setDateFormat = function (str) { - this.dateFormat = str; + this.dateFormat = str; }; /** Customizes the tooltip date format. */ Calendar.prototype.setTtDateFormat = function (str) { - this.ttDateFormat = str; + this.ttDateFormat = str; }; /** @@ -1521,119 +1521,119 @@ Calendar.prototype.setTtDateFormat = function (str) { * calls this.setDate which moves the calendar to the given date. */ Calendar.prototype.parseDate = function(str, fmt) { - if (!fmt) - fmt = this.dateFormat; - this.setDate(Date.parseDate(str, fmt)); + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); }; Calendar.prototype.hideShowCovered = function () { - if (!Calendar.is_ie && !Calendar.is_opera) - return; - function getVisib(obj){ - var value = obj.style.visibility; - if (!value) { - if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C - if (!Calendar.is_khtml) - value = document.defaultView. - getComputedStyle(obj, "").getPropertyValue("visibility"); - else - value = ''; - } else if (obj.currentStyle) { // IE - value = obj.currentStyle.visibility; - } else - value = ''; - } - return value; - }; - - var tags = new Array("applet", "iframe", "select"); - var el = this.element; - - var p = Calendar.getAbsolutePos(el); - var EX1 = p.x; - var EX2 = el.offsetWidth + EX1; - var EY1 = p.y; - var EY2 = el.offsetHeight + EY1; - - for (var k = tags.length; k > 0; ) { - var ar = document.getElementsByTagName(tags[--k]); - var cc = null; - - for (var i = ar.length; i > 0;) { - cc = ar[--i]; - - p = Calendar.getAbsolutePos(cc); - var CX1 = p.x; - var CX2 = cc.offsetWidth + CX1; - var CY1 = p.y; - var CY2 = cc.offsetHeight + CY1; - - if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = cc.__msh_save_visibility; - } else { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getVisib(cc); - } - cc.style.visibility = "hidden"; - } - } - } + if (!Calendar.is_ie && !Calendar.is_opera) + return; + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; + } + return value; + }; + + var tags = new Array("applet", "iframe", "select"); + var el = this.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; + } + } + } }; /** Internal function; it displays the bar with the names of the weekday. */ Calendar.prototype._displayWeekdays = function () { - var fdow = this.firstDayOfWeek; - var cell = this.firstdayname; - var weekend = Calendar._TT["WEEKEND"]; - for (var i = 0; i < 7; ++i) { - cell.className = "day name"; - var realday = (i + fdow) % 7; - if (i) { - cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); - cell.navtype = 100; - cell.calendar = this; - cell.fdow = realday; - Calendar._add_evs(cell); - } - if (weekend.indexOf(realday.toString()) != -1) { - Calendar.addClass(cell, "weekend"); - } - cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; - cell = cell.nextSibling; - } + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } }; /** Internal function. Hides all combo boxes that might be displayed. */ Calendar.prototype._hideCombos = function () { - this.monthsCombo.style.display = "none"; - this.yearsCombo.style.display = "none"; + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; }; /** Internal function. Starts dragging the element. */ Calendar.prototype._dragStart = function (ev) { - if (this.dragging) { - return; - } - this.dragging = true; - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posY = ev.clientY + window.scrollY; - posX = ev.clientX + window.scrollX; - } - var st = this.element.style; - this.xOffs = posX - parseInt(st.left); - this.yOffs = posY - parseInt(st.top); - with (Calendar) { - addEvent(document, "mousemove", calDragIt); - addEvent(document, "mouseup", calDragEnd); - } + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } }; // BEGIN: DATE OBJECT PATCHES @@ -1649,242 +1649,242 @@ Date.DAY = 24 * Date.HOUR; Date.WEEK = 7 * Date.DAY; Date.parseDate = function(str, fmt) { - var today = new CalendarDateObject(); - var y = 0; - var m = -1; - var d = 0; - - // translate date into en_US, because split() cannot parse non-latin stuff - var a = str; - var i; - for (i = 0; i < Calendar._MN.length; i++) { - a = a.replace(Calendar._MN[i], enUS.m.wide[i]); - } - for (i = 0; i < Calendar._SMN.length; i++) { - a = a.replace(Calendar._SMN[i], enUS.m.abbr[i]); - } - a = a.replace(Calendar._am, 'am'); - a = a.replace(Calendar._am.toLowerCase(), 'am'); - a = a.replace(Calendar._pm, 'pm'); - a = a.replace(Calendar._pm.toLowerCase(), 'pm'); - - a = a.split(/\W+/); - - var b = fmt.match(/%./g); - var i = 0, j = 0; - var hr = 0; - var min = 0; - for (i = 0; i < a.length; ++i) { - if (!a[i]) - continue; - switch (b[i]) { - case "%d": - case "%e": - d = parseInt(a[i], 10); - break; - - case "%m": - m = parseInt(a[i], 10) - 1; - break; - - case "%Y": - case "%y": - y = parseInt(a[i], 10); - (y < 100) && (y += (y > 29) ? 1900 : 2000); - break; - - case "%b": - for (j = 0; j < 12; ++j) { - if (enUS.m.abbr[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } - } - break; - - case "%B": - for (j = 0; j < 12; ++j) { - if (enUS.m.wide[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } - } - break; - - case "%H": - case "%I": - case "%k": - case "%l": - hr = parseInt(a[i], 10); - break; - - case "%P": - case "%p": - if (/pm/i.test(a[i]) && hr < 12) - hr += 12; - else if (/am/i.test(a[i]) && hr >= 12) - hr -= 12; - break; - - case "%M": - min = parseInt(a[i], 10); - break; - } - } - if (isNaN(y)) y = today.getFullYear(); - if (isNaN(m)) m = today.getMonth(); - if (isNaN(d)) d = today.getDate(); - if (isNaN(hr)) hr = today.getHours(); - if (isNaN(min)) min = today.getMinutes(); - if (y != 0 && m != -1 && d != 0) - return new CalendarDateObject(y, m, d, hr, min, 0); - y = 0; m = -1; d = 0; - for (i = 0; i < a.length; ++i) { - if (a[i].search(/[a-zA-Z]+/) != -1) { - var t = -1; - for (j = 0; j < 12; ++j) { - if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } - } - if (t != -1) { - if (m != -1) { - d = m+1; - } - m = t; - } - } else if (parseInt(a[i], 10) <= 12 && m == -1) { - m = a[i]-1; - } else if (parseInt(a[i], 10) > 31 && y == 0) { - y = parseInt(a[i], 10); - (y < 100) && (y += (y > 29) ? 1900 : 2000); - } else if (d == 0) { - d = a[i]; - } - } - if (y == 0) - y = today.getFullYear(); - if (m != -1 && d != 0) - return new CalendarDateObject(y, m, d, hr, min, 0); - return today; + var today = new CalendarDateObject(); + var y = 0; + var m = -1; + var d = 0; + + // translate date into en_US, because split() cannot parse non-latin stuff + var a = str; + var i; + for (i = 0; i < Calendar._MN.length; i++) { + a = a.replace(Calendar._MN[i], enUS.m.wide[i]); + } + for (i = 0; i < Calendar._SMN.length; i++) { + a = a.replace(Calendar._SMN[i], enUS.m.abbr[i]); + } + a = a.replace(Calendar._am, 'am'); + a = a.replace(Calendar._am.toLowerCase(), 'am'); + a = a.replace(Calendar._pm, 'pm'); + a = a.replace(Calendar._pm.toLowerCase(), 'pm'); + + a = a.split(/\W+/); + + var b = fmt.match(/%./g); + var i = 0, j = 0; + var hr = 0; + var min = 0; + for (i = 0; i < a.length; ++i) { + if (!a[i]) + continue; + switch (b[i]) { + case "%d": + case "%e": + d = parseInt(a[i], 10); + break; + + case "%m": + m = parseInt(a[i], 10) - 1; + break; + + case "%Y": + case "%y": + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + break; + + case "%b": + for (j = 0; j < 12; ++j) { + if (enUS.m.abbr[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%B": + for (j = 0; j < 12; ++j) { + if (enUS.m.wide[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } + } + break; + + case "%H": + case "%I": + case "%k": + case "%l": + hr = parseInt(a[i], 10); + break; + + case "%P": + case "%p": + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; + break; + + case "%M": + min = parseInt(a[i], 10); + break; + } + } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new CalendarDateObject(y, m, d, hr, min, 0); + y = 0; m = -1; d = 0; + for (i = 0; i < a.length; ++i) { + if (a[i].search(/[a-zA-Z]+/) != -1) { + var t = -1; + for (j = 0; j < 12; ++j) { + if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; } + } + if (t != -1) { + if (m != -1) { + d = m+1; + } + m = t; + } + } else if (parseInt(a[i], 10) <= 12 && m == -1) { + m = a[i]-1; + } else if (parseInt(a[i], 10) > 31 && y == 0) { + y = parseInt(a[i], 10); + (y < 100) && (y += (y > 29) ? 1900 : 2000); + } else if (d == 0) { + d = a[i]; + } + } + if (y == 0) + y = today.getFullYear(); + if (m != -1 && d != 0) + return new CalendarDateObject(y, m, d, hr, min, 0); + return today; }; /** Returns the number of days in the current month */ Date.prototype.getMonthDays = function(month) { - var year = this.getFullYear(); - if (typeof month == "undefined") { - month = this.getMonth(); - } - if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { - return 29; - } else { - return Date._MD[month]; - } + var year = this.getFullYear(); + if (typeof month == "undefined") { + month = this.getMonth(); + } + if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) { + return 29; + } else { + return Date._MD[month]; + } }; /** Returns the number of day in the year. */ Date.prototype.getDayOfYear = function() { - var now = new CalendarDateObject(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); - var then = new CalendarDateObject(this.getFullYear(), 0, 0, 0, 0, 0); - var time = now - then; - return Math.floor(time / Date.DAY); + var now = new CalendarDateObject(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new CalendarDateObject(this.getFullYear(), 0, 0, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); }; /** Returns the number of the week in year, as defined in ISO 8601. */ Date.prototype.getWeekNumber = function() { - var d = new CalendarDateObject(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); - var DoW = d.getDay(); - d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu - var ms = d.valueOf(); // GMT - d.setMonth(0); - d.setDate(4); // Thu in Week 1 - return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; + var d = new CalendarDateObject(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var DoW = d.getDay(); + d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu + var ms = d.valueOf(); // GMT + d.setMonth(0); + d.setDate(4); // Thu in Week 1 + return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; }; /** Checks date and time equality */ Date.prototype.equalsTo = function(date) { - return ((this.getFullYear() == date.getFullYear()) && - (this.getMonth() == date.getMonth()) && - (this.getDate() == date.getDate()) && - (this.getHours() == date.getHours()) && - (this.getMinutes() == date.getMinutes())); + return ((this.getFullYear() == date.getFullYear()) && + (this.getMonth() == date.getMonth()) && + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); }; /** Set only the year, month, date parts (keep existing time) */ Date.prototype.setDateOnly = function(date) { - var tmp = new CalendarDateObject(date); - this.setDate(1); - this.setFullYear(tmp.getFullYear()); - this.setMonth(tmp.getMonth()); - this.setDate(tmp.getDate()); + var tmp = new CalendarDateObject(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); }; /** Prints the date in a string according to the given format. */ Date.prototype.print = function (str) { - var m = this.getMonth(); - var d = this.getDate(); - var y = this.getFullYear(); - var wn = this.getWeekNumber(); - var w = this.getDay(); - var s = {}; - var hr = this.getHours(); - var pm = (hr >= 12); - var ir = (pm) ? (hr - 12) : hr; - var dy = this.getDayOfYear(); - if (ir == 0) - ir = 12; - var min = this.getMinutes(); - var sec = this.getSeconds(); - s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] - s["%A"] = Calendar._DN[w]; // full weekday name - s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] - s["%B"] = Calendar._MN[m]; // full month name - // FIXME: %c : preferred date and time representation for the current locale - s["%C"] = 1 + Math.floor(y / 100); // the century number - s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) - s["%e"] = d; // the day of the month (range 1 to 31) - // FIXME: %D : american date style: %m/%d/%y - // FIXME: %E, %F, %G, %g, %h (man strftime) - s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) - s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) - s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) - s["%k"] = hr; // hour, range 0 to 23 (24h format) - s["%l"] = ir; // hour, range 1 to 12 (12h format) - s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 - s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 - s["%n"] = "\n"; // a newline character - s["%p"] = pm ? Calendar._pm.toUpperCase() : Calendar._am.toUpperCase(); - s["%P"] = pm ? Calendar._pm.toLowerCase() : Calendar._am.toLowerCase(); - // FIXME: %r : the time in am/pm notation %I:%M:%S %p - // FIXME: %R : the time in 24-hour notation %H:%M - s["%s"] = Math.floor(this.getTime() / 1000); - s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 - s["%t"] = "\t"; // a tab character - // FIXME: %T : the time in 24-hour notation (%H:%M:%S) - s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; - s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) - s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) - // FIXME: %x : preferred date representation for the current locale without the time - // FIXME: %X : preferred time representation for the current locale without the date - s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) - s["%Y"] = y; // year with the century - s["%%"] = "%"; // a literal '%' character - - var re = /%./g; - if (!Calendar.is_ie5 && !Calendar.is_khtml) - return str.replace(re, function (par) { return s[par] || par; }); - - var a = str.match(re); - for (var i = 0; i < a.length; i++) { - var tmp = s[a[i]]; - if (tmp) { - re = new RegExp(a[i], 'g'); - str = str.replace(re, tmp); - } - } - - return str; + var m = this.getMonth(); + var d = this.getDate(); + var y = this.getFullYear(); + var wn = this.getWeekNumber(); + var w = this.getDay(); + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? Calendar._pm.toUpperCase() : Calendar._am.toUpperCase(); + s["%P"] = pm ? Calendar._pm.toLowerCase() : Calendar._am.toLowerCase(); + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + + var re = /%./g; + if (!Calendar.is_ie5 && !Calendar.is_khtml) + return str.replace(re, function (par) { return s[par] || par; }); + + var a = str.match(re); + for (var i = 0; i < a.length; i++) { + var tmp = s[a[i]]; + if (tmp) { + re = new RegExp(a[i], 'g'); + str = str.replace(re, tmp); + } + } + + return str; }; Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear; Date.prototype.setFullYear = function(y) { - var d = new CalendarDateObject(this); - d.__msh_oldSetFullYear(y); - if (d.getMonth() != this.getMonth()) - this.setDate(28); - this.__msh_oldSetFullYear(y); + var d = new CalendarDateObject(this); + d.__msh_oldSetFullYear(y); + if (d.getMonth() != this.getMonth()) + this.setDate(28); + this.__msh_oldSetFullYear(y); }; CalendarDateObject.prototype = new Date(); diff --git a/js/extjs/ext-tree.js b/js/extjs/ext-tree.js index ce27fbbf..35b41260 100644 --- a/js/extjs/ext-tree.js +++ b/js/extjs/ext-tree.js @@ -6,7 +6,6 @@ * http://www.extjs.com/license */ - Ext={};window["undefined"]=window["undefined"];Ext.apply=function(o,c,_3){if(_3){Ext.apply(o,_3);}if(o&&c&&typeof c=="object"){for(var p in c){o[p]=c[p];}}return o;};(function(){var _5=0;var ua=navigator.userAgent.toLowerCase();var _7=document.compatMode=="CSS1Compat",_8=ua.indexOf("opera")>-1,_9=(/webkit|khtml/).test(ua),_a=ua.indexOf("msie")>-1,_b=ua.indexOf("msie 7")>-1,_c=!_9&&ua.indexOf("gecko")>-1,_d=_a&&!_7,_e=(ua.indexOf("windows")!=-1||ua.indexOf("win32")!=-1),_f=(ua.indexOf("macintosh")!=-1||ua.indexOf("mac os x")!=-1),_10=window.location.href.toLowerCase().indexOf("https")===0;if(_a&&!_b){try{document.execCommand("BackgroundImageCache",false,true);}catch(e){}}Ext.apply(Ext,{isStrict:_7,isSecure:_10,isReady:false,SSL_SECURE_URL:"javascript:false",BLANK_IMAGE_URL:"http:/"+"/extjs.com/s.gif",emptyFn:function(){},applyIf:function(o,c){if(o&&c){for(var p in c){if(typeof o[p]=="undefined"){o[p]=c[p];}}}return o;},addBehaviors:function(o){if(!Ext.isReady){Ext.onReady(function(){Ext.addBehaviors(o);});return;}var _15={};for(var b in o){var _17=b.split("@");if(_17[1]){var s=_17[0];if(!_15[s]){_15[s]=Ext.select(s);}_15[s].on(_17[1],o[b]);}}_15=null;},id:function(el,_1a){_1a=_1a||"ext-gen";el=Ext.getDom(el);var id=_1a+(++_5);return el?(el.id?el.id:(el.id=id)):id;},extend:function(){var io=function(o){for(var m in o){this[m]=o[m];}};return function(sb,sp,_21){if(typeof sp=="object"){_21=sp;sp=sb;sb=function(){sp.apply(this,arguments);};}var F=function(){},sbp,spp=sp.prototype;F.prototype=spp;sbp=sb.prototype=new F();sbp.constructor=sb;sb.superclass=spp;if(spp.constructor==Object.prototype.constructor){spp.constructor=sp;}sb.override=function(o){Ext.override(sb,o);};sbp.override=io;sbp.__extcls=sb;Ext.override(sb,_21);return sb;};}(),override:function(_26,_27){if(_27){var p=_26.prototype;for(var _29 in _27){p[_29]=_27[_29];}}},namespace:function(){var a=arguments,o=null,i,j,d,rt;for(i=0;i
    • ' + message + '
    '; + }, + + getPostData: function() { + var data = $('rollback-form').serialize(true); + data['time'] = this.time; + data['type'] = this.type; + return data; + }, + + showPopup: function(divId) { + $(divId).show().setStyle({ + 'marginTop': -$(divId).getDimensions().height / 2 + 'px' + }); + $('popup-window-mask').setStyle({ + height: $('html-body').getHeight() + 'px' + }).show(); + }, + + hidePopups: function() { + $$('.backup-dialog').each(Element.hide); + $('popup-window-mask').hide(); + } +} diff --git a/js/mage/adminhtml/browser.js b/js/mage/adminhtml/browser.js index 8eb67095..b9518c19 100644 --- a/js/mage/adminhtml/browser.js +++ b/js/mage/adminhtml/browser.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ MediabrowserUtility = { @@ -228,7 +228,8 @@ Mediabrowser.prototype = { getTargetElement: function() { if (typeof(tinyMCE) != 'undefined' && tinyMCE.get(this.targetElementId)) { if ((opener = this.getMediaBrowserOpener())) { - return opener.document.getElementById('src') || opener.document.getElementById('href'); + var targetElementId = tinyMceEditors.get(this.targetElementId).getMediaBrowserTargetElementId(); + return opener.document.getElementById(targetElementId); } else { return null; } @@ -313,7 +314,7 @@ Mediabrowser.prototype = { i++; }); new Ajax.Request(this.deleteFilesUrl, { - parameters: {files: ids.toJSON()}, + parameters: {files: Object.toJSON(ids)}, onSuccess: function(transport) { try { this.onAjaxSuccess(transport); diff --git a/js/mage/adminhtml/events.js b/js/mage/adminhtml/events.js index 5eb028ab..0908b0db 100644 --- a/js/mage/adminhtml/events.js +++ b/js/mage/adminhtml/events.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ // from http://www.someelement.com/2007/03/eventpublisher-custom-events-la-pubsub.html diff --git a/js/mage/adminhtml/flexuploader.js b/js/mage/adminhtml/flexuploader.js index 01f0b776..ab1647d7 100644 --- a/js/mage/adminhtml/flexuploader.js +++ b/js/mage/adminhtml/flexuploader.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -65,8 +65,8 @@ if(!window.Flex) { top: 300, width: flexWidth, height: 20, - src: uploaderSrc - // wmode: 'transparent' + src: uploaderSrc, + wmode: 'transparent' }); // this.getInnerElement('browse').disabled = true; // this.getInnerElement('upload').disabled = true; @@ -283,16 +283,16 @@ if(!window.Flex) { }; }, formatSize: function(size) { - if (size > 1024*1024*1024*1024) { - return this.round(size/(1024*1024*1024*1024)) + ' ' + this.translate('Tb'); - } else if (size > 1024*1024*1024) { - return this.round(size/(1024*1024*1024)) + ' ' + this.translate('Gb'); - } else if (size > 1024*1024) { - return this.round(size/(1024*1024)) + ' ' + this.translate('Mb'); + if (size > 1024 * 1024 * 1024 * 1024) { + return this.round(size / (1024 * 1024 * 1024 * 1024)) + ' ' + this.translate('TB'); + } else if (size > 1024 * 1024 * 1024) { + return this.round(size / (1024 * 1024 * 1024)) + ' ' + this.translate('GB'); + } else if (size > 1024 * 1024) { + return this.round(size / (1024 * 1024)) + ' ' + this.translate('MB'); } else if (size > 1024) { - return this.round(size/(1024)) + ' ' + this.translate('Kb'); + return this.round(size / (1024)) + ' ' + this.translate('kB'); } - return size + ' ' + this.translate('b'); + return size + ' ' + this.translate('B'); }, round: function(number) { return Math.round(number*100)/100; diff --git a/js/mage/adminhtml/form.js b/js/mage/adminhtml/form.js index 4ee8146f..596b83f8 100644 --- a/js/mage/adminhtml/form.js +++ b/js/mage/adminhtml/form.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var varienForm = new Class.create(); @@ -82,6 +82,9 @@ varienForm.prototype = { }, _processValidationResult : function(transport){ + if (typeof varienGlobalEvents != undefined) { + varienGlobalEvents.fireEvent('formValidateAjaxComplete', transport); + } var response = transport.responseText.evalJSON(); if(response.error){ if($('messages')){ @@ -98,10 +101,11 @@ varienForm.prototype = { }, _submit : function(){ + var $form = $(this.formId); if(this.submitUrl){ - $(this.formId).action = this.submitUrl; + $form.action = this.submitUrl; } - $(this.formId).submit(); + $form.submit(); } } @@ -188,6 +192,8 @@ RegionUpdater.prototype = { // // clone for select element (#6924) // this._regionSelectEl = {}; // this.tpl = new Template(''); + this.config = regions['config']; + delete regions.config; this.regions = regions; this.disableAction = (typeof disableAction=='undefined') ? 'hide' : disableAction; this.clearRegionValueOnDisable = (typeof clearRegionValueOnDisable == 'undefined') ? false : clearRegionValueOnDisable; @@ -204,6 +210,64 @@ RegionUpdater.prototype = { Event.observe(this.countryEl, 'change', this.update.bind(this)); }, + _checkRegionRequired: function() + { + var label, wildCard; + var elements = [this.regionTextEl, this.regionSelectEl]; + var that = this; + if (typeof this.config == 'undefined') { + return; + } + var regionRequired = this.config.regions_required.indexOf(this.countryEl.value) >= 0; + + elements.each(function(currentElement) { + if(!currentElement) { + return; + } + Validation.reset(currentElement); + label = $$('label[for="' + currentElement.id + '"]')[0]; + if (label) { + wildCard = label.down('em') || label.down('span.required'); + var topElement = label.up('tr') || label.up('li'); + if (!that.config.show_all_regions && topElement) { + if (regionRequired) { + topElement.show(); + } else { + topElement.hide(); + } + } + } + + if (label && wildCard) { + if (!regionRequired) { + wildCard.hide(); + } else { + wildCard.show(); + } + } + + if (!regionRequired || !currentElement.visible()) { + if (currentElement.hasClassName('required-entry')) { + currentElement.removeClassName('required-entry'); + } + if ('select' == currentElement.tagName.toLowerCase() && + currentElement.hasClassName('validate-select') + ) { + currentElement.removeClassName('validate-select'); + } + } else { + if (!currentElement.hasClassName('required-entry')) { + currentElement.addClassName('required-entry'); + } + if ('select' == currentElement.tagName.toLowerCase() && + !currentElement.hasClassName('validate-select') + ) { + currentElement.addClassName('validate-select'); + } + } + }); + }, + update: function() { if (this.regions[this.countryEl.value]) { @@ -214,13 +278,13 @@ RegionUpdater.prototype = { if (this.lastCountryId!=this.countryEl.value) { var i, option, region, def; + def = this.regionSelectEl.getAttribute('defaultValue'); if (this.regionTextEl) { - def = this.regionTextEl.value.toLowerCase(); + if (!def) { + def = this.regionTextEl.value.toLowerCase(); + } this.regionTextEl.value = ''; } - if (!def) { - def = this.regionSelectEl.getAttribute('defaultValue'); - } this.regionSelectEl.options.length = 1; for (regionId in this.regions[this.countryEl.value]) { @@ -228,7 +292,8 @@ RegionUpdater.prototype = { option = document.createElement('OPTION'); option.value = regionId; - option.text = region.name; + option.text = region.name.stripTags(); + option.title = region.name; if (this.regionSelectEl.options.add) { this.regionSelectEl.options.add(option); @@ -291,6 +356,7 @@ RegionUpdater.prototype = { // this.regionSelectEl = null; } varienGlobalEvents.fireEvent("address_country_changed", this.countryEl); + this._checkRegionRequired(); }, setMarkDisplay: function(elem, display){ @@ -384,7 +450,6 @@ SelectUpdater.prototype = { /** * Observer that watches for dependent form elements * If an element depends on 1 or more of other elements, it should show up only when all of them gain specified values - * TODO: implement multiple values per "master" elements */ FormElementDependenceController = Class.create(); FormElementDependenceController.prototype = { @@ -393,6 +458,7 @@ FormElementDependenceController.prototype = { * 'id_of_dependent_element' : { * 'id_of_master_element_1' : 'reference_value', * 'id_of_master_element_2' : 'reference_value' + * 'id_of_master_element_3' : ['reference_value1', 'reference_value2'] * ... * } * } @@ -406,8 +472,12 @@ FormElementDependenceController.prototype = { } for (var idTo in elementsMap) { for (var idFrom in elementsMap[idTo]) { - Event.observe($(idFrom), 'change', this.trackChange.bindAsEventListener(this, idTo, elementsMap[idTo])); - this.trackChange(null, idTo, elementsMap[idTo]); + if ($(idFrom)) { + Event.observe($(idFrom), 'change', this.trackChange.bindAsEventListener(this, idTo, elementsMap[idTo])); + this.trackChange(null, idTo, elementsMap[idTo]); + } else { + this.trackChange(null, idTo, elementsMap[idTo]); + } } } }, @@ -433,22 +503,33 @@ FormElementDependenceController.prototype = { // define whether the target should show up var shouldShowUp = true; for (var idFrom in valuesFrom) { - if ($(idFrom).value != valuesFrom[idFrom]) { - shouldShowUp = false; + var from = $(idFrom); + if (valuesFrom[idFrom] instanceof Array) { + if (!from || valuesFrom[idFrom].indexOf(from.value) == -1) { + shouldShowUp = false; + } + } else { + if (!from || from.value != valuesFrom[idFrom]) { + shouldShowUp = false; + } } } // toggle target row if (shouldShowUp) { - $(idTo).up(this._config.levels_up).select('input', 'select').each(function (item) { - if (!item.type || item.type != 'hidden') { // don't touch hidden inputs, bc they may have custom logic + var currentConfig = this._config; + $(idTo).up(this._config.levels_up).select('input', 'select', 'td').each(function (item) { + // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic + if ((!item.type || item.type != 'hidden') && !($(item.id+'_inherit') && $(item.id+'_inherit').checked) + && !(currentConfig.can_edit_price != undefined && !currentConfig.can_edit_price)) { item.disabled = false; } }); $(idTo).up(this._config.levels_up).show(); } else { - $(idTo).up(this._config.levels_up).select('input', 'select').each(function (item){ - if (!item.type || item.type != 'hidden') { // don't touch hidden inputs, bc they may have custom logic + $(idTo).up(this._config.levels_up).select('input', 'select', 'td').each(function (item){ + // don't touch hidden inputs (and Use Default inputs too), bc they may have custom logic + if ((!item.type || item.type != 'hidden') && !($(item.id+'_inherit') && $(item.id+'_inherit').checked)) { item.disabled = true; } }); diff --git a/js/mage/adminhtml/grid.js b/js/mage/adminhtml/grid.js index 4bd6d3f6..36cbd793 100644 --- a/js/mage/adminhtml/grid.js +++ b/js/mage/adminhtml/grid.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var varienGrid = new Class.create(); @@ -175,6 +175,8 @@ varienGrid.prototype = { onComplete: this.initGridAjax.bind(this), onSuccess: function(transport) { try { + var responseText = transport.responseText.replace(/>\s+<'); + if (transport.responseText.isJSON()) { var response = transport.responseText.evalJSON() if (response.error) { @@ -184,11 +186,27 @@ varienGrid.prototype = { setLocation(response.ajaxRedirect); } } else { - $(this.containerId).update(transport.responseText); + /** + * For IE <= 7. + * If there are two elements, and first has name, that equals id of second. + * In this case, IE will choose one that is above + * + * @see https://prototype.lighthouseapp.com/projects/8886/tickets/994-id-selector-finds-elements-by-name-attribute-in-ie7 + */ + var divId = $(this.containerId); + if (divId.id == this.containerId) { + divId.update(responseText); + } else { + $$('div[id="'+this.containerId+'"]')[0].update(responseText); + } + } + } catch (e) { + var divId = $(this.containerId); + if (divId.id == this.containerId) { + divId.update(responseText); + } else { + $$('div[id="'+this.containerId+'"]')[0].update(responseText); } - } - catch (e) { - $(this.containerId).update(transport.responseText); } }.bind(this) }); @@ -222,20 +240,27 @@ varienGrid.prototype = { _processFailure : function(transport){ location.href = BASE_URL; }, - addVarToUrl : function(varName, varValue){ + _addVarToUrl : function(url, varName, varValue){ var re = new RegExp('\/('+varName+'\/.*?\/)'); - var parts = this.url.split(new RegExp('\\?')); - this.url = parts[0].replace(re, '/'); - this.url+= varName+'/'+varValue+'/'; + var parts = url.split(new RegExp('\\?')); + url = parts[0].replace(re, '/'); + url+= varName+'/'+varValue+'/'; if(parts.size()>1) { - this.url+= '?' + parts[1]; + url+= '?' + parts[1]; } - //this.url = this.url.replace(/([^:])\/{2,}/g, '$1/'); + return url; + }, + addVarToUrl : function(varName, varValue){ + this.url = this._addVarToUrl(this.url, varName, varValue); return this.url; }, doExport : function(){ if($(this.containerId+'_export')){ - location.href = $(this.containerId+'_export').value; + var exportUrl = $(this.containerId+'_export').value; + if(this.massaction && this.massaction.checkedString) { + exportUrl = this._addVarToUrl(exportUrl, this.massaction.formFieldNameInternal, this.massaction.checkedString); + } + location.href = exportUrl; } }, bindFilterFields : function(){ @@ -325,6 +350,7 @@ varienGridMassaction.prototype = { gridIds: [], useSelectAll: false, currentItem: false, + lastChecked: { left: false, top: false, checkbox: false }, fieldTemplate: new Template(''), initialize: function (containerId, grid, checkedValues, formFieldNameInternal, formFieldName) { this.setOldCallback('row_click', grid.rowClickCallback); @@ -334,6 +360,7 @@ varienGridMassaction.prototype = { this.useAjax = false; this.grid = grid; + this.grid.massaction = this; this.containerId = containerId; this.initMassactionElements(); @@ -356,13 +383,36 @@ varienGridMassaction.prototype = { }, initMassactionElements: function() { this.container = $(this.containerId); - this.form = $(this.containerId + '-form'); this.count = $(this.containerId + '-count'); - this.validator = new Validation(this.form); this.formHiddens = $(this.containerId + '-form-hiddens'); this.formAdditional = $(this.containerId + '-form-additional'); this.select = $(this.containerId + '-select'); + this.form = this.prepareForm(); + this.validator = new Validation(this.form); this.select.observe('change', this.onSelectChange.bindAsEventListener(this)); + this.lastChecked = { left: false, top: false, checkbox: false }; + this.initMassSelect(); + }, + prepareForm: function() { + var form = $(this.containerId + '-form'), formPlace = null, + formElement = this.formHiddens || this.formAdditional; + + if (!formElement) { + formElement = this.container.getElementsByTagName('button')[0]; + formElement && formElement.parentNode; + } + if (!form && formElement) { + /* fix problem with rendering form in FF through innerHTML property */ + form = document.createElement('form'); + form.setAttribute('method', 'post'); + form.setAttribute('action', ''); + form.id = this.containerId + '-form'; + formPlace = formElement.parentNode.parentNode; + formPlace.parentNode.appendChild(form); + form.appendChild(formPlace); + } + + return form; }, setGridIds: function(gridIds) { this.gridIds = gridIds; @@ -474,18 +524,21 @@ varienGridMassaction.prototype = { this.setCheckedValues((this.useSelectAll ? this.getGridIds() : this.getCheckboxesValuesAsString())); this.checkCheckboxes(); this.updateCount(); + this.clearLastChecked(); return false; }, unselectAll: function() { this.setCheckedValues(''); this.checkCheckboxes(); this.updateCount(); + this.clearLastChecked(); return false; }, selectVisible: function() { this.setCheckedValues(this.getCheckboxesValuesAsString()); this.checkCheckboxes(); this.updateCount(); + this.clearLastChecked(); return false; }, unselectVisible: function() { @@ -494,6 +547,7 @@ varienGridMassaction.prototype = { }.bind(this)); this.checkCheckboxes(); this.updateCount(); + this.clearLastChecked(); return false; }, setCheckedValues: function(values) { @@ -593,6 +647,64 @@ varienGridMassaction.prototype = { }, getListener: function(strValue) { return eval(strValue); + }, + initMassSelect: function() { + $$('input[class~="massaction-checkbox"]').each( + function(element) { + element.observe('click', this.massSelect.bind(this)); + }.bind(this) + ); + }, + clearLastChecked: function() { + this.lastChecked = { + left: false, + top: false, + checkbox: false + }; + }, + massSelect: function(evt) { + if(this.lastChecked.left !== false && this.lastChecked.top !== false) { + // Left mouse button and "Shift" key was pressed together + if(evt.button === 0 && evt.shiftKey === true) { + var clickedOffset = Event.element(evt).viewportOffset(); + + this.grid.rows.each( + function(row) { + var element = row.select('.massaction-checkbox')[0]; + var offset = element.viewportOffset(); + + if( + ( + // The checkbox is past the most recently clicked checkbox + (offset.top < clickedOffset.top) && + // The checkbox is not past the "boundary" checkbox + (offset.top > this.lastChecked.top || element == this.lastChecked.checkbox) + ) + || + ( + // The checkbox is before the most recently clicked checkbox + (offset.top > clickedOffset.top) && + // The checkbox is after the "boundary" checkbox + (offset.top < this.lastChecked.top || element == this.lastChecked.checkbox) + ) + ) { + // Set the checkbox to the state of the most recently clicked checkbox + element.checked = Event.element(evt).checked; + + this.setCheckbox(element); + } + }.bind(this) + ); + + this.updateCount(); + } + } + + this.lastChecked = { + left: Event.element(evt).viewportOffset().left, + top: Event.element(evt).viewportOffset().top, + checkbox: Event.element(evt) // "boundary" checkbox + }; } }; @@ -748,7 +860,7 @@ serializerController.prototype = { var isInput = Event.element(event).tagName == 'INPUT'; if(trElement){ var checkbox = Element.select(trElement, 'input'); - if(checkbox[0]){ + if(checkbox[0] && !checkbox[0].disabled){ var checked = isInput ? checkbox[0].checked : !checkbox[0].checked; this.grid.setCheckboxChecked(checkbox[0], checked); } @@ -765,8 +877,8 @@ serializerController.prototype = { rowInit : function(grid, row) { if(this.multidimensionalMode){ var checkbox = $(row).select('.checkbox')[0]; - var selectors = this.inputsToManage.map(function (name) { return 'input[name="' + name + '"]' }); - var inputs = $(row).select.apply($(row), selectors); + var selectors = this.inputsToManage.map(function (name) { return ['input[name="' + name + '"]', 'select[name="' + name + '"]']; }); + var inputs = $(row).select.apply($(row), selectors.flatten()); if(checkbox && inputs.length > 0) { checkbox.inputElements = inputs; for(var i = 0; i < inputs.length; i++) { diff --git a/js/mage/adminhtml/hash.js b/js/mage/adminhtml/hash.js index 55f42eda..99b8a97b 100644 --- a/js/mage/adminhtml/hash.js +++ b/js/mage/adminhtml/hash.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ /* diff --git a/js/mage/adminhtml/image.js b/js/mage/adminhtml/image.js index 99b0b351..ed9bda38 100644 --- a/js/mage/adminhtml/image.js +++ b/js/mage/adminhtml/image.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/adminhtml/loader.js b/js/mage/adminhtml/loader.js index 08bd3117..9291bf09 100644 --- a/js/mage/adminhtml/loader.js +++ b/js/mage/adminhtml/loader.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -41,15 +41,23 @@ Ajax.Request.addMethods({ if (!url.match(new RegExp('[?&]isAjax=true',''))) { url = url.match(new RegExp('\\?',"g")) ? url + '&isAjax=true' : url + '?isAjax=true'; } - if (!this.options.parameters) { - this.options.parameters = { + if (Object.isString(this.options.parameters) + && this.options.parameters.indexOf('form_key=') == -1 + ) { + this.options.parameters += '&' + Object.toQueryString({ form_key: FORM_KEY - }; - } - if (!this.options.parameters.form_key) { - this.options.parameters.form_key = FORM_KEY; + }); + } else { + if (!this.options.parameters) { + this.options.parameters = { + form_key: FORM_KEY + }; + } + if (!this.options.parameters.form_key) { + this.options.parameters.form_key = FORM_KEY; + } } - + this.request(url); }, respondToReadyState: function(readyState) { @@ -217,9 +225,12 @@ varienLoaderHandler.handler = { function setLoaderPosition(){ var elem = $('loading_mask_loader'); if (elem && Prototype.Browser.IE) { - var middle = parseInt(document.body.clientHeight/2)+document.body.scrollTop; + var elementDims = elem.getDimensions(); + var viewPort = document.viewport.getDimensions(); + var offsets = document.viewport.getScrollOffsets(); + elem.style.left = Math.floor(viewPort.width / 2 + offsets.left - elementDims.width / 2) + 'px'; + elem.style.top = Math.floor(viewPort.height / 2 + offsets.top - elementDims.height / 2) + 'px'; elem.style.position = 'absolute'; - elem.style.top = middle; } } diff --git a/js/mage/adminhtml/magento-all.js b/js/mage/adminhtml/magento-all.js index 35ee9d23..a377e9d8 100644 --- a/js/mage/adminhtml/magento-all.js +++ b/js/mage/adminhtml/magento-all.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/adminhtml/scrollbar.js b/js/mage/adminhtml/scrollbar.js index b7e06d50..2a8096d3 100644 --- a/js/mage/adminhtml/scrollbar.js +++ b/js/mage/adminhtml/scrollbar.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ /************************************************** diff --git a/js/mage/adminhtml/tabs.js b/js/mage/adminhtml/tabs.js index a40305c4..a9347f03 100644 --- a/js/mage/adminhtml/tabs.js +++ b/js/mage/adminhtml/tabs.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var varienTabs = new Class.create(); diff --git a/js/mage/adminhtml/tools.js b/js/mage/adminhtml/tools.js index 0d437bdd..b5053166 100644 --- a/js/mage/adminhtml/tools.js +++ b/js/mage/adminhtml/tools.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ function setLocation(url){ @@ -99,6 +99,24 @@ function imagePreview(element){ } } +function checkByProductPriceType(elem) { + if (elem.id == 'price_type') { + this.productPriceType = elem.value; + return false; + } else { + if (elem.id == 'price' && this.productPriceType == 0) { + return false; + } + return true; + } +} + +Event.observe(window, 'load', function() { + if ($('price_default') && $('price_default').checked) { + $('price').disabled = 'disabled'; + } +}); + function toggleValueElements(checkbox, container, excludedElements, checked){ if(container && checkbox){ var ignoredElements = [checkbox]; @@ -114,24 +132,26 @@ function toggleValueElements(checkbox, container, excludedElements, checked){ var elems = Element.select(container, ['select', 'input', 'textarea', 'button', 'img']); var isDisabled = (checked != undefined ? checked : checkbox.checked); elems.each(function (elem) { - var isIgnored = false; - for (var i = 0; i < ignoredElements.length; i++) { - if (elem == ignoredElements[i]) { - isIgnored = true; - break; + if (checkByProductPriceType(elem)) { + var isIgnored = false; + for (var i = 0; i < ignoredElements.length; i++) { + if (elem == ignoredElements[i]) { + isIgnored = true; + break; + } + } + if (isIgnored) { + return; + } + elem.disabled=isDisabled; + if (isDisabled) { + elem.addClassName('disabled'); + } else { + elem.removeClassName('disabled'); + } + if(elem.tagName == 'IMG') { + isDisabled ? elem.hide() : elem.show(); } - } - if (isIgnored) { - return; - } - elem.disabled=isDisabled; - if (isDisabled) { - elem.addClassName('disabled'); - } else { - elem.removeClassName('disabled'); - } - if(elem.tagName == 'IMG') { - isDisabled ? elem.hide() : elem.show(); } }) } @@ -222,120 +242,268 @@ function firebugEnabled() { return false; } +function disableElement(elem) { + elem.disabled = true; + elem.addClassName('disabled'); +} + +function enableElement(elem) { + elem.disabled = false; + elem.removeClassName('disabled'); +} + function disableElements(search){ - $$('.' + search).each(function (elem) {elem.disabled=true;elem.addClassName('disabled');}); + $$('.' + search).each(disableElement); } function enableElements(search){ - $$('.' + search).each(function (elem) {elem.disabled=false;elem.removeClassName('disabled');}); + $$('.' + search).each(enableElement); } -/********** Ajax session expiration ***********/ - +/********** Toolbar toggle object to manage normal/floating toolbar toggle during vertical scroll ***********/ +var toolbarToggle = { + // Properties + header: null, // Normal toolbar + headerOffset: null, // Normal toolbar offset - calculated once + headerCopy: null, // Floating toolbar + eventsAdded: false, // We're listening to scroll/resize + compatible: !navigator.appVersion.match('MSIE 6.'), // Whether object is compatible with browser (do not support old browsers, legacy code) + + // Inits object and pushes it into work. Can be used to init/reset(update) object by current DOM. + reset: function () { + // Maybe we are already using floating toolbar - just remove it to update from html + if (this.headerCopy) { + this.headerCopy.remove(); + } + this.createToolbar(); + this.updateForScroll(); + }, -if (!navigator.appVersion.match('MSIE 6.')) { - var header, header_offset, header_copy; - Event.observe(window, 'load', function() { - createTopButtonToolbarToggle(); - }); + // Creates toolbar and inits all needed properties + createToolbar: function () { + if (!this.compatible) { + return; + } - function createTopButtonToolbarToggle() - { + // Extract header that we will use as toolbar var headers = $$('.content-header'); - for(var i=0; i= 0; i--) { + if (!headers[i].hasClassName('skip-header')) { + this.header = headers[i]; + break; } } - - if (!header) { + if (!this.header) { return; } - header_offset = Element.cumulativeOffset(header)[1]; + + // Calculate header offset once - for optimization + this.headerOffset = Element.cumulativeOffset(this.header)[1]; + + // Toolbar buttons var buttons = $$('.content-buttons')[0]; if (buttons) { + // Wrap buttons with 'placeholder' div - to serve as container for buttons Element.insert(buttons, {before: '
    '}); buttons.placeholder = buttons.previous('.content-buttons-placeholder'); buttons.remove(); buttons.placeholder.appendChild(buttons); - header_offset = Element.cumulativeOffset(buttons)[1]; - + this.headerOffset = Element.cumulativeOffset(buttons)[1]; } - header_copy = document.createElement('div'); - header_copy.appendChild(header.cloneNode(true)); - document.body.insertBefore(header_copy, document.body.lastChild) - $(header_copy).addClassName('content-header-floating'); - if ($(header_copy).down('.content-buttons-placeholder')) { - $(header_copy).down('.content-buttons-placeholder').remove(); - } - } + // Create copy of header, that will serve as floating toolbar docked to top of window + this.headerCopy = $(document.createElement('div')); + this.headerCopy.appendChild(this.header.cloneNode(true)); + document.body.insertBefore(this.headerCopy, document.body.lastChild) + this.headerCopy.addClassName('content-header-floating'); - function updateTopButtonToolbarToggle() - { - if (header_copy) { - header_copy.remove(); + // Remove duplicated buttons and their container + var placeholder = this.headerCopy.down('.content-buttons-placeholder'); + if (placeholder) { + placeholder.remove(); } - createTopButtonToolbarToggle(); - floatingTopButtonToolbarToggle(); - } + }, - function floatingTopButtonToolbarToggle() { + // Checks whether object properties are ready and valid + ready: function () { + // Return definitely boolean value + return (this.compatible && this.header && this.headerCopy && this.headerCopy.parentNode) ? true : false; + }, - if (!header || !header_copy || !header_copy.parentNode) { + // Updates toolbars for current scroll - shows/hides normal and floating toolbar + updateForScroll: function () { + if (!this.ready()) { return; } - var s; + // scrolling offset calculation via www.quirksmode.org + var s; if (self.pageYOffset){ s = self.pageYOffset; - }else if (document.documentElement && document.documentElement.scrollTop) { + } else if (document.documentElement && document.documentElement.scrollTop) { s = document.documentElement.scrollTop; - }else if (document.body) { + } else if (document.body) { s = document.body.scrollTop; } + // Show floating or normal toolbar + if (s > this.headerOffset) { + // Page offset is more than header offset, switch to floating toolbar + this.showFloatingToolbar(); + } else { + // Page offset is less than header offset, switch to normal toolbar + this.showNormalToolbar(); + } + }, + + // Shows normal toolbar (and hides floating one) + showNormalToolbar: function () { + if (!this.ready()) { + return; + } var buttons = $$('.content-buttons')[0]; + if (buttons && buttons.oldParent && buttons.oldParent != buttons.parentNode) { + buttons.remove(); + if(buttons.oldBefore) { + buttons.oldParent.insertBefore(buttons, buttons.oldBefore); + } else { + buttons.oldParent.appendChild(buttons); + } + } - if (s > header_offset) { - if (buttons) { - if (!buttons.oldParent) { - buttons.oldParent = buttons.parentNode; - buttons.oldBefore = buttons.previous(); - } - if (buttons.oldParent==buttons.parentNode) { - var dimensions = buttons.placeholder.getDimensions() // Make static dimens. + this.headerCopy.style.display = 'none'; + }, + + // Shows floating toolbar (and hides normal one) + // Notice that buttons could had changed in html by setting new inner html, + // so our added custom properties (placeholder, oldParent) can be not present in them any more + showFloatingToolbar: function () { + if (!this.ready()) { + return; + } + + var buttons = $$('.content-buttons')[0]; + + if (buttons) { + // Remember original parent in normal toolbar to which these buttons belong + if (!buttons.oldParent) { + buttons.oldParent = buttons.parentNode; + buttons.oldBefore = buttons.previous(); + } + + // Move buttons from normal to floating toolbar + if (buttons.oldParent == buttons.parentNode) { + // Make static dimensions for placeholder, so it's not collapsed when buttons are removed + if (buttons.placeholder) { + var dimensions = buttons.placeholder.getDimensions() buttons.placeholder.style.width = dimensions.width + 'px'; buttons.placeholder.style.height = dimensions.height + 'px'; + } + // Move to floating + var target = this.headerCopy.down('div'); + if (target) { buttons.hide(); buttons.remove(); - $(header_copy).down('div').appendChild(buttons); + + target.appendChild(buttons); buttons.show(); } } + } - //header.style.visibility = 'hidden'; - header_copy.style.display = 'block'; - } else { - if (buttons && buttons.oldParent && buttons.oldParent != buttons.parentNode) { - buttons.remove(); - buttons.oldParent.insertBefore(buttons, buttons.oldBefore); - //buttons.placeholder.style.width = undefined; - //buttons.placeholder.style.height = undefined; - } - header.style.visibility = 'visible'; - header_copy.style.display = 'none'; + this.headerCopy.style.display = 'block'; + }, + + // Starts object on window load + startOnLoad: function () { + if (!this.compatible) { + return; + } + + if (!this.funcOnWindowLoad) { + this.funcOnWindowLoad = this.start.bind(this); + } + Event.observe(window, 'load', this.funcOnWindowLoad); + }, + + // Removes object start on window load + removeOnLoad: function () { + if (!this.funcOnWindowLoad) { + return; + } + Event.stopObserving(window, 'load', this.funcOnWindowLoad); + }, + // Starts object by creating toolbar and enabling scroll/resize events + start: function () { + if (!this.compatible) { + return; } + + this.reset(); + this.startListening(); + }, + + // Stops object by removing toolbar and stopping listening to events + stop: function () { + this.stopListening(); + this.removeOnLoad(); + this.showNormalToolbar(); + }, + + // Addes events on scroll/resize + startListening: function () { + if (this.eventsAdded) { + return; + } + + if (!this.funcUpdateForViewport) { + this.funcUpdateForViewport = this.updateForScroll.bind(this); + } + + Event.observe(window, 'scroll', this.funcUpdateForViewport); + Event.observe(window, 'resize', this.funcUpdateForViewport); + + this.eventsAdded = true; + }, + + // Removes listening to events on resize/update + stopListening: function () { + if (!this.eventsAdded) { + return; + } + Event.stopObserving(window, 'scroll', this.funcUpdateForViewport); + Event.stopObserving(window, 'resize', this.funcUpdateForViewport); + + this.eventsAdded = false; } +} - Event.observe(window, 'scroll', floatingTopButtonToolbarToggle); - Event.observe(window, 'resize', floatingTopButtonToolbarToggle); +// Deprecated since 1.4.2.0-beta1 - use toolbarToggle.reset() instead +function updateTopButtonToolbarToggle() +{ + toolbarToggle.reset(); } +// Deprecated since 1.4.2.0-beta1 - use toolbarToggle.createToolbar() instead +function createTopButtonToolbarToggle() +{ + toolbarToggle.createToolbar(); +} + +// Deprecated since 1.4.2.0-beta1 - use toolbarToggle.updateForScroll() instead +function floatingTopButtonToolbarToggle() +{ + toolbarToggle.updateForScroll(); +} + +// Start toolbar on window load +toolbarToggle.startOnLoad(); + + /** Cookie Reading And Writing **/ var Cookie = { @@ -570,3 +738,18 @@ var Base64 = { return string; } }; + +/** + * Array functions + */ + +/** + * Callback function for sort numeric values + * + * @param val1 + * @param val2 + */ +function sortNumeric(val1, val2) +{ + return val1 - val2; +} diff --git a/js/mage/adminhtml/uploader.js b/js/mage/adminhtml/uploader.js index 437ad20f..b68b178e 100644 --- a/js/mage/adminhtml/uploader.js +++ b/js/mage/adminhtml/uploader.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ /** diff --git a/js/mage/adminhtml/variables.js b/js/mage/adminhtml/variables.js index cec1f9e4..15f8e49e 100644 --- a/js/mage/adminhtml/variables.js +++ b/js/mage/adminhtml/variables.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -106,7 +106,7 @@ var Variables = { }, prepareVariableRow: function(varValue, varLabel) { var value = (varValue).replace(/"/g, '"').replace(/'/g, '\\''); - var content = '' + varLabel + ''; + var content = '' + varLabel + ''; return content; }, insertVariable: function(value) { diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentovariable/editor_plugin.js b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentovariable/editor_plugin.js index f49d135e..4ad98c88 100644 --- a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentovariable/editor_plugin.js +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentovariable/editor_plugin.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index cddbe8de..7154fcfc 100644 --- a/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ tinyMCE.addI18n({en:{ diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js index 088eccab..06b5de7e 100644 --- a/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -27,6 +27,8 @@ var tinyMceWysiwygSetup = Class.create(); tinyMceWysiwygSetup.prototype = { mediaBrowserOpener: null, + mediaBrowserTargetElementId: null, + initialize: function(htmlId, config) { this.id = htmlId; @@ -79,9 +81,10 @@ tinyMceWysiwygSetup.prototype = plugins = 'magentowidget,' + plugins; } + var magentoPluginsOptions = $H({}); + var magentoPlugins = ''; + if (this.config.plugins) { - var magentoPluginsOptions = $H({}); - var magentoPlugins = ''; (this.config.plugins).each(function(plugin){ magentoPlugins = plugin.name + ',' + magentoPlugins; magentoPluginsOptions.set(plugin.name, plugin.options); @@ -168,12 +171,13 @@ tinyMceWysiwygSetup.prototype = openFileBrowser: function(o) { var typeTitle; var storeId = this.config.store_id !== null ? this.config.store_id : 0; - var wUrl = this.config.files_browser_window_url + + var wUrl = this.config.files_browser_window_url + 'target_element_id/' + this.id + '/' + - 'store/' + storeId + '/'; + 'store/' + storeId + '/'; this.mediaBrowserOpener = o.win; this.mediaBrowserOpener.blur(); + this.mediaBrowserTargetElementId = o.field; if (typeof(o.type) != 'undefined' && o.type != "") { typeTitle = 'image' == o.type ? this.translate('Insert Image...') : this.translate('Insert Media...'); @@ -193,6 +197,10 @@ tinyMceWysiwygSetup.prototype = return this.mediaBrowserOpener; }, + getMediaBrowserTargetElementId: function() { + return this.mediaBrowserTargetElementId; + }, + getToggleButton: function() { return $('toggle' + this.id); }, @@ -216,6 +224,12 @@ tinyMceWysiwygSetup.prototype = this.getPluginButtons().each(function(e) { e.show(); }); + if (Prototype.Browser.IE) { + // workaround for ie textarea redraw bug + window.setTimeout(function(){ + $(this.id).value = $(this.id).value; + }.bind(this), 0); + } }, closePopups: function() { @@ -250,15 +264,18 @@ tinyMceWysiwygSetup.prototype = } }, + // retrieve directives URL with substituted directive value + makeDirectiveUrl: function(directive) { + return this.config.directives_url.replace('directive', 'directive/___directive/' + directive); + }, + encodeDirectives: function(content) { // collect all HTML tags with attributes that contain directives - return content.gsub(/<([a-z0-9\-\_]+.+?)([a-z0-9\-\_]+=["']\{\{.+?\}\}.*?["'].+?)>/i, function(match) { + return content.gsub(/<([a-z0-9\-\_]+.+?)([a-z0-9\-\_]+=".*?\{\{.+?\}\}.*?".+?)>/i, function(match) { var attributesString = match[2]; // process tag attributes string - attributesString = attributesString.gsub(/([a-z0-9\-\_]+)=["'](\{\{.+?\}\})(.*?)["']/i, function(m) { - // include server URL only for images src to avoid unnecessary requests - var url = m[1].toLowerCase() == 'src' ? this.config.directives_url : ''; - return m[1] + '="' + url + '___directive/' + Base64.mageEncode(m[2]) + '/' + m[3] + '"'; + attributesString = attributesString.gsub(/([a-z0-9\-\_]+)="(.*?)(\{\{.+?\}\})(.*?)"/i, function(m) { + return m[1] + '="' + m[2] + this.makeDirectiveUrl(Base64.mageEncode(m[3])) + m[4] + '"'; }.bind(this)); return '<' + match[1] + attributesString + '>'; @@ -287,8 +304,11 @@ tinyMceWysiwygSetup.prototype = }, decodeDirectives: function(content) { - return content.gsub(/([a-z0-9\-\_]+)=["]\S*?___directive\/([a-zA-Z0-9\-\_\,]+)\/(.*?)["]/i, function(match) { - return match[1] + '="' + Base64.mageDecode(match[2]) + '"'; + // escape special chars in directives url to use it in regular expression + var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'); + var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')); + return content.gsub(reg, function(match) { + return Base64.mageDecode(match[1]); }.bind(this)); }, @@ -336,4 +356,3 @@ tinyMceWysiwygSetup.prototype = return this.config.widget_placeholders.indexOf(filename) != -1; } } - diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css index e40f9ad8..1728f5b5 100644 --- a/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/content.css @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css index 9bc1977e..8fb1c3a8 100644 --- a/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css +++ b/js/mage/adminhtml/wysiwyg/tiny_mce/themes/advanced/skins/default/dialog.css @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/adminhtml/wysiwyg/widget.js b/js/mage/adminhtml/wysiwyg/widget.js index 5e7f2778..9f2a5a97 100644 --- a/js/mage/adminhtml/wysiwyg/widget.js +++ b/js/mage/adminhtml/wysiwyg/widget.js @@ -19,7 +19,7 @@ * * @category Mage * @package Mage_Adminhtml - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ @@ -51,7 +51,7 @@ var widgetTools = { closable:true, className:'magento', windowClassName:"popup-window", - title:'Insert Widget', + title:Translator.translate('Insert Widget...'), top:50, width:950, //height:450, @@ -88,6 +88,9 @@ WysiwygWidget.Widget.prototype = { this.optionsUrl = optionsSourceUrl; this.optionValues = new Hash({}); this.widgetTargetId = widgetTargetId; + if (typeof(tinyMCE) != "undefined" && tinyMCE.activeEditor) { + this.bMark = tinyMCE.activeEditor.selection.getBookmark(); + } Event.observe(this.widgetEl, "change", this.loadOptions.bind(this)); @@ -231,6 +234,14 @@ WysiwygWidget.Widget.prototype = { try { widgetTools.onAjaxSuccess(transport); Windows.close("widget_window"); + + if (typeof(tinyMCE) != "undefined" && tinyMCE.activeEditor) { + tinyMCE.activeEditor.focus(); + if (this.bMark) { + tinyMCE.activeEditor.selection.moveToBookmark(this.bMark); + } + } + this.updateContent(transport.responseText); } catch(e) { alert(e.message); diff --git a/js/mage/captcha.js b/js/mage/captcha.js new file mode 100644 index 00000000..7e7ca1ea --- /dev/null +++ b/js/mage/captcha.js @@ -0,0 +1,87 @@ +/** + * Magento + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License (AFL 3.0) + * that is bundled with this package in the file LICENSE_AFL.txt. + * It is also available through the world-wide-web at this URL: + * http://opensource.org/licenses/afl-3.0.php + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@magentocommerce.com so we can send you a copy immediately. + * + * DISCLAIMER + * + * Do not edit or add to this file if you wish to upgrade Magento to newer + * versions in the future. If you wish to customize Magento for your + * needs please refer to http://www.magentocommerce.com for more information. + * + * @category Mage + * @package js + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ +var Captcha = Class.create(); +Captcha.prototype = { + initialize: function(url, formId){ + this.url = url; + this.formId = formId; + }, + refresh: function(elem) { + formId = this.formId; + if (elem) Element.addClassName(elem, 'refreshing'); + new Ajax.Request(this.url, { + onSuccess: function (response) { + if (response.responseText.isJSON()) { + var json = response.responseText.evalJSON(); + if (!json.error && json.imgSrc) { + $(formId).writeAttribute('src', json.imgSrc); + if (elem) Element.removeClassName(elem, 'refreshing'); + } else { + if (elem) Element.removeClassName(elem, 'refreshing'); + } + } + }, + method: 'post', + parameters: { + 'formId' : this.formId + } + }); + } +}; + +document.observe('billing-request:completed', function(event) { + if (typeof window.checkout != 'undefined') { + if (window.checkout.method == 'guest' && $('guest_checkout')){ + $('guest_checkout').captcha.refresh() + } + if (window.checkout.method == 'register' && $('register_during_checkout')){ + $('register_during_checkout').captcha.refresh() + } + } +}); + + +document.observe('login:setMethod', function(event) { + var switchCaptchaElement = function(shown, hidden) { + var inputPrefix = 'captcha-input-box-', imagePrefix = 'captcha-image-box-'; + if ($(inputPrefix + hidden)) { + $(inputPrefix + hidden).hide(); + $(imagePrefix + hidden).hide(); + } + if ($(inputPrefix + shown)) { + $(inputPrefix + shown).show(); + $(imagePrefix + shown).show(); + } + }; + + switch (event.memo.method) { + case 'guest': + switchCaptchaElement('guest_checkout', 'register_during_checkout'); + break; + case 'register': + switchCaptchaElement('register_during_checkout', 'guest_checkout'); + break; + } +}); diff --git a/js/mage/cookies.js b/js/mage/cookies.js index 792067b5..f9dbc7af 100644 --- a/js/mage/cookies.js +++ b/js/mage/cookies.js @@ -19,7 +19,7 @@ * * @category Mage * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ // old school cookie functions grabbed off the web diff --git a/js/mage/translate.js b/js/mage/translate.js index 4e5df1aa..6bf101e9 100644 --- a/js/mage/translate.js +++ b/js/mage/translate.js @@ -19,7 +19,7 @@ * * @category Mage * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/mage/translate_inline.css b/js/mage/translate_inline.css index a897b723..62510a84 100644 --- a/js/mage/translate_inline.css +++ b/js/mage/translate_inline.css @@ -19,16 +19,20 @@ * * @category Mage * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ -.translate-inline { /* background:black; font-weight:bold; color:red; */ border:dotted 1px red;} +[translate], .translate-inline { /* background:black; font-weight:bold; color:red; */ outline: dotted 1px red!important;} +* html .translate-inline, +*+html .translate-inline{ border: dotted 1px red !important;} .translate-inline-script, .translate-inline-title { background:yellow; color:black; font-weight:bold; } -#translate-inline-trig { position:absolute;left:-1000px; opacity:.8; filter:alpha(opacity=80); cursor:pointer; margin-top:10px; z-index:1000;} +#translate-inline-trig { position:absolute;left:-1000px; opacity:.8; filter:alpha(opacity=80); cursor:pointer; margin-top:10px; z-index:2000;} -.magento_table_container { background:#cbdee6; margin:10px; padding:8px 10px; } +.magento_table_container { background:#e7efef; margin:10px; padding:10px; } .magento_table_container table { width:100%; } -.magento_table_container td { padding:3px 0; text-align:left; } -.magento_table_container td.label { padding-right:9px; width:120px; } -.magento_table_container td.value input.input-text { width:95%; } +.magento_table_container td { padding:2px 0 !important; text-align:left; } +.magento_table_container th.label { width:150px; padding:2px 9px 2px 0 !important; font-weight:bold; } +.magento_table_container td.value .input-text { width:98%; } +.magento_table_container td.value textarea.input-text { height:8em; } +p.accent { color:#d12c01; } diff --git a/js/mage/translate_inline.js b/js/mage/translate_inline.js index 94afa046..8d68c86a 100644 --- a/js/mage/translate_inline.js +++ b/js/mage/translate_inline.js @@ -19,53 +19,78 @@ * * @category Mage * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var TranslateInline = Class.create(); TranslateInline.prototype = { - initialize: function(trigEl, ajaxUrl, area){ + initialize: function(trigEl, ajaxUrl, area) { this.ajaxUrl = ajaxUrl; this.area = area; this.trigTimer = null; this.trigContentEl = null; - - $$('*[translate]').each(this.initializeElement.bind(this)); - var scope = this; - Ajax.Responders.register({onComplete: function() {setTimeout(scope.reinitElements.bind(scope), 50)}}); + if (Prototype.Browser.IE) { + $$('*[translate]').each(this.initializeElement.bind(this)); + var scope = this; + Ajax.Responders.register({ onComplete: function() { + window.setTimeout(scope.reinitElements.bind(scope), 50) + } + }); + var ElementNode = (typeof HTMLElement != 'undefined' ? HTMLElement : Element) + var ElementUpdate = ElementNode.prototype.update; + ElementNode.prototype.update = function() { + ElementUpdate.apply(this, arguments); + $(this).select('*[translate]').each(scope.initializeElement.bind(scope)); + } + } this.trigEl = $(trigEl); - this.trigEl.observe('mouseover', this.trigHideClear.bind(this)); - this.trigEl.observe('mouseout', this.trigHideDelayed.bind(this)); this.trigEl.observe('click', this.formShow.bind(this)); + Event.observe(document.body, 'mousemove', function(e) { + var target = Event.element(e); + if (!$(target).match('*[translate]')) { + target = target.up('*[translate]'); + } + + if (target && $(target).match('*[translate]')) { + this.trigShow(target, e); + } else { + if (Event.element(e).match('#' + trigEl)) { + this.trigHideClear(); + } else { + this.trigHideDelayed(); + } + } + }.bind(this)); + this.helperDiv = document.createElement('div'); }, initializeElement: function(el) { - if(!el.initializedTranslate) { + if (!el.initializedTranslate) { el.addClassName('translate-inline'); el.initializedTranslate = true; - Event.observe(el, 'mouseover', this.trigShow.bind(this, el)); - Event.observe(el, 'mouseout', this.trigHideDelayed.bind(this)); } }, - reinitElements: function (el) { + reinitElements: function(el) { $$('*[translate]').each(this.initializeElement.bind(this)); }, - trigShow: function (el) { - this.trigHideClear(); - - var p = Element.cumulativeOffset(el); + trigShow: function(el, event) { + if (this.trigContentEl != el) { + this.trigHideClear(); + this.trigContentEl = el; + var p = Element.cumulativeOffset(el); - this.trigEl.style.left = p[0]+'px'; - this.trigEl.style.top = p[1]+'px'; - this.trigEl.style.display = 'block'; + this.trigEl.style.left = p[0] + 'px'; + this.trigEl.style.top = p[1] + 'px'; + this.trigEl.style.display = 'block'; - this.trigContentEl = el; + Event.stop(event); + }; }, trigHide: function() { @@ -73,15 +98,18 @@ TranslateInline.prototype = { this.trigContentEl = null; }, - trigHideDelayed: function () { - this.trigTimer = window.setTimeout(this.trigHide.bind(this), 500); + trigHideDelayed: function() { + if (this.trigTimer === null) { + this.trigTimer = window.setTimeout(this.trigHide.bind(this), 2000); + } }, trigHideClear: function() { clearInterval(this.trigTimer); + this.trigTimer = null; }, - formShow: function () { + formShow: function() { if (this.formIsShown) { return; } @@ -91,27 +119,27 @@ TranslateInline.prototype = { if (!el) { return; } - - eval('var data = '+el.getAttribute('translate')); + this.trigHideClear(); + eval('var data = ' + el.getAttribute('translate')); var content = '
    '; var t = new Template( - '
    '+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ - ''+ + '
    Location: #{location}
    Scope: #{scope}
    Shown: #{shown_escape}
    Original: #{original_escape}
    Translated: #{translated_escape}
    '+ - ''+ - '
    '+ - ''+ - ''+ - '
    ' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '
    Location:#{location}
    Scope:#{scope}
    Shown:#{shown_escape}
    Original:#{original_escape}
    Translated:#{translated_escape}
    ' + + '' + + '
    ' + + '' + + '' + + '
    ' ); - for (i=0; i -1, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && - navigator.userAgent.indexOf('KHTML') === -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, + + Version: '1.7', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), BrowserFeatures: { XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div')['__proto__'] && - document.createElement('div')['__proto__'] !== - document.createElement('form')['__proto__'] + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, + K: function(x) { return x } }; @@ -40,9 +61,38 @@ if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + /* Based on Alex Arnell's inheritance implementation. */ -var Class = { - create: function() { + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); @@ -56,39 +106,39 @@ var Class = { klass.subclasses = []; if (parent) { - var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } - for (var i = 0; i < properties.length; i++) + for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; - return klass; } -}; -Class.Methods = { - addMethods: function(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { + value.argumentNames()[0] == "$super") { var method = value; value = (function(m) { - return function() { return ancestor[m].apply(this, arguments) }; + return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); @@ -99,198 +149,338 @@ Class.Methods = { return this; } -}; -var Abstract = { }; + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + FUNCTION_CLASS = '[object Function]', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + DATE_CLASS = '[object Date]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } + + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } -Object.extend(Object, { - inspect: function(object) { + function inspect(object) { try { - if (Object.isUndefined(object)) return 'undefined'; + if (isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } - }, + } - toJSON: function(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); } - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; + var _class = _toString.call(value); - var results = []; - for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) - results.push(property.toJSON() + ': ' + value); + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); } - return '{' + results.join(', ') + '}'; - }, + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); - toQueryString: function(object) { + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } + + function toQueryString(object) { return $H(object).toQueryString(); - }, + } - toHTML: function(object) { + function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, + } - keys: function(object) { - var keys = []; - for (var property in object) - keys.push(property); - return keys; - }, + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } - values: function(object) { - var values = []; + function values(object) { + var results = []; for (var property in object) - values.push(object[property]); - return values; - }, + results.push(object[property]); + return results; + } - clone: function(object) { - return Object.extend({ }, object); - }, + function clone(object) { + return extend({ }, object); + } - isElement: function(object) { + function isElement(object) { return !!(object && object.nodeType == 1); - }, + } - isArray: function(object) { - return object != null && typeof object == "object" && - 'splice' in object && 'join' in object; - }, + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); - isHash: function(object) { + if (hasNativeIsArray) { + isArray = Array.isArray; + } + + function isHash(object) { return object instanceof Hash; - }, + } - isFunction: function(object) { - return typeof object == "function"; - }, + function isFunction(object) { + return _toString.call(object) === FUNCTION_CLASS; + } - isString: function(object) { - return typeof object == "string"; - }, + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } - isNumber: function(object) { - return typeof object == "number"; - }, + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } - isUndefined: function(object) { - return typeof object == "undefined"; + function isDate(object) { + return _toString.call(object) === DATE_CLASS; } -}); -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; + } + + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; - }, + } - bind: function() { + function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); + var __method = this, args = slice.call(arguments, 1); return function() { - return __method.apply(object, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(context, a); } - }, + } - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); return function(event) { - return __method.apply(object, [event || window.event].concat(args)); + var a = update([event || window.event], args); + return __method.apply(context, a); } - }, + } - curry: function() { + function curry() { if (!arguments.length) return this; - var __method = this, args = $A(arguments); + var __method = this, args = slice.call(arguments, 0); return function() { - return __method.apply(this, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(this, a); } - }, + } - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); - }, + } - defer: function() { - var args = [0.01].concat($A(arguments)); + function defer() { + var args = update([0.01], arguments); return this.delay.apply(this, args); - }, + } - wrap: function(wrapper) { + function wrap(wrapper) { var __method = this; return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); } - }, + } - methodize: function() { + function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); + var a = update([this], arguments); + return __method.apply(null, a); }; } -}); -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); -var Try = { - these: function() { - var returnValue; - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - return returnValue; +(function(proto) { + + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; } -}; + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; - -/*--------------------------------------------------------------------------*/ - var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; @@ -319,8 +509,10 @@ var PeriodicalExecuter = Class.create({ try { this.currentlyExecuting = true; this.execute(); - } finally { this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; } } } @@ -339,10 +531,28 @@ Object.extend(String, { } }); -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } while (source.length > 0) { if (match = source.match(pattern)) { @@ -354,76 +564,72 @@ Object.extend(String.prototype, { } } return result; - }, + } - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); - }, + } - scan: function(pattern, iterator) { + function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); - }, + } - truncate: function(length, truncation) { + function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); - }, + } - strip: function() { + function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, + } - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } - stripScripts: function() { + function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, + } - extractScripts: function() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); - }, + } - evalScripts: function() { + function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); - }, + } - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } - unescapeHTML: function() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; - }, - toQueryParams: function(separator) { + function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); if (key in hash) { @@ -434,128 +640,144 @@ Object.extend(String.prototype, { } return hash; }); - }, + } - toArray: function() { + function toArray() { return this.split(''); - }, + } - succ: function() { + function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, + } - times: function(count) { + function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + } - return camelized; - }, + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } - capitalize: function() { + function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, + } - underscore: function() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } - dasherize: function() { - return this.gsub(/_/,'-'); - }, + function dasherize() { + return this.replace(/_/g, '-'); + } - inspect: function(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { - return this.inspect(true); - }, + } - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } - isJSON: function() { + function isJSON() { var str = this; if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } - evalJSON: function(sanitize) { - var json = this.unfilterJSON(); + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } - include: function(pattern) { + function include(pattern) { return this.indexOf(pattern) > -1; - }, + } - startsWith: function(pattern) { - return this.indexOf(pattern) === 0; - }, + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } - endsWith: function(pattern) { + function endsWith(pattern) { var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - }, + return d >= 0 && this.indexOf(pattern, d) === d; + } - empty: function() { + function empty() { return this == ''; - }, + } - blank: function() { + function blank() { return /^\s*$/.test(this); - }, - - interpolate: function(object, pattern) { - return new Template(this, pattern).evaluate(object); } -}); -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); } -}); - -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); -String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); var Template = Class.create({ initialize: function(template, pattern) { @@ -564,22 +786,23 @@ var Template = Class.create({ }, evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) + if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; + if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); if (match == null) return before; while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); @@ -594,8 +817,8 @@ Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; -var Enumerable = { - each: function(iterator, context) { +var Enumerable = (function() { + function each(iterator, context) { var index = 0; try { this._each(function(value) { @@ -605,17 +828,17 @@ var Enumerable = { if (e != $break) throw e; } return this; - }, + } - eachSlice: function(number, iterator, context) { + function eachSlice(number, iterator, context) { var index = -number, slices = [], array = this.toArray(); if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); - }, + } - all: function(iterator, context) { + function all(iterator, context) { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { @@ -623,9 +846,9 @@ var Enumerable = { if (!result) throw $break; }); return result; - }, + } - any: function(iterator, context) { + function any(iterator, context) { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { @@ -633,18 +856,18 @@ var Enumerable = { throw $break; }); return result; - }, + } - collect: function(iterator, context) { + function collect(iterator, context) { iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator.call(context, value, index)); }); return results; - }, + } - detect: function(iterator, context) { + function detect(iterator, context) { var result; this.each(function(value, index) { if (iterator.call(context, value, index)) { @@ -653,32 +876,32 @@ var Enumerable = { } }); return result; - }, + } - findAll: function(iterator, context) { + function findAll(iterator, context) { var results = []; this.each(function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; - }, + } - grep: function(filter, iterator, context) { + function grep(filter, iterator, context) { iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) - filter = new RegExp(filter); + filter = new RegExp(RegExp.escape(filter)); this.each(function(value, index) { if (filter.match(value)) results.push(iterator.call(context, value, index)); }); return results; - }, + } - include: function(object) { + function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; @@ -690,31 +913,31 @@ var Enumerable = { } }); return found; - }, + } - inGroupsOf: function(number, fillWith) { + function inGroupsOf(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); - }, + } - inject: function(memo, iterator, context) { + function inject(memo, iterator, context) { this.each(function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; - }, + } - invoke: function(method) { + function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); - }, + } - max: function(iterator, context) { + function max(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { @@ -723,9 +946,9 @@ var Enumerable = { result = value; }); return result; - }, + } - min: function(iterator, context) { + function min(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { @@ -734,9 +957,9 @@ var Enumerable = { result = value; }); return result; - }, + } - partition: function(iterator, context) { + function partition(iterator, context) { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { @@ -744,26 +967,26 @@ var Enumerable = { trues : falses).push(value); }); return [trues, falses]; - }, + } - pluck: function(property) { + function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; - }, + } - reject: function(iterator, context) { + function reject(iterator, context) { var results = []; this.each(function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; - }, + } - sortBy: function(iterator, context) { + function sortBy(iterator, context) { return this.map(function(value, index) { return { value: value, @@ -773,13 +996,13 @@ var Enumerable = { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); - }, + } - toArray: function() { + function toArray() { return this.map(); - }, + } - zip: function() { + function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); @@ -788,336 +1011,416 @@ var Enumerable = { return this.map(function(value, index) { return iterator(collections.pluck(index)); }); - }, + } - size: function() { + function size() { return this.toArray().length; - }, + } - inspect: function() { + function inspect() { return '#'; } -}; -Object.extend(Enumerable, { - map: Enumerable.collect, - find: Enumerable.detect, - select: Enumerable.findAll, - filter: Enumerable.findAll, - member: Enumerable.include, - entries: Enumerable.toArray, - every: Enumerable.all, - some: Enumerable.any -}); + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + function $A(iterable) { if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); + if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } -if (Prototype.Browser.WebKit) { - $A = function(iterable) { - if (!iterable) return []; - // In Safari, only use the `toArray` method if it's not a NodeList. - // A NodeList is a function, has an function `item` property, and a numeric - // `length` property. Adapted from Google Doctype. - if (!(typeof iterable === 'function' && typeof iterable.length === - 'number' && typeof iterable.item === 'function') && iterable.toArray) - return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - }; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; } Array.from = $A; -Object.extend(Array.prototype, Enumerable); -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available -Object.extend(Array.prototype, { - _each: function(iterator) { - for (var i = 0, length = this.length; i < length; i++) - iterator(this[i]); - }, + function each(iterator, context) { + for (var i = 0, length = this.length >>> 0; i < length; i++) { + if (i in this) iterator.call(context, this[i], i, this); + } + } + if (!_each) _each = each; - clear: function() { + function clear() { this.length = 0; return this; - }, + } - first: function() { + function first() { return this[0]; - }, + } - last: function() { + function last() { return this[this.length - 1]; - }, + } - compact: function() { + function compact() { return this.select(function(value) { return value != null; }); - }, + } - flatten: function() { + function flatten() { return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; }); - }, + } - without: function() { - var values = $A(arguments); + function without() { + var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); - }, - - reverse: function(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - }, + } - reduce: function() { - return this.length > 1 ? this : this[0]; - }, + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } - uniq: function(sorted) { + function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); - }, + } - intersect: function(array) { + function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); - }, + } - clone: function() { - return [].concat(this); - }, - size: function() { + function clone() { + return slice.call(this, 0); + } + + function size() { return this.length; - }, + } - inspect: function() { + function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; - }, - - toJSON: function() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; } -}); -// use native browser JS 1.6 implementation if available -if (Object.isFunction(Array.prototype.forEach)) - Array.prototype._each = Array.prototype.forEach; - -if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; -}; - -if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; -}; - -Array.prototype.toArray = Array.prototype.clone; + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + function concat() { + var array = slice.call(this, 0), item; for (var i = 0, length = arguments.length; i < length; i++) { - if (Object.isArray(arguments[i])) { - for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) - array.push(arguments[i][j]); + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); } else { - array.push(arguments[i]); + array.push(item); } } return array; - }; -} -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); - }, - - succ: function() { - return this + 1; - }, + } - times: function(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - }, + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + } - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); + + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } } - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, + function set(key, value) { + return this._object[key] = value; + } - _each: function(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, + function get(key) { + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + } - set: function(key, value) { - return this._object[key] = value; - }, + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } - get: function(key) { - // simulating poorly supported hasOwnProperty - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - }, + function toObject() { + return Object.clone(this._object); + } - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, - toObject: function() { - return Object.clone(this._object); - }, - keys: function() { - return this.pluck('key'); - }, + function keys() { + return this.pluck('key'); + } - values: function() { - return this.pluck('value'); - }, + function values() { + return this.pluck('value'); + } - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } - merge: function(object) { - return this.clone().update(object); - }, + function merge(object) { + return this.clone().update(object); + } - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } - toQueryString: function() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return results.concat(values.map(toQueryPair.curry(key))); - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - }, + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; - inspect: function() { - return '#'; - }, + if (values && typeof values == 'object') { + if (Object.isArray(values)) { + var queryValues = []; + for (var i = 0, len = values.length, value; i < len; i++) { + value = values[i]; + queryValues.push(toQueryPair(key, value)); + } + return results.concat(queryValues); + } + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } - toJSON: function() { - return Object.toJSON(this.toObject()); - }, + function inspect() { + return '#'; + } - clone: function() { - return new Hash(this); - } + function clone() { + return new Hash(this); } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; })()); -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; - }, + } - _each: function(iterator) { + function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } - }, + } - include: function(value) { + function include(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } -}); -var $R = function(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -}; + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + var Ajax = { getTransport: function() { @@ -1164,7 +1467,6 @@ Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); - Ajax.Base = Class.create({ initialize: function(options) { this.options = { @@ -1180,13 +1482,10 @@ Ajax.Base = Class.create({ this.options.method = this.options.method.toLowerCase(); - if (Object.isString(this.options.parameters)) - this.options.parameters = this.options.parameters.toQueryParams(); - else if (Object.isHash(this.options.parameters)) + if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); - Ajax.Request = Class.create(Ajax.Base, { _complete: false, @@ -1199,24 +1498,21 @@ Ajax.Request = Class.create(Ajax.Base, { request: function(url) { this.url = url; this.method = this.options.method; - var params = Object.clone(this.options.parameters); + var params = Object.isString(this.options.parameters) ? + this.options.parameters : + Object.toQueryString(this.options.parameters); if (!['get', 'post'].include(this.method)) { - // simulate other verbs over post - params['_method'] = this.method; + params += (params ? '&' : '') + "_method=" + this.method; this.method = 'post'; } - this.parameters = params; - - if (params = Object.toQueryString(params)) { - // when GET, append parameters to URL - if (this.method == 'get') - this.url += (this.url.include('?') ? '&' : '?') + params; - else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) - params += '&_='; + if (params && this.method === 'get') { + this.url += (this.url.include('?') ? '&' : '?') + params; } + this.parameters = params.toQueryParams(); + try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); @@ -1269,7 +1565,6 @@ Ajax.Request = Class.create(Ajax.Base, { headers['Connection'] = 'close'; } - // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; @@ -1286,11 +1581,12 @@ Ajax.Request = Class.create(Ajax.Base, { success: function() { var status = this.getStatus(); - return !status || (status >= 200 && status < 300); + return !status || (status >= 200 && status < 300) || status == 304; }, getStatus: function() { try { + if (this.transport.status === 1223) return 204; return this.transport.status || 0; } catch (e) { return 0 } }, @@ -1323,7 +1619,6 @@ Ajax.Request = Class.create(Ajax.Base, { } if (state == 'Complete') { - // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, @@ -1340,7 +1635,7 @@ Ajax.Request = Class.create(Ajax.Base, { getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; - } catch (e) { return null } + } catch (e) { return null; } }, evalResponse: function() { @@ -1360,20 +1655,27 @@ Ajax.Request = Class.create(Ajax.Base, { Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } - if(readyState == 4) { + if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); @@ -1381,6 +1683,7 @@ Ajax.Response = Class.create({ }, status: 0, + statusText: '', getStatus: Ajax.Request.prototype.getStatus, @@ -1510,6 +1813,8 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); + + function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) @@ -1534,10 +1839,9 @@ if (Prototype.BrowserFeatures.XPath) { /*--------------------------------------------------------------------------*/ -if (!window.Node) var Node = { }; +if (!Node) var Node = { }; if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, @@ -1554,26 +1858,63 @@ if (!Node.ELEMENT_NODE) { }); } -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { + + +(function(global) { + function shouldUseCache(tagName, attributes) { + if (tagName === 'select') return false; + if ('type' in attributes) return false; + return true; + } + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { + + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + + var node = shouldUseCache(tagName, attributes) ? + cache[tagName].cloneNode(false) : document.createElement(tagName); + + return Element.writeAttribute(node, attributes); }; - Object.extend(this.Element, element || { }); - if (element) this.Element.prototype = element.prototype; -}).call(window); + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; Element.cache = { }; +Element._purgeElement = function(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; @@ -1603,15 +1944,116 @@ Element.Methods = { return element; }, - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "test"; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var LINK_ELEMENT_INNERHTML_BUGGY = (function() { + try { + var el = document.createElement('div'); + el.innerHTML = ""; + var isBuggy = (el.childNodes.length === 0); + el = null; + return isBuggy; + } catch(e) { + return true; + } + })(); + + var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || + TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + + function update(element, content) { + element = $(element); + var purgeElement = Element._purgeElement; + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (ANY_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); + nodes.each(function(node) { element.appendChild(node) }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), replace: function(element, content) { element = $(element); @@ -1679,28 +2121,35 @@ Element.Methods = { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, - recursivelyCollect: function(element, property) { + recursivelyCollect: function(element, property, maximumLength) { element = $(element); + maximumLength = maximumLength || -1; var elements = []; - while (element = element[property]) + + while (element = element[property]) { if (element.nodeType == 1) elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + return elements; }, ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); + return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { - return $(element).select("*"); + return Element.select(element, "*"); }, firstDescendant: function(element) { @@ -1710,78 +2159,96 @@ Element.Methods = { }, immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; }, - previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); + return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); }, match: function(element, selector) { + element = $(element); if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); + return Prototype.Selector.match(element, selector); + return selector.match(element); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); + var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); + Prototype.Selector.find(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? element.descendants()[expression] : + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } }, next: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } }, - select: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element, args); + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); }, - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element.parentNode, args).without(element); + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); }, identify: function(element) { element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; + var id = Element.readAttribute(element, 'id'); if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); return id; }, @@ -1820,11 +2287,11 @@ Element.Methods = { }, getHeight: function(element) { - return $(element).getDimensions().height; + return Element.getDimensions(element).height; }, getWidth: function(element) { - return $(element).getDimensions().width; + return Element.getDimensions(element).width; }, classNames: function(element) { @@ -1840,7 +2307,7 @@ Element.Methods = { addClassName: function(element, className) { if (!(element = $(element))) return; - if (!element.hasClassName(className)) + if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, @@ -1854,11 +2321,10 @@ Element.Methods = { toggleClassName: function(element, className) { if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); }, - // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; @@ -1892,7 +2358,7 @@ Element.Methods = { scrollTo: function(element) { element = $(element); - var pos = element.cumulativeOffset(); + var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, @@ -1938,37 +2404,12 @@ Element.Methods = { return element; }, - getDimensions: function(element) { - element = $(element); - var display = element.getStyle('display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; - // Opera returns the offset relative to the positioning context, when an - // element is position relative but top and left have not been defined if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; @@ -2007,119 +2448,6 @@ Element.Methods = { return element; }, - cumulativeOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - positionedOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (element.tagName.toUpperCase() == 'BODY') break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - absolutize: function(element) { - element = $(element); - if (element.getStyle('position') == 'absolute') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - var offsets = element.positionedOffset(); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; - - element._originalLeft = left - parseFloat(element.style.left || 0); - element._originalTop = top - parseFloat(element.style.top || 0); - element._originalWidth = element.style.width; - element._originalHeight = element.style.height; - - element.style.position = 'absolute'; - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.width = width + 'px'; - element.style.height = height + 'px'; - return element; - }, - - relativize: function(element) { - element = $(element); - if (element.getStyle('position') == 'relative') return element; - // Position.prepare(); // To be done manually by Scripty when it needs it. - - element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); - - element.style.top = top + 'px'; - element.style.left = left + 'px'; - element.style.height = element._originalHeight; - element.style.width = element._originalWidth; - return element; - }, - - cumulativeScrollOffset: function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return Element._returnOffset(valueL, valueT); - }, - - getOffsetParent: function(element) { - if (element.offsetParent) return $(element.offsetParent); - if (element == document.body) return $(element); - if(element.tagName.toUpperCase()=='HTML') //for IE6,7 - return $(document.body); // - - while ((element = element.parentNode) && element != document.body) - if (Element.getStyle(element, 'position') != 'static') - return $(element); - - return $(document.body); - }, - - viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - - // Safari fix - if (element.offsetParent == document.body && - Element.getStyle(element, 'position') == 'absolute') break; - - } while (element = element.offsetParent); - - element = forElement; - do { - if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - - return Element._returnOffset(valueL, valueT); - }, - clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, @@ -2130,28 +2458,21 @@ Element.Methods = { offsetLeft: 0 }, arguments[2] || { }); - // find page position of source source = $(source); - var p = source.viewportOffset(); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; - // find coordinate system to use element = $(element); - var delta = [0, 0]; - var parent = null; - // delta [0,0] will do fine with position: fixed elements, - // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); } - // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } - // set position if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; @@ -2160,10 +2481,9 @@ Element.Methods = { } }; -Element.Methods.identify.counter = 1; - Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants }); @@ -2181,14 +2501,9 @@ if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { - case 'left': case 'top': case 'right': case 'bottom': - if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': - // returns '0px' for hidden elements; we want it to return null if (!Element.visible(element)) return null; - // returns the border-box dimensions rather than the content-box - // dimensions, so we subtract padding and borders from the value var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) @@ -2221,52 +2536,6 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - // IE doesn't report offsets correctly for static elements, so we change them - // to "relative" to get the values, then change them back. - Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - // IE throws an error if element is not in document - try { element.offsetParent } - catch(e) { return $(document.body) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - $w('positionedOffset viewportOffset').each(function(method) { - Element.Methods[method] = Element.Methods[method].wrap( - function(proceed, element) { - element = $(element); - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - // Trigger hasLayout on the offset parent so that IE6 reports - // accurate offsetTop and offsetLeft values for position: fixed. - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - offsetParent.setStyle({ zoom: 1 }); - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - }); - - Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( - function(proceed, element) { - try { element.offsetParent } - catch(e) { return Element._returnOffset(0,0) } - return proceed(element); - } - ); - Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); @@ -2308,36 +2577,90 @@ else if (Prototype.Browser.IE) { return element; }; - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - return attribute ? attribute.toString().slice(23, -2) : null; - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp }, - title: function(element) { - return element.title; + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } } } } - }; + })(); Element._attributeTranslations.write = { names: Object.extend({ @@ -2365,8 +2688,8 @@ else if (Prototype.Browser.IE) { (function(v) { Object.extend(v, { - href: v._getAttr, - src: v._getAttr, + href: v._getAttr2, + src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, @@ -2393,6 +2716,26 @@ else if (Prototype.Browser.IE) { onchange: v._getEv }); })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { @@ -2411,7 +2754,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { + if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2421,49 +2764,9 @@ else if (Prototype.Browser.WebKit) { return element; }; - - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. - Element.Methods.cumulativeOffset = function(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return Element._returnOffset(valueL, valueT); - }; -} - -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; } -if ('outerHTML' in document.createElement('div')) { +if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2477,8 +2780,8 @@ if ('outerHTML' in document.createElement('div')) { var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); @@ -2499,12 +2802,27 @@ Element._returnOffset = function(l, t) { return result; }; -Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - if (t) { - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; +Element._getContentFromAnonymousElement = function(tagName, html, force) { + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + + var workaround = false; + if (t) workaround = true; + else if (force) { + workaround = true; + t = ['', '', 0]; + } + + if (workaround) { + div.innerHTML = ' ' + t[0] + html + t[1]; + div.removeChild(div.firstChild); + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } return $A(div.childNodes); }; @@ -2531,12 +2849,13 @@ Element._insertionTranslations = { }; (function() { - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD }); -}).call(Element._insertionTranslations); +})(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { @@ -2550,41 +2869,81 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); -if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div')['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div')['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; -} +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } return Prototype.K; + } var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { - if (!element || element._extendedByPrototype || + if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(), property, value; + tagName = element.tagName.toUpperCase(); - // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } + extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { - // extend methods for all tags (Safari doesn't need this) if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); @@ -2596,10 +2955,14 @@ Element.extend = (function() { return extend; })(); -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; @@ -2611,7 +2974,8 @@ Element.addMethods = function(methods) { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods) + "TEXTAREA": Object.clone(Form.Element.Methods), + "BUTTON": Object.clone(Form.Element.Methods) }); } @@ -2663,14 +3027,19 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = { }; - window[klass].prototype = document.createElement(tagName)['__proto__']; - return window[klass]; + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; } + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { @@ -2688,791 +3057,1947 @@ Element.addMethods = function(methods) { Element.cache = { }; }; + document.viewport = { + getDimensions: function() { - var dimensions = { }, B = Prototype.Browser; - $w('width height').each(function(d) { - var D = d.capitalize(); - if (B.WebKit && !document.evaluate) { - // Safari <3.0 needs self.innerWidth/Height - dimensions[d] = self['inner' + D]; - } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { - // Opera <9.5 needs document.body.clientWidth/Height - dimensions[d] = document.body['client' + D] - } else { - dimensions[d] = document.documentElement['client' + D]; - } - }); - return dimensions; - }, - - getWidth: function() { - return this.getDimensions().width; - }, - - getHeight: function() { - return this.getDimensions().height; + return { width: this.getWidth(), height: this.getHeight() }; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ - -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - }, +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; - var e = this.expression; + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; + return document.documentElement; + } - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; + function define(D) { + if (!element) element = getRootElement(); - return true; - }, + property[D] = 'client' + D; - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } - if (!Selector._div) Selector._div = new Element('div'); + viewport.getWidth = define.curry('Width'); - // Make sure the browser treats the selector as valid. Test on an - // isolated element to minimize cost of this check. - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; + viewport.getHeight = define.curry('Height'); +})(document.viewport); + + +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; } - return true; + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; }, - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; + store: function(element, key, value) { + if (!(element = $(element))) return; + + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); + } - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; } - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; + return value; + }, - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; } } - - this.matcher.push("return h.unique(n);\n}"); - eval(this.matcher.join('\n')); - Selector._cache[this.expression] = this.matcher; + return Element.extend(clone); }, - compileXPathMatcher: function() { - var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; + purge: function(element) { + if (!(element = $(element))) return; + var purgeElement = Element._purgeElement; + + purgeElement(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { - if (Selector._cache[e]) { - this.xpath = Selector._cache[e]; return; + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property, context) { + var element = null; + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); } - this.matcher = ['.//*']; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - if (m = e.match(ps[i])) { - this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : - new Template(x[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } + if (value === null) { + return null; } - this.xpath = this.matcher.join(''); - Selector._cache[this.expression] = this.xpath; - }, + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); + } - findElements: function(root) { - root = root || document; - var e = this.expression, results; - - switch (this.mode) { - case 'selectorsAPI': - // querySelectorAll queries document-wide, then filters to descendants - // of the context element. That's not what we want. - // Add an explicit context to the selector if necessary. - if (root !== document) { - var oldId = root.id, id = $(root).identify(); - e = "#" + id + " " + e; - } + var isPercentage = value.include('%'), isViewport = (context === document.viewport); - results = $A(root.querySelectorAll(e)).map(Element.extend); - root.id = oldId; + if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; - return results; - case 'xpath': - return document._getElementsByXPath(this.xpath, root); - default: - return this.matcher(root); + return value; } - }, - match: function(element) { - this.tokens = []; - - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; - - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - // use the Selector.assertions methods unless the selector - // is too complex. - if (as[i]) { - this.tokens.push([i, Object.clone(m)]); - e = e.replace(m[0], ''); - } else { - // reluctantly do a document-wide search - // and look for a match in the array - return this.findElements(document).include(element); - } + if (element && isPercentage) { + context = context || element.parentNode; + var decimal = toDecimal(value); + var whole = null; + var position = element.getStyle('position'); + + var isHorizontal = property.include('left') || property.include('right') || + property.include('width'); + + var isVertical = property.include('top') || property.include('bottom') || + property.include('height'); + + if (context === document.viewport) { + if (isHorizontal) { + whole = document.viewport.getWidth(); + } else if (isVertical) { + whole = document.viewport.getHeight(); + } + } else { + if (isHorizontal) { + whole = $(context).measure('width'); + } else if (isVertical) { + whole = $(context).measure('height'); } } + + return (whole === null) ? 0 : whole * decimal; } - var match = true, name, matches; - for (var i = 0, token; token = this.tokens[i]; i++) { - name = token[0], matches = token[1]; - if (!Selector.assertions[name](element, matches)) { - match = false; break; - } + return 0; + } + + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; } + return number + 'px'; + } - return match; - }, + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } - toString: function() { - return this.expression; - }, + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } - inspect: function() { - return "#"; + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); } -}); -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } + }, + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); + }, + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; + }, + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + if (width === "0px" || width === null) { + element.style.display = 'block'; + width = element.getStyle('width'); + } + + var context = (position === 'fixed') ? document.viewport : + element.parentNode; + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(element, 'width', context); + } else if (position === 'absolute' || position === 'fixed') { + newWidth = getPixelValue(element, 'width', context); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + + return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in p) { - if (m = e.match(p[i])) { - v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); - exclusion.push("(" + v.substring(1, v.length - 1) + ")"); - e = e.replace(m[0], ''); - break; - } - } + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) { + if (!this._preComputing) this._end(); + return 0; + } + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; + }, + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) { + if (!this._preComputing) this._end(); + return 0; } - return "[not(" + exclusion.join(" and ") + ")]"; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; + }, + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; + }, + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; + }, + + 'border-box-height': function(element) { + if (!this._preComputing) this._begin(); + var height = element.offsetHeight; + if (!this._preComputing) this._end(); + return height; + }, + + 'border-box-width': function(element) { + if (!this._preComputing) this._begin(); + var width = element.offsetWidth; + if (!this._preComputing) this._end(); + return width; + }, + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; + }, + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; + }, + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); }, - 'nth-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); }, - 'nth-last-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); }, - 'nth-of-type': function(m) { - return Selector.xpath.pseudos.nth("position() ", m); + + 'border-top': function(element) { + return getPixelValue(element, 'borderTopWidth'); }, - 'nth-last-of-type': function(m) { - return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + + 'border-bottom': function(element) { + return getPixelValue(element, 'borderBottomWidth'); }, - 'first-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + + 'border-left': function(element) { + return getPixelValue(element, 'borderLeftWidth'); }, - 'last-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + + 'border-right': function(element) { + return getPixelValue(element, 'borderRightWidth'); }, - 'only-of-type': function(m) { - var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); }, - nth: function(fragment, m) { - var mm, formula = m[6], predicate; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - if (mm = formula.match(/^(\d+)$/)) // digit only - return '[' + fragment + "= " + mm[1] + ']'; - if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; - predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + - "((#{fragment} - #{b}) div #{a} >= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); } } - }, + }); - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); - patterns: { - // combinators must be listed first - // (and descendant needs to be last combinator) - laterSibling: /^\s*~\s*/, - child: /^\s*>\s*/, - adjacent: /^\s*\+\s*/, - descendant: /^\s/, - - // selectors follow - tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, - id: /^#([\w\-\*]+)(\b|$)/, - className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: -/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, - attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ - }, + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; }, - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); }, - id: function(element, matches) { - return element.id === matches[1]; + inspect: function() { + return "#".interpolate(this); }, - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); + toString: function() { + return "[#{left}, #{top}]".interpolate(this); }, - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + toArray: function() { + return [this.left, this.top]; } - }, + }); - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } - // marks an array of nodes for counting - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, + function measure(element, property) { + return $(element).getLayout().get(property); + } - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = undefined; - return nodes; - }, + function getDimensions(element) { + element = $(element); + var display = Element.getStyle(element, 'display'); - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, + if (display && display !== 'none') { + return { width: element.offsetWidth, height: element.offsetHeight }; + } - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._countedByPrototype) { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, + var style = element.style; + var originalStyles = { + visibility: style.visibility, + position: style.position, + display: style.display + }; - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, + var newStyles = { + visibility: 'hidden', + display: 'block' + }; - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, + if (originalStyles.position !== 'fixed') + newStyles.position = 'absolute'; - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, + Element.setStyle(element, newStyles); - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, + var dimensions = { + width: element.offsetWidth, + height: element.offsetHeight + }; - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, + Element.setStyle(element, originalStyles); - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, + return dimensions; + } - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, + function getOffsetParent(element) { + element = $(element); - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return isHtml(element) ? $(document.body) : $(element); } - return results; - }, + } - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, + return $(document.body); + } - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); + function cumulativeOffset(element) { + element = $(element); + var valueT = 0, valueL = 0; + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); } - }, + return new Element.Offset(valueL, valueT); + } - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, + function positionedOffset(element) { + element = $(element); - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, + var layout = element.getLayout(); - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + element = $(element); + var valueT = 0, valueL = 0, docBody = document.body; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; } - return results; - }, + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, + function absolutize(element) { + element = $(element); - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); + + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; } - }, - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '$=': function(nv, v) { return nv.endsWith(v); }, - '*=': function(nv, v) { return nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + if (Prototype.Browser.IE) { + getOffsetParent = getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + + if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) + return $(document.body); + + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + positionedOffset = positionedOffset.wrap(function(proceed, element) { + element = $(element); + if (!element.parentNode) return new Element.Offset(0, 0); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + hasLayout(offsetParent); + + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; }); - return expressions; - }, + } else if (Prototype.Browser.Webkit) { + cumulativeOffset = function(element) { + element = $(element); + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; - matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; - }, + element = element.offsetParent; + } while (element); - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; + return new Element.Offset(valueL, valueT); + }; + } + + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isHtml(element) { + return element.nodeName.toUpperCase() === 'HTML'; + } + + function isDocument(element) { + return element.nodeType === Node.DOCUMENT_NODE; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + } + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +Prototype.Selector = (function() { + + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, + } - findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); } - return (l > 1) ? h.unique(results) : results; + return elements; } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; }); -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }, +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; - // IE improperly serializes _countedByPrototype in (inner|outer)HTML. - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } - }); +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; } -function $$() { - return Selector.findChildElements(document, $A(arguments)); +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; } + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + + +window.Sizzle = Sizzle; + +})(); + +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; + + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; + var Form = { reset: function(form) { - $(form).reset(); + form = $(form); + form.reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit; + var key, value, submitted = false, submit = options.submit, accumulator, initial; + + if (options.hash) { + initial = {}; + accumulator = function(result, key, value) { + if (key in result) { + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } else result[key] = value; + return result; + }; + } else { + initial = ''; + accumulator = function(result, key, value) { + return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value); + } + } - var data = elements.inject({ }, function(result, element) { + return elements.inject(initial, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { - if (key in result) { - // a key is already present; construct an array of values - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } - else result[key] = value; + result = accumulator(result, key, value); } } return result; }); - - return options.hash ? data : Object.toQueryString(data); } }; @@ -3482,13 +5007,18 @@ Form.Methods = { }, getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) }, getInputs: function(form, typeName, name) { @@ -3528,13 +5058,14 @@ Form.Methods = { }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, focusFirstElement: function(form) { form = $(form); - form.findFirstElement().activate(); + var element = form.findFirstElement(); + if (element) element.activate(); return form; }, @@ -3559,6 +5090,7 @@ Form.Methods = { /*--------------------------------------------------------------------------*/ + Form.Element = { focus: function(element) { $(element).focus(); @@ -3572,6 +5104,7 @@ Form.Element = { }; Form.Element.Methods = { + serialize: function(element) { element = $(element); if (!element.disabled && element.name) { @@ -3612,7 +5145,7 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; @@ -3634,75 +5167,86 @@ Form.Element.Methods = { /*--------------------------------------------------------------------------*/ var Field = Form.Element; + var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ -Form.Element.Serializers = { - input: function(element, value) { +Form.Element.Serializers = (function() { + function input(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': - return Form.Element.Serializers.inputSelector(element, value); + return inputSelector(element, value); default: - return Form.Element.Serializers.textarea(element, value); + return valueSelector(element, value); } - }, + } - inputSelector: function(element, value) { - if (Object.isUndefined(value)) return element.checked ? element.value : null; + function inputSelector(element, value) { + if (Object.isUndefined(value)) + return element.checked ? element.value : null; else element.checked = !!value; - }, + } - textarea: function(element, value) { + function valueSelector(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; - }, + } - select: function(element, value) { + function select(element, value) { if (Object.isUndefined(value)) - return this[element.type == 'select-one' ? - 'selectOne' : 'selectMany'](element); - else { - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } + return (element.type === 'select-one' ? selectOne : selectMany)(element); + + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; } - else opt.selected = value.include(currentValue); } + else opt.selected = value.include(currentValue); } - }, + } - selectOne: function(element) { + function selectOne(element) { var index = element.selectedIndex; - return index >= 0 ? this.optionValue(element.options[index]) : null; - }, + return index >= 0 ? optionValue(element.options[index]) : null; + } - selectMany: function(element) { + function selectMany(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; - if (opt.selected) values.push(this.optionValue(opt)); + if (opt.selected) values.push(optionValue(opt)); } return values; - }, + } - optionValue: function(opt) { - // extend element because hasAttribute may not be native - return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + function optionValue(opt) { + return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; } -}; + + return { + input: input, + inputSelector: inputSelector, + textarea: valueSelector, + select: select, + selectOne: selectOne, + selectMany: selectMany, + optionValue: optionValue, + button: valueSelector + }; +})(); /*--------------------------------------------------------------------------*/ + Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); @@ -3784,360 +5328,527 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { return Form.serialize(this.element); } }); -if (!window.Event) var Event = { }; - -Object.extend(Event, { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: { }, - - relatedTarget: function(event) { - var element; - switch(event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; + + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + + + + var isIELegacyEvent = function(event) { return false; }; + + if (window.attachEvent) { + if (window.addEventListener) { + isIELegacyEvent = function(event) { + return !(event instanceof window.Event); + }; + } else { + isIELegacyEvent = function(event) { return true; }; + } } -}); -Event.Methods = (function() { - var isButton; + var _isButton; - if (Prototype.Browser.IE) { - var buttonMap = { 0: 1, 1: 4, 2: 2 }; - isButton = function(event, code) { - return event.button == buttonMap[code]; - }; + function _isButtonForDOMEvents(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + } - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 1 && event.metaKey; - default: return false; - } - }; + var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; + function _isButtonForLegacyEvents(event, code) { + return event.button === legacyButtonMap[code]; + } - } else { - isButton = function(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - }; + function _isButtonForWebKit(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 2 || (event.which == 1 && event.metaKey); + case 2: return event.which == 3; + default: return false; + } } - return { - isLeftClick: function(event) { return isButton(event, 0) }, - isMiddleClick: function(event) { return isButton(event, 1) }, - isRightClick: function(event) { return isButton(event, 2) }, - - element: function(event) { - event = Event.extend(event); - - var node = event.target, - type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - // Firefox screws up the "click" event when moving between radio buttons - // via arrow keys. It also screws up the "load" and "error" events on images, - // reporting the document as the target instead of the original image. - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; + if (window.attachEvent) { + if (!window.addEventListener) { + _isButton = _isButtonForLegacyEvents; + } else { + _isButton = function(event, code) { + return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : + _isButtonForDOMEvents(event, code); } - if (node) { - if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; - return Element.extend(node); - } else return false; - }, + } + } else if (Prototype.Browser.WebKit) { + _isButton = _isButtonForWebKit; + } else { + _isButton = _isButtonForDOMEvents; + } - findElement: function(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - }, + function isLeftClick(event) { return _isButton(event, 0) } - pointer: function(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0, scrollTop: 0 }; - return { - x: event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)), - y: event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)) - }; - }, + function isMiddleClick(event) { return _isButton(event, 1) } + + function isRightClick(event) { return _isButton(event, 2) } + + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; + + return Element.extend(node); + } - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, + function findElement(event, expression) { + var element = Event.element(event); - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop }; -})(); -Event.extend = (function() { var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); - if (Prototype.Browser.IE) { - Object.extend(methods, { + if (window.attachEvent) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': + case 'mouseenter': + element = event.fromElement; + break; + case 'mouseout': + case 'mouseleave': + element = event.toElement; + break; + default: + return null; + } + return Element.extend(element); + } + + var additionalMethods = { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, - inspect: function() { return "[object Event]" } - }); + inspect: function() { return '[object Event]' } + }; - return function(event) { + Event.extend = function(event, element) { if (!event) return false; - if (event._extendedByPrototype) return event; + if (!isIELegacyEvent(event)) return event; + + if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { - target: event.srcElement, - relatedTarget: Event.relatedTarget(event), + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); - return Object.extend(event, methods); - }; + Object.extend(event, methods); + Object.extend(event, additionalMethods); + + return event; + }; } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; + Event.extend = Prototype.K; + } + + if (window.addEventListener) { + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); - return Prototype.K; } -})(); -Object.extend(Event, (function() { - var cache = Event.cache; + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); - function getEventID(element) { - try { - if (element._prototypeEventID) return element._prototypeEventID[0]; - arguments.callee.id = arguments.callee.id || 1; - return element._prototypeEventID = [++arguments.callee.id]; - } catch (error) { - return false; + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); } - } - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } - - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } + if (respondersForEvent.pluck('handler').include(handler)) return false; - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) + if (event.eventName !== eventName) return false; - Event.extend(event); - handler.call(element, event); - }; + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } + if (parent === element) return; - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + responder.handler = handler; + respondersForEvent.push(responder); + return responder; } - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } } + var CACHE = []; - // Internet Explorer needs to remove event handlers on page unload - // in order to avoid memory leaks. - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); - } + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); - // Safari has a dummy event handler on page unload so that it won't - // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" - // object when page is returned to via the back button using its bfcache. - if (Prototype.Browser.WebKit) { + if (Prototype.Browser.WebKit) window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; } - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); + function observe(element, eventName, handler) { + element = $(element); - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; + var responder = _createResponder(element, eventName, handler); - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onlosecapture", responder); } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + function stopObserving(element, eventName, handler) { + element = $(element); + + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); return element; - }, + } - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); + var responders = registry.get(eventName); + if (!responders) return element; - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; + var i = responders.length, responder; + while (i--) { + if (responders[i].handler === handler) { + responder = responders[i]; + break; + } + } + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onlosecapture", responder); } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; + registry.set(eventName, responders.without(responder)); - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } + return element; + } - destroyWrapper(id, eventName, handler); + function fire(element, eventName, memo, bubble) { + element = $(element); - return element; - }, + if (Object.isUndefined(bubble)) + bubble = true; - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; - var event; - if (document.createEvent) { - event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - event = document.createEventObject(); - event.eventType = "ondataavailable"; - } + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', bubble, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; + } - event.eventName = eventName; - event.memo = memo || { }; + event.eventName = eventName; + event.memo = memo || { }; - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); - } + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, - return Event.extend(event); + handleEvent: function(event) { + var element = Event.findElement(event, this.selector); + if (element) this.callback.call(this.element, event, element); } - }; -})()); + }); -Object.extend(Event, Event.Methods); + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); + return new Event.Handler(element, eventName, selector, callback).start(); + } -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize(), - loaded: false -}); + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards and John Resig. */ + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); + if (timer) window.clearTimeout(timer); document.loaded = true; + document.fire('dom:loaded'); } - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; } + fireContentLoadedEvent(); + } + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { - document.write("'; - - bi = s.body_id || 'tinymce'; - if (bi.indexOf('=') != -1) { - bi = t.getParam('body_id', '', 'hash'); - bi = bi[t.id] || bi; - } - - bc = s.body_class || ''; - if (bc.indexOf('=') != -1) { - bc = t.getParam('body_class', '', 'hash'); - bc = bc[t.id] || ''; - } - - t.iframeHTML += ''; - - // Domain relaxing enabled, then set document domain - if (tinymce.relaxedDomain) { - // We need to write the contents here in IE since multiple writes messes up refresh button and back button - if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; - else if (tinymce.isOpera) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; - } - - // Create iframe - n = DOM.add(o.iframeContainer, 'iframe', { - id : t.id + "_ifr", - src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 - frameBorder : '0', - style : { - width : '100%', - height : h - } - }); - - t.contentAreaContainer = o.iframeContainer; - DOM.get(o.editorContainer).style.display = t.orgDisplay; - DOM.get(t.id).style.display = 'none'; - - if (!isIE || !tinymce.relaxedDomain) - t.setupIframe(); - - e = n = o = null; // Cleanup - }, - - /** - * This method get called by the init method ones the iframe is loaded. - * It will fill the iframe with contents, setups DOM and selection objects for the iframe. - * This method should not be called directly. - * - * @method setupIframe - */ - setupIframe : function() { - var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; - - // Setup iframe body - if (!isIE || !tinymce.relaxedDomain) { - d.open(); - d.write(t.iframeHTML); - d.close(); - } - - // Design mode needs to be added here Ctrl+A will fail otherwise - if (!isIE) { - try { - if (!s.readonly) - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - - // IE needs to use contentEditable or it will display non secure items for HTTPS - if (isIE) { - // It will not steal focus if we hide it while setting contentEditable - b = t.getBody(); - DOM.hide(b); - - if (!s.readonly) - b.contentEditable = true; - - DOM.show(b); - } - - /** - * DOM instance for the editor. - * - * @property dom - * @type tinymce.dom.DOMUtils - */ - t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { - keep_values : true, - url_converter : t.convertURL, - url_converter_scope : t, - hex_colors : s.force_hex_style_colors, - class_filter : s.class_filter, - update_styles : 1, - fix_ie_paragraphs : 1, - valid_styles : s.valid_styles - }); - - /** - * Schema instance, enables you to validate elements and it's children. - * - * @property schema - * @type tinymce.dom.Schema - */ - t.schema = new tinymce.dom.Schema(); - - /** - * DOM serializer for the editor. - * - * @property serializer - * @type tinymce.dom.Serializer - */ - t.serializer = new tinymce.dom.Serializer(extend(s, { - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - dom : t.dom, - schema : t.schema - })); - - /** - * Selection instance for the editor. - * - * @property selection - * @type tinymce.dom.Selection - */ - t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); - - /** - * Formatter instance. - * - * @property formatter - * @type tinymce.Formatter - */ - t.formatter = new tinymce.Formatter(this); - - // Register default formats - t.formatter.register({ - alignleft : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, - {selector : 'img,table', styles : {'float' : 'left'}} - ], - - aligncenter : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, - {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, - {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} - ], - - alignright : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, - {selector : 'img,table', styles : {'float' : 'right'}} - ], - - alignfull : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} - ], - - bold : [ - {inline : 'strong'}, - {inline : 'span', styles : {fontWeight : 'bold'}}, - {inline : 'b'} - ], - - italic : [ - {inline : 'em'}, - {inline : 'span', styles : {fontStyle : 'italic'}}, - {inline : 'i'} - ], - - underline : [ - {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, - {inline : 'u'} - ], - - strikethrough : [ - {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, - {inline : 'u'} - ], - - forecolor : {inline : 'span', styles : {color : '%value'}}, - hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, - fontname : {inline : 'span', styles : {fontFamily : '%value'}}, - fontsize : {inline : 'span', styles : {fontSize : '%value'}}, - blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, - - removeformat : [ - {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, - {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, - {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} - ] - }); - - // Register default block formats - each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { - t.formatter.register(name, {block : name, remove : 'all'}); - }); - - // Register user defined formats - t.formatter.register(t.settings.formats); - - /** - * Undo manager instance, responsible for handling undo levels. - * - * @property undoManager - * @type tinymce.UndoManager - */ - t.undoManager = new tinymce.UndoManager(t); - - // Pass through - t.undoManager.onAdd.add(function(um, l) { - if (!l.initial) - return t.onChange.dispatch(t, l, um); - }); - - t.undoManager.onUndo.add(function(um, l) { - return t.onUndo.dispatch(t, l, um); - }); - - t.undoManager.onRedo.add(function(um, l) { - return t.onRedo.dispatch(t, l, um); - }); - - t.forceBlocks = new tinymce.ForceBlocks(t, { - forced_root_block : s.forced_root_block - }); - - t.editorCommands = new tinymce.EditorCommands(t); - - // Pass through - t.serializer.onPreProcess.add(function(se, o) { - return t.onPreProcess.dispatch(t, o, se); - }); - - t.serializer.onPostProcess.add(function(se, o) { - return t.onPostProcess.dispatch(t, o, se); - }); - - t.onPreInit.dispatch(t); - - if (!s.gecko_spellcheck) - t.getBody().spellcheck = 0; - - if (!s.readonly) - t._addEvents(); - - t.controlManager.onPostRender.dispatch(t, t.controlManager); - t.onPostRender.dispatch(t); - - if (s.directionality) - t.getBody().dir = s.directionality; - - if (s.nowrap) - t.getBody().style.whiteSpace = "nowrap"; - - if (s.custom_elements) { - function handleCustom(ed, o) { - each(explode(s.custom_elements), function(v) { - var n; - - if (v.indexOf('~') === 0) { - v = v.substring(1); - n = 'span'; - } else - n = 'div'; - - o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); - o.content = o.content.replace(new RegExp('', 'g'), ''); - }); - }; - - t.onBeforeSetContent.add(handleCustom); - t.onPostProcess.add(function(ed, o) { - if (o.set) - handleCustom(ed, o); - }); - } - - if (s.handle_node_change_callback) { - t.onNodeChange.add(function(ed, cm, n) { - t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); - }); - } - - if (s.save_callback) { - t.onSaveContent.add(function(ed, o) { - var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); - - if (h) - o.content = h; - }); - } - - if (s.onchange_callback) { - t.onChange.add(function(ed, l) { - t.execCallback('onchange_callback', t, l); - }); - } - - if (s.convert_newlines_to_brs) { - t.onBeforeSetContent.add(function(ed, o) { - if (o.initial) - o.content = o.content.replace(/\r?\n/g, '
    '); - }); - } - - if (s.fix_nesting && isIE) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t._fixNesting(o.content); - }); - } - - if (s.preformatted) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^\s*/, ''); - o.content = o.content.replace(/<\/pre>\s*$/, ''); - - if (o.set) - o.content = '
    ' + o.content + '
    '; - }); - } - - if (s.verify_css_classes) { - t.serializer.attribValueFilter = function(n, v) { - var s, cl; - - if (n == 'class') { - // Build regexp for classes - if (!t.classesRE) { - cl = t.dom.getClasses(); - - if (cl.length > 0) { - s = ''; - - each (cl, function(o) { - s += (s ? '|' : '') + o['class']; - }); - - t.classesRE = new RegExp('(' + s + ')', 'gi'); - } - } - - return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; - } - - return v; - }; - } - - if (s.cleanup_callback) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - }); - - t.onPreProcess.add(function(ed, o) { - if (o.set) - t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); - - if (o.get) - t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); - }); - - t.onPostProcess.add(function(ed, o) { - if (o.set) - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - - if (o.get) - o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); - }); - } - - if (s.save_callback) { - t.onGetContent.add(function(ed, o) { - if (o.save) - o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); - }); - } - - if (s.handle_event_callback) { - t.onEvent.add(function(ed, e, o) { - if (t.execCallback('handle_event_callback', e, ed, o) === false) - Event.cancel(e); - }); - } - - // Add visual aids when new contents is added - t.onSetContent.add(function() { - t.addVisual(t.getBody()); - }); - - // Remove empty contents - if (s.padd_empty_editor) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); - }); - } - - if (isGecko) { - // Fix gecko link bug, when a link is placed at the end of block elements there is - // no way to move the caret behind the link. This fix adds a bogus br element after the link - function fixLinks(ed, o) { - each(ed.dom.select('a'), function(n) { - var pn = n.parentNode; - - if (ed.dom.isBlock(pn) && pn.lastChild === n) - ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); - }); - }; - - t.onExecCommand.add(function(ed, cmd) { - if (cmd === 'CreateLink') - fixLinks(ed); - }); - - t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); - - if (!s.readonly) { - try { - // Design mode must be set here once again to fix a bug where - // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again - d.designMode = 'Off'; - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - } - - // A small timeout was needed since firefox will remove. Bug: #1838304 - setTimeout(function () { - if (t.removed) - return; - - t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); - t.startContent = t.getContent({format : 'raw'}); - t.initialized = true; - - t.onInit.dispatch(t); - t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); - t.execCallback('init_instance_callback', t); - t.focus(true); - t.nodeChanged({initial : 1}); - - // Load specified content CSS last - if (s.content_css) { - tinymce.each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } - - // Handle auto focus - if (s.auto_focus) { - setTimeout(function () { - var ed = tinymce.get(s.auto_focus); - - ed.selection.select(ed.getBody(), 1); - ed.selection.collapse(1); - ed.getWin().focus(); - }, 100); - } - }, 1); - - e = null; - }, - - // #ifdef contentEditable - - /** - * Sets up the contentEditable mode. - * - * @method setupContentEditable - */ - setupContentEditable : function() { - var t = this, s = t.settings, e = t.getElement(); - - t.contentDocument = s.content_document || document; - t.contentWindow = s.content_window || window; - t.bodyElement = e; - - // Prevent leak in IE - s.content_document = s.content_window = null; - - DOM.hide(e); - e.contentEditable = t.getParam('content_editable_state', true); - DOM.show(e); - - if (!s.gecko_spellcheck) - t.getDoc().body.spellcheck = 0; - - // Setup objects - t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { - keep_values : true, - url_converter : t.convertURL, - url_converter_scope : t, - hex_colors : s.force_hex_style_colors, - class_filter : s.class_filter, - root_element : t.id, - fix_ie_paragraphs : 1, - update_styles : 1, - valid_styles : s.valid_styles - }); - - t.serializer = new tinymce.dom.Serializer({ - entity_encoding : s.entity_encoding, - entities : s.entities, - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - extended_valid_elements : s.extended_valid_elements, - valid_child_elements : s.valid_child_elements, - invalid_elements : s.invalid_elements, - fix_table_elements : s.fix_table_elements, - fix_list_elements : s.fix_list_elements, - fix_content_duplication : s.fix_content_duplication, - font_size_classes : s.font_size_classes, - apply_source_formatting : s.apply_source_formatting, - dom : t.dom, - schema : schema - }); - - t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); - t.forceBlocks = new tinymce.ForceBlocks(t, { - forced_root_block : s.forced_root_block - }); - - t.editorCommands = new tinymce.EditorCommands(t); - - // Pass through - t.serializer.onPreProcess.add(function(se, o) { - return t.onPreProcess.dispatch(t, o, se); - }); - - t.serializer.onPostProcess.add(function(se, o) { - return t.onPostProcess.dispatch(t, o, se); - }); - - t.onPreInit.dispatch(t); - t._addEvents(); - - t.controlManager.onPostRender.dispatch(t, t.controlManager); - t.onPostRender.dispatch(t); - - t.onSetContent.add(function() { - t.addVisual(t.getBody()); - }); - - //t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); - t.startContent = t.getContent({format : 'raw'}); - t.undoManager.add({initial : true}); - t.initialized = true; - - t.onInit.dispatch(t); - t.focus(true); - t.nodeChanged({initial : 1}); - - // Load specified content CSS last - if (s.content_css) { - each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } - - if (isIE) { - // Store away selection - t.dom.bind(t.getElement(), 'beforedeactivate', function() { - t.lastSelectionBookmark = t.selection.getBookmark(1); - }); - - t.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) { - if (!DOM.getParent(ed.selection.getStart(), function(n) {return n == ed.getBody();})) - o.terminate = 1; - - if (!DOM.getParent(ed.selection.getEnd(), function(n) {return n == ed.getBody();})) - o.terminate = 1; - }); - } - - e = null; // Cleanup - }, - - // #endif - - /** - * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection - * it will also place DOM focus inside the editor. - * - * @method focus - * @param {Boolean} sf Skip DOM focus. Just set is as the active editor. - */ - focus : function(sf) { - var oed, t = this, ce = t.settings.content_editable; - - if (!sf) { - // Is not content editable - if (!ce) - t.getWin().focus(); - - // #ifdef contentEditable - - // Content editable mode ends here - if (ce) { - if (tinymce.isWebKit) - t.getWin().focus(); - else { - if (tinymce.isIE) - t.getElement().setActive(); - else - t.getElement().focus(); - } - } - - // #endif - } - - if (tinymce.activeEditor != t) { - if ((oed = tinymce.activeEditor) != null) - oed.onDeactivate.dispatch(oed, t); - - t.onActivate.dispatch(t, oed); - } - - tinymce._setActive(t); - }, - - /** - * Executes a legacy callback. This method is useful to call old 2.x option callbacks. - * There new event model is a better way to add callback so this method might be removed in the future. - * - * @method execCallback - * @param {String} n Name of the callback to execute. - * @return {Object} Return value passed from callback function. - */ - execCallback : function(n) { - var t = this, f = t.settings[n], s; - - if (!f) - return; - - // Look through lookup - if (t.callbackLookup && (s = t.callbackLookup[n])) { - f = s.func; - s = s.scope; - } - - if (is(f, 'string')) { - s = f.replace(/\.\w+$/, ''); - s = s ? tinymce.resolve(s) : 0; - f = tinymce.resolve(f); - t.callbackLookup = t.callbackLookup || {}; - t.callbackLookup[n] = {func : f, scope : s}; - } - - return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); - }, - - /** - * Translates the specified string by replacing variables with language pack items it will also check if there is - * a key mathcin the input. - * - * @method translate - * @param {String} s String to translate by the language pack data. - * @return {String} Translated string. - */ - translate : function(s) { - var c = this.settings.language || 'en', i18n = tinymce.i18n; - - if (!s) - return ''; - - return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { - return i18n[c + '.' + b] || '{#' + b + '}'; - }); - }, - - /** - * Returns a language pack item by name/key. - * - * @method getLang - * @param {String} n Name/key to get from the language pack. - * @param {String} dv Optional default value to retrive. - */ - getLang : function(n, dv) { - return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); - }, - - /** - * Returns a configuration parameter by name. - * - * @method getParam - * @param {String} n Configruation parameter to retrive. - * @param {String} dv Optional default value to return. - * @param {String} ty Optional type parameter. - * @return {String} Configuration parameter value or default value. - */ - getParam : function(n, dv, ty) { - var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; - - if (ty === 'hash') { - o = {}; - - if (is(v, 'string')) { - each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { - v = v.split('='); - - if (v.length > 1) - o[tr(v[0])] = tr(v[1]); - else - o[tr(v[0])] = tr(v); - }); - } else - o = v; - - return o; - } - - return v; - }, - - /** - * Distpaches out a onNodeChange event to all observers. This method should be called when you - * need to update the UI states or element path etc. - * - * @method nodeChanged - * @param {Object} o Optional object to pass along for the node changed event. - */ - nodeChanged : function(o) { - var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); - - // Fix for bug #1896577 it seems that this can not be fired while the editor is loading - if (t.initialized) { - o = o || {}; - n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state - - // Get parents and add them to object - o.parents = []; - t.dom.getParent(n, function(node) { - if (node.nodeName == 'BODY') - return true; - - o.parents.push(node); - }); - - t.onNodeChange.dispatch( - t, - o ? o.controlManager || t.controlManager : t.controlManager, - n, - s.isCollapsed(), - o - ); - } - }, - - /** - * Adds a button that later gets created by the ControlManager. This is a shorter and easier method - * of adding buttons without the need to deal with the ControlManager directly. But it's also less - * powerfull if you need more control use the ControlManagers factory methods instead. - * - * @method addButton - * @param {String} n Button name to add. - * @param {Object} s Settings object with title, cmd etc. - */ - addButton : function(n, s) { - var t = this; - - t.buttons = t.buttons || {}; - t.buttons[n] = s; - }, - - /** - * Adds a custom command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with execCommand. - * - * @method addCommand - * @param {String} n Command name to add/override. - * @param {function} f Function to execute when the command occurs. - * @param {Object} s Optional scope to execute the function in. - */ - addCommand : function(n, f, s) { - this.execCommands[n] = {func : f, scope : s || this}; - }, - - /** - * Adds a custom query state command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with queryCommandState function. - * - * @method addQueryStateHandler - * @param {String} n Command name to add/override. - * @param {function} f Function to execute when the command state retrival occurs. - * @param {Object} s Optional scope to execute the function in. - */ - addQueryStateHandler : function(n, f, s) { - this.queryStateCommands[n] = {func : f, scope : s || this}; - }, - - /** - * Adds a custom query value command to the editor, you can also override existing commands with this method. - * The command that you add can be executed with queryCommandValue function. - * - * @method addQueryValueHandler - * @param {String} n Command name to add/override. - * @param {function} f Function to execute when the command value retrival occurs. - * @param {Object} s Optional scope to execute the function in. - */ - addQueryValueHandler : function(n, f, s) { - this.queryValueCommands[n] = {func : f, scope : s || this}; - }, - - /** - * Adds a keyboard shortcut for some command or function. - * - * @method addShortcut - * @param {String} pa Shortcut pattern. Like for example: ctrl+alt+o. - * @param {String} desc Text description for the command. - * @param {String/Function} cmd_func Command name string or function to execute when the key is pressed. - * @param {Object} sc Optional scope to execute the function in. - * @return {Boolean} true/false state if the shortcut was added or not. - */ - addShortcut : function(pa, desc, cmd_func, sc) { - var t = this, c; - - if (!t.settings.custom_shortcuts) - return false; - - t.shortcuts = t.shortcuts || {}; - - if (is(cmd_func, 'string')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c, false, null); - }; - } - - if (is(cmd_func, 'object')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c[0], c[1], c[2]); - }; - } - - each(explode(pa), function(pa) { - var o = { - func : cmd_func, - scope : sc || this, - desc : desc, - alt : false, - ctrl : false, - shift : false - }; - - each(explode(pa, '+'), function(v) { - switch (v) { - case 'alt': - case 'ctrl': - case 'shift': - o[v] = true; - break; - - default: - o.charCode = v.charCodeAt(0); - o.keyCode = v.toUpperCase().charCodeAt(0); - } - }); - - t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; - }); - - return true; - }, - - /** - * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or - * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. - * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these - * return true it will handle the command as a internal browser command. - * - * @method execCommand - * @param {String} cmd Command name to execute, for example mceLink or Bold. - * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. - * @param {mixed} val Optional command value, this can be anything. - * @param {Object} a Optional arguments object. - * @return {Boolean} True/false if the command was executed or not. - */ - execCommand : function(cmd, ui, val, a) { - var t = this, s = 0, o, st; - - if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) - t.focus(); - - o = {}; - t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); - if (o.terminate) - return false; - - // Command callback - if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Registred commands - if (o = t.execCommands[cmd]) { - st = o.func.call(o.scope, ui, val); - - // Fall through on true - if (st !== true) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return st; - } - } - - // Plugin commands - each(t.plugins, function(p) { - if (p.execCommand && p.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - s = 1; - return false; - } - }); - - if (s) - return true; - - // Theme commands - if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Execute global commands - if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Editor commands - if (t.editorCommands.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Browser commands - t.getDoc().execCommand(cmd, ui, val); - t.onExecCommand.dispatch(t, cmd, ui, val, a); - }, - - /** - * Returns a command specific state, for example if bold is enabled or not. - * - * @method queryCommandState - * @param {string} cmd Command to query state from. - * @return {Boolean} Command specific state, for example if bold is enabled or not. - */ - queryCommandState : function(cmd) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryStateCommands[cmd]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandState(cmd); - if (o !== -1) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandState(cmd); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - /** - * Returns a command specific value, for example the current font size. - * - * @method queryCommandValue - * @param {string} c Command to query value from. - * @return {Object} Command specific value, for example the current font size. - */ - queryCommandValue : function(c) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryValueCommands[c]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandValue(c); - if (is(o)) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandValue(c); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - /** - * Shows the editor and hides any textarea/div that the editor is supposed to replace. - * - * @method show - */ - show : function() { - var t = this; - - DOM.show(t.getContainer()); - DOM.hide(t.id); - t.load(); - }, - - /** - * Hides the editor and shows any textarea/div that the editor is supposed to replace. - * - * @method hide - */ - hide : function() { - var t = this, d = t.getDoc(); - - // Fixed bug where IE has a blinking cursor left from the editor - if (isIE && d) - d.execCommand('SelectAll'); - - // We must save before we hide so Safari doesn't crash - t.save(); - DOM.hide(t.getContainer()); - DOM.setStyle(t.id, 'display', t.orgDisplay); - }, - - /** - * Returns true/false if the editor is hidden or not. - * - * @method isHidden - * @return {Boolean} True/false if the editor is hidden or not. - */ - isHidden : function() { - return !DOM.isHidden(this.id); - }, - - /** - * Sets the progress state, this will display a throbber/progess for the editor. - * This is ideal for asycronous operations like an AJAX save call. - * - * @method setProgressState - * @param {Boolean} b Boolean state if the progress should be shown or hidden. - * @param {Number} ti Optional time to wait before the progress gets shown. - * @param {Object} o Optional object to pass to the progress observers. - * @return {Boolean} Same as the input state. - */ - setProgressState : function(b, ti, o) { - this.onSetProgressState.dispatch(this, b, ti, o); - - return b; - }, - - /** - * Loads contents from the textarea or div element that got converted into an editor instance. - * This method will move the contents from that textarea or div into the editor by using setContent - * so all events etc that method has will get dispatched as well. - * - * @method load - * @param {Object} o Optional content object, this gets passed around through the whole load process. - * @return {String} HTML string that got set into the editor. - */ - load : function(o) { - var t = this, e = t.getElement(), h; - - if (e) { - o = o || {}; - o.load = true; - - // Double encode existing entities in the value - h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); - o.element = e; - - if (!o.no_events) - t.onLoadContent.dispatch(t, o); - - o.element = e = null; - - return h; - } - }, - - /** - * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. - * This method will move the HTML contents from the editor into that textarea or div by getContent - * so all events etc that method has will get dispatched as well. - * - * @method save - * @param {Object} o Optional content object, this gets passed around through the whole save process. - * @return {String} HTML string that got set into the textarea/div. - */ - save : function(o) { - var t = this, e = t.getElement(), h, f; - - if (!e || !t.initialized) - return; - - o = o || {}; - o.save = true; - - // Add undo level will trigger onchange event - if (!o.no_events) { - t.undoManager.typing = 0; - t.undoManager.add(); - } - - o.element = e; - h = o.content = t.getContent(o); - - if (!o.no_events) - t.onSaveContent.dispatch(t, o); - - h = o.content; - - if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { - e.innerHTML = h; - - // Update hidden form element - if (f = DOM.getParent(t.id, 'form')) { - each(f.elements, function(e) { - if (e.name == t.id) { - e.value = h; - return false; - } - }); - } - } else - e.value = h; - - o.element = e = null; - - return h; - }, - - /** - * Sets the specified content to the editor instance, this will cleanup the content before it gets set using - * the different cleanup rules options. - * - * @method setContent - * @param {String} h Content to set to editor, normally HTML contents but can be other formats as well. - * @param {Object} o Optional content object, this gets passed around through the whole set process. - * @return {String} HTML string that got set into the editor. - */ - setContent : function(h, o) { - var t = this; - - o = o || {}; - o.format = o.format || 'html'; - o.set = true; - o.content = h; - - if (!o.no_events) - t.onBeforeSetContent.dispatch(t, o); - - // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content - // It will also be impossible to place the caret in the editor unless there is a BR element present - if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { - o.content = t.dom.setHTML(t.getBody(), '
    '); - o.format = 'raw'; - } - - o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); - } - - if (!o.no_events) - t.onSetContent.dispatch(t, o); - - return o.content; - }, - - /** - * Gets the content from the editor instance, this will cleanup the content before it gets returned using - * the different cleanup rules options. - * - * @method getContent - * @param {Object} o Optional content object, this gets passed around through the whole get process. - * @return {String} Cleaned content string, normally HTML contents. - */ - getContent : function(o) { - var t = this, h; - - o = o || {}; - o.format = o.format || 'html'; - o.get = true; - - if (!o.no_events) - t.onBeforeGetContent.dispatch(t, o); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - h = t.serializer.serialize(t.getBody(), o); - } else - h = t.getBody().innerHTML; - - h = h.replace(/^\s*|\s*$/g, ''); - o.content = h; - - if (!o.no_events) - t.onGetContent.dispatch(t, o); - - return o.content; - }, - - /** - * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. - * - * @method isDirty - * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. - */ - isDirty : function() { - var t = this; - - return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; - }, - - /** - * Returns the editors container element. The container element wrappes in - * all the elements added to the page for the editor. Such as UI, iframe etc. - * - * @method getContainer - * @return {Element} HTML DOM element for the editor container. - */ - getContainer : function() { - var t = this; - - if (!t.container) - t.container = DOM.get(t.editorContainer || t.id + '_parent'); - - return t.container; - }, - - /** - * Returns the editors content area container element. The this element is the one who - * holds the iframe or the editable element. - * - * @method getContentAreaContainer - * @return {Element} HTML DOM element for the editor area container. - */ - getContentAreaContainer : function() { - return this.contentAreaContainer; - }, - - /** - * Returns the target element/textarea that got replaced with a TinyMCE editor instance. - * - * @method getElement - * @return {Element} HTML DOM element for the replaced element. - */ - getElement : function() { - return DOM.get(this.settings.content_element || this.id); - }, - - /** - * Returns the iframes window object. - * - * @method getWin - * @return {Window} Iframe DOM window object. - */ - getWin : function() { - var t = this, e; - - if (!t.contentWindow) { - e = DOM.get(t.id + "_ifr"); - - if (e) - t.contentWindow = e.contentWindow; - } - - return t.contentWindow; - }, - - /** - * Returns the iframes document object. - * - * @method getDoc - * @return {Document} Iframe DOM document object. - */ - getDoc : function() { - var t = this, w; - - if (!t.contentDocument) { - w = t.getWin(); - - if (w) - t.contentDocument = w.document; - } - - return t.contentDocument; - }, - - /** - * Returns the iframes body element. - * - * @method getBody - * @return {Element} Iframe body element. - */ - getBody : function() { - return this.bodyElement || this.getDoc().body; - }, - - /** - * URL converter function this gets executed each time a user adds an img, a or - * any other element that has a URL in it. This will be called both by the DOM and HTML - * manipulation functions. - * - * @method convertURL - * @param {string} u URL to convert. - * @param {string} n Attribute name src, href etc. - * @param {string/HTMLElement} Tag name or HTML DOM element depending on HTML or DOM insert. - * @return {string} Converted URL string. - */ - convertURL : function(u, n, e) { - var t = this, s = t.settings; - - // Use callback instead - if (s.urlconverter_callback) - return t.execCallback('urlconverter_callback', u, e, true, n); - - // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs - if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) - return u; - - // Convert to relative - if (s.relative_urls) - return t.documentBaseURI.toRelative(u); - - // Convert to absolute - u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); - - return u; - }, - - /** - * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. - * - * @method addVisual - * @param {Element} e Optional root element to loop though to find tables etc that needs the visual aid. - */ - addVisual : function(e) { - var t = this, s = t.settings; - - e = e || t.getBody(); - - if (!is(t.hasVisual)) - t.hasVisual = s.visual; - - each(t.dom.select('table,a', e), function(e) { - var v; - - switch (e.nodeName) { - case 'TABLE': - v = t.dom.getAttrib(e, 'border'); - - if (!v || v == '0') { - if (t.hasVisual) - t.dom.addClass(e, s.visual_table_class); - else - t.dom.removeClass(e, s.visual_table_class); - } - - return; - - case 'A': - v = t.dom.getAttrib(e, 'name'); - - if (v) { - if (t.hasVisual) - t.dom.addClass(e, 'mceItemAnchor'); - else - t.dom.removeClass(e, 'mceItemAnchor'); - } - - return; - } - }); - - t.onVisualAid.dispatch(t, e, t.hasVisual); - }, - - /** - * Removes the editor from the dom and tinymce collection. - * - * @method remove - */ - remove : function() { - var t = this, e = t.getContainer(); - - t.removed = 1; // Cancels post remove event execution - t.hide(); - - t.execCallback('remove_instance_callback', t); - t.onRemove.dispatch(t); - - // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command - t.onExecCommand.listeners = []; - - tinymce.remove(t); - DOM.remove(e); - }, - - /** - * Destroys the editor instance by removing all events, element references or other resources - * that could leak memory. This method will be called automatically when the page is unloaded - * but you can also call it directly if you know what you are doing. - * - * @method destroy - * @param {Boolean} s Optional state if the destroy is an automatic destroy or user called one. - */ - destroy : function(s) { - var t = this; - - // One time is enough - if (t.destroyed) - return; - - if (!s) { - tinymce.removeUnload(t.destroy); - tinyMCE.onBeforeUnload.remove(t._beforeUnload); - - // Manual destroy - if (t.theme && t.theme.destroy) - t.theme.destroy(); - - // Destroy controls, selection and dom - t.controlManager.destroy(); - t.selection.destroy(); - t.dom.destroy(); - - // Remove all events - - // Don't clear the window or document if content editable - // is enabled since other instances might still be present - if (!t.settings.content_editable) { - Event.clear(t.getWin()); - Event.clear(t.getDoc()); - } - - Event.clear(t.getBody()); - Event.clear(t.formElement); - } - - if (t.formElement) { - t.formElement.submit = t.formElement._mceOldSubmit; - t.formElement._mceOldSubmit = null; - } - - t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; - - if (t.selection) - t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; - - t.destroyed = 1; - }, - - // Internal functions - - _addEvents : function() { - // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset - var t = this, i, s = t.settings, lo = { - mouseup : 'onMouseUp', - mousedown : 'onMouseDown', - click : 'onClick', - keyup : 'onKeyUp', - keydown : 'onKeyDown', - keypress : 'onKeyPress', - submit : 'onSubmit', - reset : 'onReset', - contextmenu : 'onContextMenu', - dblclick : 'onDblClick', - paste : 'onPaste' // Doesn't work in all browsers yet - }; - - function eventHandler(e, o) { - var ty = e.type; - - // Don't fire events when it's removed - if (t.removed) - return; - - // Generic event handler - if (t.onEvent.dispatch(t, e, o) !== false) { - // Specific event handler - t[lo[e.fakeType || e.type]].dispatch(t, e, o); - } - }; - - // Add DOM events - each(lo, function(v, k) { - switch (k) { - case 'contextmenu': - if (tinymce.isOpera) { - // Fake contextmenu on Opera - t.dom.bind(t.getBody(), 'mousedown', function(e) { - if (e.ctrlKey) { - e.fakeType = 'contextmenu'; - eventHandler(e); - } - }); - } else - t.dom.bind(t.getBody(), k, eventHandler); - break; - - case 'paste': - t.dom.bind(t.getBody(), k, function(e) { - eventHandler(e); - }); - break; - - case 'submit': - case 'reset': - t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); - break; - - default: - t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); - } - }); - - t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { - t.focus(true); - }); - - // #ifdef contentEditable - - if (s.content_editable && tinymce.isOpera) { - // Opera doesn't support focus event for contentEditable elements so we need to fake it - function doFocus(e) { - t.focus(true); - }; - - t.dom.bind(t.getBody(), 'click', doFocus); - t.dom.bind(t.getBody(), 'keydown', doFocus); - } - - // #endif - - // Fixes bug where a specified document_base_uri could result in broken images - // This will also fix drag drop of images in Gecko - if (tinymce.isGecko) { - // Convert all images to absolute URLs -/* t.onSetContent.add(function(ed, o) { - each(ed.dom.select('img'), function(e) { - var v; - - if (v = e.getAttribute('_mce_src')) - e.src = t.documentBaseURI.toAbsolute(v); - }) - });*/ - - t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { - var v; - - e = e.target; - - if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) - e.src = t.documentBaseURI.toAbsolute(v); - }); - } - - // Set various midas options in Gecko - if (isGecko) { - function setOpts() { - var t = this, d = t.getDoc(), s = t.settings; - - if (isGecko && !s.readonly) { - if (t._isHidden()) { - try { - if (!s.content_editable) - d.designMode = 'On'; - } catch (ex) { - // Fails if it's hidden - } - } - - try { - // Try new Gecko method - d.execCommand("styleWithCSS", 0, false); - } catch (ex) { - // Use old method - if (!t._isHidden()) - try {d.execCommand("useCSS", 0, true);} catch (ex) {} - } - - if (!s.table_inline_editing) - try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} - - if (!s.object_resizing) - try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} - } - }; - - t.onBeforeExecCommand.add(setOpts); - t.onMouseDown.add(setOpts); - } - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - // This also fixes so it's possible to select mceItemAnchors - if (tinymce.isWebKit) { - t.onClick.add(function(ed, e) { - e = e.target; - - // Needs tobe the setBaseAndExtend or it will fail to select floated images - if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor'))) - t.selection.getSel().setBaseAndExtent(e, 0, e, 1); - }); - } - - // Add node change handlers - t.onMouseUp.add(t.nodeChanged); - t.onClick.add(t.nodeChanged); - t.onKeyUp.add(function(ed, e) { - var c = e.keyCode; - - if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) - t.nodeChanged(); - }); - - // Add reset handler - t.onReset.add(function() { - t.setContent(t.startContent, {format : 'raw'}); - }); - - // Add shortcuts - if (s.custom_shortcuts) { - if (s.custom_undo_redo_keyboard_shortcuts) { - t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); - t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); - } - - // Add default shortcuts for gecko - if (isGecko) { - t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); - t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); - t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); - } - - // BlockFormat shortcuts keys - for (i=1; i<=6; i++) - t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); - - t.addShortcut('ctrl+7', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+8', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+9', '', ['FormatBlock', false, '
    ']); - - function find(e) { - var v = null; - - if (!e.altKey && !e.ctrlKey && !e.metaKey) - return v; - - each(t.shortcuts, function(o) { - if (tinymce.isMac && o.ctrl != e.metaKey) - return; - else if (!tinymce.isMac && o.ctrl != e.ctrlKey) - return; - - if (o.alt != e.altKey) - return; - - if (o.shift != e.shiftKey) - return; - - if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { - v = o; - return false; - } - }); - - return v; - }; - - t.onKeyUp.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyPress.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyDown.add(function(ed, e) { - var o = find(e); - - if (o) { - o.func.call(o.scope); - return Event.cancel(e); - } - }); - } - - if (tinymce.isIE) { - // Fix so resize will only update the width and height attributes not the styles of an image - // It will also block mceItemNoResize items - t.dom.bind(t.getDoc(), 'controlselect', function(e) { - var re = t.resizeInfo, cb; - - e = e.target; - - // Don't do this action for non image elements - if (e.nodeName !== 'IMG') - return; - - if (re) - t.dom.unbind(re.node, re.ev, re.cb); - - if (!t.dom.hasClass(e, 'mceItemNoResize')) { - ev = 'resizeend'; - cb = t.dom.bind(e, ev, function(e) { - var v; - - e = e.target; - - if (v = t.dom.getStyle(e, 'width')) { - t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'width', ''); - } - - if (v = t.dom.getStyle(e, 'height')) { - t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'height', ''); - } - }); - } else { - ev = 'resizestart'; - cb = t.dom.bind(e, 'resizestart', Event.cancel, Event); - } - - re = t.resizeInfo = { - node : e, - ev : ev, - cb : cb - }; - }); - - t.onKeyDown.add(function(ed, e) { - switch (e.keyCode) { - case 8: - // Fix IE control + backspace browser bug - if (t.selection.getRng().item) { - ed.dom.remove(t.selection.getRng().item(0)); - return Event.cancel(e); - } - } - }); - - /*if (t.dom.boxModel) { - t.getBody().style.height = '100%'; - - Event.add(t.getWin(), 'resize', function(e) { - var docElm = t.getDoc().documentElement; - - docElm.style.height = (docElm.offsetHeight - 10) + 'px'; - }); - }*/ - } - - if (tinymce.isOpera) { - t.onClick.add(function(ed, e) { - Event.prevent(e); - }); - } - - // Add custom undo/redo handlers - if (s.custom_undo_redo) { - function addUndo() { - t.undoManager.typing = 0; - t.undoManager.add(); - }; - - t.dom.bind(t.getDoc(), 'focusout', function(e) { - if (!t.removed && t.undoManager.typing) - addUndo(); - }); - - t.onKeyUp.add(function(ed, e) { - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) - addUndo(); - }); - - t.onKeyDown.add(function(ed, e) { - // Is caracter positon keys - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { - if (t.undoManager.typing) - addUndo(); - - return; - } - - if (!t.undoManager.typing) { - t.undoManager.add(); - t.undoManager.typing = 1; - } - }); - - t.onMouseDown.add(function() { - if (t.undoManager.typing) - addUndo(); - }); - } - }, - - _isHidden : function() { - var s; - - if (!isGecko) - return 0; - - // Weird, wheres that cursor selection? - s = this.selection.getSel(); - return (!s || !s.rangeCount || s.rangeCount == 0); - }, - - // Fix for bug #1867292 - _fixNesting : function(s) { - var d = [], i; - - s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { - var e; - - // Handle end element - if (b === '/') { - if (!d.length) - return ''; - - if (c !== d[d.length - 1].tag) { - for (i=d.length - 1; i>=0; i--) { - if (d[i].tag === c) { - d[i].close = 1; - break; - } - } - - return ''; - } else { - d.pop(); - - if (d.length && d[d.length - 1].close) { - a = a + ''; - d.pop(); - } - } - } else { - // Ignore these - if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) - return a; - - // Ignore closed ones - if (/\/>$/.test(a)) - return a; - - d.push({tag : c}); // Push start element - } - - return a; - }); - - // End all open tags - for (i=d.length - 1; i>=0; i--) - s += ''; - - return s; - } - }); -})(tinymce); +/** + * Editor.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Shorten these names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, + Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko, + isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; + + /** + * This class contains the core logic for a TinyMCE editor. + * + * @class tinymce.Editor + * @example + * // Add a class to all paragraphs in the editor. + * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass'); + * + * // Gets the current editors selection as text + * tinyMCE.activeEditor.selection.getContent({format : 'text'}); + * + * // Creates a new editor instance + * var ed = new tinymce.Editor('textareaid', { + * some_setting : 1 + * }); + * + * // Select each item the user clicks on + * ed.onClick.add(function(ed, e) { + * ed.selection.select(e.target); + * }); + * + * ed.render(); + */ + tinymce.create('tinymce.Editor', { + /** + * Constructs a editor instance by id. + * + * @constructor + * @method Editor + * @param {String} id Unique id for the editor. + * @param {Object} s Optional settings string for the editor. + * @author Moxiecode + */ + Editor : function(id, s) { + var t = this; + + /** + * Editor instance id, normally the same as the div/textarea that was replaced. + * + * @property id + * @type String + */ + t.id = t.editorId = id; + + t.execCommands = {}; + t.queryStateCommands = {}; + t.queryValueCommands = {}; + + /** + * State to force the editor to return false on a isDirty call. + * + * @property isNotDirty + * @type Boolean + * @example + * function ajaxSave() { + * var ed = tinyMCE.get('elm1'); + * + * // Save contents using some XHR call + * alert(ed.getContent()); + * + * ed.isNotDirty = 1; // Force not dirty state + * } + */ + t.isNotDirty = false; + + /** + * Name/Value object containting plugin instances. + * + * @property plugins + * @type Object + * @example + * // Execute a method inside a plugin directly + * tinyMCE.activeEditor.plugins.someplugin.someMethod(); + */ + t.plugins = {}; + + // Add events to the editor + each([ + /** + * Fires before the initialization of the editor. + * + * @event onPreInit + * @param {tinymce.Editor} sender Editor instance. + * @see #onInit + * @example + * // Adds an observer to the onPreInit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPreInit.add(function(ed) { + * console.debug('PreInit: ' + ed.id); + * }); + * } + * }); + */ + 'onPreInit', + + /** + * Fires before the initialization of the editor. + * + * @event onBeforeRenderUI + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onBeforeRenderUI event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onBeforeRenderUI.add(function(ed, cm) { + * console.debug('Before render: ' + ed.id); + * }); + * } + * }); + */ + 'onBeforeRenderUI', + + /** + * Fires after the rendering has completed. + * + * @event onPostRender + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onPostRender event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPostRender.add(function(ed, cm) { + * console.debug('After render: ' + ed.id); + * }); + * } + * }); + */ + 'onPostRender', + + /** + * Fires after the initialization of the editor is done. + * + * @event onInit + * @param {tinymce.Editor} sender Editor instance. + * @see #onPreInit + * @example + * // Adds an observer to the onInit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onInit.add(function(ed) { + * console.debug('Editor is done: ' + ed.id); + * }); + * } + * }); + */ + 'onInit', + + /** + * Fires when the editor instance is removed from page. + * + * @event onRemove + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onRemove event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onRemove.add(function(ed) { + * console.debug('Editor was removed: ' + ed.id); + * }); + * } + * }); + */ + 'onRemove', + + /** + * Fires when the editor is activated. + * + * @event onActivate + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onActivate event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onActivate.add(function(ed) { + * console.debug('Editor was activated: ' + ed.id); + * }); + * } + * }); + */ + 'onActivate', + + /** + * Fires when the editor is deactivated. + * + * @event onDeactivate + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onDeactivate event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onDeactivate.add(function(ed) { + * console.debug('Editor was deactivated: ' + ed.id); + * }); + * } + * }); + */ + 'onDeactivate', + + /** + * Fires when something in the body of the editor is clicked. + * + * @event onClick + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onClick event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onClick.add(function(ed, e) { + * console.debug('Editor was clicked: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onClick', + + /** + * Fires when a registered event is intercepted. + * + * @event onEvent + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onEvent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onEvent.add(function(ed, e) { + * console.debug('Editor event occured: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onEvent', + + /** + * Fires when a mouseup event is intercepted inside the editor. + * + * @event onMouseUp + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onMouseUp event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onMouseUp.add(function(ed, e) { + * console.debug('Mouse up event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onMouseUp', + + /** + * Fires when a mousedown event is intercepted inside the editor. + * + * @event onMouseDown + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onMouseDown event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onMouseDown.add(function(ed, e) { + * console.debug('Mouse down event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onMouseDown', + + /** + * Fires when a dblclick event is intercepted inside the editor. + * + * @event onDblClick + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onDblClick event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onDblClick.add(function(ed, e) { + * console.debug('Double click event: ' + e.target.nodeName); + * }); + * } + * }); + */ + 'onDblClick', + + /** + * Fires when a keydown event is intercepted inside the editor. + * + * @event onKeyDown + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyDown event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyDown.add(function(ed, e) { + * console.debug('Key down event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyDown', + + /** + * Fires when a keydown event is intercepted inside the editor. + * + * @event onKeyUp + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyUp event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyUp.add(function(ed, e) { + * console.debug('Key up event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyUp', + + /** + * Fires when a keypress event is intercepted inside the editor. + * + * @event onKeyPress + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onKeyPress event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onKeyPress.add(function(ed, e) { + * console.debug('Key press event: ' + e.keyCode); + * }); + * } + * }); + */ + 'onKeyPress', + + /** + * Fires when a contextmenu event is intercepted inside the editor. + * + * @event onContextMenu + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onContextMenu event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onContextMenu.add(function(ed, e) { + * console.debug('Context menu event:' + e.target); + * }); + * } + * }); + */ + 'onContextMenu', + + /** + * Fires when a form submit event is intercepted. + * + * @event onSubmit + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onSubmit event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onSubmit.add(function(ed, e) { + * console.debug('Form submit:' + e.target); + * }); + * } + * }); + */ + 'onSubmit', + + /** + * Fires when a form reset event is intercepted. + * + * @event onReset + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onReset event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onReset.add(function(ed, e) { + * console.debug('Form reset:' + e.target); + * }); + * } + * }); + */ + 'onReset', + + /** + * Fires when a paste event is intercepted inside the editor. + * + * @event onPaste + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onPaste event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPaste.add(function(ed, e) { + * console.debug('Pasted plain text'); + * }); + * } + * }); + */ + 'onPaste', + + /** + * Fires when the Serializer does a preProcess on the contents. + * + * @event onPreProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + * @option {Node} node DOM node for the item being serialized. + * @option {String} format The specified output format normally "html". + * @option {Boolean} get Is true if the process is on a getContent operation. + * @option {Boolean} set Is true if the process is on a setContent operation. + * @option {Boolean} cleanup Is true if the process is on a cleanup operation. + * @example + * // Adds an observer to the onPreProcess event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPreProcess.add(function(ed, o) { + * // Add a class to each paragraph in the editor + * ed.dom.addClass(ed.dom.select('p', o.node), 'myclass'); + * }); + * } + * }); + */ + 'onPreProcess', + + /** + * Fires when the Serializer does a postProcess on the contents. + * + * @event onPostProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + * @example + * // Adds an observer to the onPostProcess event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onPostProcess.add(function(ed, o) { + * // Remove all paragraphs and replace with BR + * o.content = o.content.replace(/]+>|

    /g, ''); + * o.content = o.content.replace(/<\/p>/g, '
    '); + * }); + * } + * }); + */ + 'onPostProcess', + + /** + * Fires before new contents is added to the editor. Using for example setContent. + * + * @event onBeforeSetContent + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onBeforeSetContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onBeforeSetContent.add(function(ed, o) { + * // Replaces all a characters with b characters + * o.content = o.content.replace(/a/g, 'b'); + * }); + * } + * }); + */ + 'onBeforeSetContent', + + /** + * Fires before contents is extracted from the editor using for example getContent. + * + * @event onBeforeGetContent + * @param {tinymce.Editor} sender Editor instance. + * @param {Event} evt W3C DOM Event instance. + * @example + * // Adds an observer to the onBeforeGetContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onBeforeGetContent.add(function(ed, o) { + * console.debug('Before get content.'); + * }); + * } + * }); + */ + 'onBeforeGetContent', + + /** + * Fires after the contents has been added to the editor using for example onSetContent. + * + * @event onSetContent + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onSetContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onSetContent.add(function(ed, o) { + * // Replaces all a characters with b characters + * o.content = o.content.replace(/a/g, 'b'); + * }); + * } + * }); + */ + 'onSetContent', + + /** + * Fires after the contents has been extracted from the editor using for example getContent. + * + * @event onGetContent + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onGetContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onGetContent.add(function(ed, o) { + * // Replace all a characters with b + * o.content = o.content.replace(/a/g, 'b'); + * }); + * } + * }); + */ + 'onGetContent', + + /** + * Fires when the editor gets loaded with contents for example when the load method is executed. + * + * @event onLoadContent + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onLoadContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onLoadContent.add(function(ed, o) { + * // Output the element name + * console.debug(o.element.nodeName); + * }); + * } + * }); + */ + 'onLoadContent', + + /** + * Fires when the editor contents gets saved for example when the save method is executed. + * + * @event onSaveContent + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onSaveContent event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onSaveContent.add(function(ed, o) { + * // Output the element name + * console.debug(o.element.nodeName); + * }); + * } + * }); + */ + 'onSaveContent', + + /** + * Fires when the user changes node location using the mouse or keyboard. + * + * @event onNodeChange + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onNodeChange event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onNodeChange.add(function(ed, cm, e) { + * // Activates the link button when the caret is placed in a anchor element + * if (e.nodeName == 'A') + * cm.setActive('link', true); + * }); + * } + * }); + */ + 'onNodeChange', + + /** + * Fires when a new undo level is added to the editor. + * + * @event onChange + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onChange event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onChange.add(function(ed, l) { + * console.debug('Editor contents was modified. Contents: ' + l.content); + * }); + * } + * }); + */ + 'onChange', + + /** + * Fires before a command gets executed for example "Bold". + * + * @event onBeforeExecCommand + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onBeforeExecCommand event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onBeforeExecCommand.add(function(ed, cmd, ui, val) { + * console.debug('Command is to be executed: ' + cmd); + * }); + * } + * }); + */ + 'onBeforeExecCommand', + + /** + * Fires after a command is executed for example "Bold". + * + * @event onExecCommand + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onExecCommand event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onExecCommand.add(function(ed, cmd, ui, val) { + * console.debug('Command was executed: ' + cmd); + * }); + * } + * }); + */ + 'onExecCommand', + + /** + * Fires when the contents is undo:ed. + * + * @event onUndo + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} level Undo level object. + * @ example + * // Adds an observer to the onUndo event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onUndo.add(function(ed, level) { + * console.debug('Undo was performed: ' + level.content); + * }); + * } + * }); + */ + 'onUndo', + + /** + * Fires when the contents is redo:ed. + * + * @event onRedo + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} level Undo level object. + * @example + * // Adds an observer to the onRedo event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onRedo.add(function(ed, level) { + * console.debug('Redo was performed: ' +level.content); + * }); + * } + * }); + */ + 'onRedo', + + /** + * Fires when visual aids is enabled/disabled. + * + * @event onVisualAid + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onVisualAid event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onVisualAid.add(function(ed, e, s) { + * console.debug('onVisualAid event: ' + ed.id + ", State: " + s); + * }); + * } + * }); + */ + 'onVisualAid', + + /** + * Fires when the progress throbber is shown above the editor. + * + * @event onSetProgressState + * @param {tinymce.Editor} sender Editor instance. + * @example + * // Adds an observer to the onSetProgressState event using tinyMCE.init + * tinyMCE.init({ + * ... + * setup : function(ed) { + * ed.onSetProgressState.add(function(ed, b) { + * if (b) + * console.debug('SHOW!'); + * else + * console.debug('HIDE!'); + * }); + * } + * }); + */ + 'onSetProgressState' + ], function(e) { + t[e] = new Dispatcher(t); + }); + + /** + * Name/value collection with editor settings. + * + * @property settings + * @type Object + * @example + * // Get the value of the theme setting + * tinyMCE.activeEditor.windowManager.alert("You are using the " + tinyMCE.activeEditor.settings.theme + " theme"); + */ + t.settings = s = extend({ + id : id, + language : 'en', + docs_language : 'en', + theme : 'simple', + skin : 'default', + delta_width : 0, + delta_height : 0, + popup_css : '', + plugins : '', + document_base_url : tinymce.documentBaseURL, + add_form_submit_trigger : 1, + submit_patch : 1, + add_unload_trigger : 1, + convert_urls : 1, + relative_urls : 1, + remove_script_host : 1, + table_inline_editing : 0, + object_resizing : 1, + cleanup : 1, + accessibility_focus : 1, + custom_shortcuts : 1, + custom_undo_redo_keyboard_shortcuts : 1, + custom_undo_redo_restore_selection : 1, + custom_undo_redo : 1, + doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll + visual_table_class : 'mceItemTable', + visual : 1, + font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', + font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size + apply_source_formatting : 1, + directionality : 'ltr', + forced_root_block : 'p', + hidden_input : 1, + padd_empty_editor : 1, + render_ui : 1, + init_theme : 1, + force_p_newlines : 1, + indentation : '30px', + keep_styles : 1, + fix_table_elements : 1, + inline_styles : 1, + convert_fonts_to_spans : true, + indent : 'simple', + indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + validate : true, + entity_encoding : 'named', + url_converter : t.convertURL, + url_converter_scope : t, + ie7_compat : true + }, s); + + /** + * URI object to document configured for the TinyMCE instance. + * + * @property documentBaseURI + * @type tinymce.util.URI + * @example + * // Get relative URL from the location of document_base_url + * tinyMCE.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); + * + * // Get absolute URL from the location of document_base_url + * tinyMCE.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); + */ + t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, { + base_uri : tinyMCE.baseURI + }); + + /** + * URI object to current document that holds the TinyMCE editor instance. + * + * @property baseURI + * @type tinymce.util.URI + * @example + * // Get relative URL from the location of the API + * tinyMCE.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); + * + * // Get absolute URL from the location of the API + * tinyMCE.activeEditor.baseURI.toAbsolute('somefile.htm'); + */ + t.baseURI = tinymce.baseURI; + + /** + * Array with CSS files to load into the iframe. + * + * @property contentCSS + * @type Array + */ + t.contentCSS = []; + + // Call setup + t.execCallback('setup', t); + }, + + /** + * Renderes the editor/adds it to the page. + * + * @method render + */ + render : function(nst) { + var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; + + // Page is not loaded yet, wait for it + if (!Event.domLoaded) { + Event.add(document, 'init', function() { + t.render(); + }); + return; + } + + tinyMCE.settings = s; + + // Element not found, then skip initialization + if (!t.getElement()) + return; + + // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff + // here since the browser says it has contentEditable support but there is no visible + // caret We will remove this check ones Apple implements full contentEditable support + if (tinymce.isIDevice && !tinymce.isIOS5) + return; + + // Add hidden input for non input elements inside form elements + if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) + DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); + + /** + * Window manager reference, use this to open new windows and dialogs. + * + * @property windowManager + * @type tinymce.WindowManager + * @example + * // Shows an alert message + * tinyMCE.activeEditor.windowManager.alert('Hello world!'); + * + * // Opens a new dialog with the file.htm file and the size 320x240 + * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. + * tinyMCE.activeEditor.windowManager.open({ + * url : 'file.htm', + * width : 320, + * height : 240 + * }, { + * custom_param : 1 + * }); + */ + if (tinymce.WindowManager) + t.windowManager = new tinymce.WindowManager(t); + + if (s.encoding == 'xml') { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = DOM.encode(o.content); + }); + } + + if (s.add_form_submit_trigger) { + t.onSubmit.addToTop(function() { + if (t.initialized) { + t.save(); + t.isNotDirty = 1; + } + }); + } + + if (s.add_unload_trigger) { + t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { + if (t.initialized && !t.destroyed && !t.isHidden()) + t.save({format : 'raw', no_events : true}); + }); + } + + tinymce.addUnload(t.destroy, t); + + if (s.submit_patch) { + t.onBeforeRenderUI.add(function() { + var n = t.getElement().form; + + if (!n) + return; + + // Already patched + if (n._mceOldSubmit) + return; + + // Check page uses id="submit" or name="submit" for it's submit button + if (!n.submit.nodeType && !n.submit.length) { + t.formElement = n; + n._mceOldSubmit = n.submit; + n.submit = function() { + // Save all instances + tinymce.triggerSave(); + t.isNotDirty = 1; + + return t.formElement._mceOldSubmit(t.formElement); + }; + } + + n = null; + }); + } + + // Load scripts + function loadScripts() { + if (s.language && s.language_load !== false) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + each(explode(s.plugins), function(p) { + if (p &&!PluginManager.urls[p]) { + if (p.charAt(0) == '-') { + p = p.substr(1, p.length); + var dependencies = PluginManager.dependencies(p); + each(dependencies, function(dep) { + var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; + var dep = PluginManager.createUrl(defaultSettings, dep); + PluginManager.load(dep.resource, dep); + + }); + } else { + // Skip safari plugin, since it is removed as of 3.3b1 + if (p == 'safari') { + return; + } + PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); + } + } + }); + + // Init when que is loaded + sl.loadQueue(function() { + if (!t.removed) + t.init(); + }); + }; + + loadScripts(); + }, + + /** + * Initializes the editor this will be called automatically when + * all plugins/themes and language packs are loaded by the rendered method. + * This method will setup the iframe and create the theme and plugin instances. + * + * @method init + */ + init : function() { + var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; + + tinymce.add(t); + + s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); + + /** + * Reference to the theme instance that was used to generate the UI. + * + * @property theme + * @type tinymce.Theme + * @example + * // Executes a method on the theme directly + * tinyMCE.activeEditor.theme.someMethod(); + */ + if (s.theme) { + s.theme = s.theme.replace(/-/, ''); + o = ThemeManager.get(s.theme); + t.theme = new o(); + + if (t.theme.init && s.init_theme) + t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); + } + function initPlugin(p) { + var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; + if (c && tinymce.inArray(initializedPlugins,p) === -1) { + each(PluginManager.dependencies(p), function(dep){ + initPlugin(dep); + }); + po = new c(t, u); + + t.plugins[p] = po; + + if (po.init) { + po.init(t, u); + initializedPlugins.push(p); + } + } + } + + // Create all plugins + each(explode(s.plugins.replace(/\-/g, '')), initPlugin); + + // Setup popup CSS path(s) + if (s.popup_css !== false) { + if (s.popup_css) + s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); + else + s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); + } + + if (s.popup_css_add) + s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); + + /** + * Control manager instance for the editor. Will enables you to create new UI elements and change their states etc. + * + * @property controlManager + * @type tinymce.ControlManager + * @example + * // Disables the bold button + * tinyMCE.activeEditor.controlManager.setDisabled('bold', true); + */ + t.controlManager = new tinymce.ControlManager(t); + + if (s.custom_undo_redo) { + t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.beforeChange(); + }); + + t.onExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.add(); + }); + } + + t.onExecCommand.add(function(ed, c) { + // Don't refresh the select lists until caret move + if (!/^(FontName|FontSize)$/.test(c)) + t.nodeChanged(); + }); + + // Remove ghost selections on images and tables in Gecko + if (isGecko) { + function repaint(a, o) { + if (!o || !o.initial) + t.execCommand('mceRepaint'); + }; + + t.onUndo.add(repaint); + t.onRedo.add(repaint); + t.onSetContent.add(repaint); + } + + // Enables users to override the control factory + t.onBeforeRenderUI.dispatch(t, t.controlManager); + + // Measure box + if (s.render_ui) { + w = s.width || e.style.width || e.offsetWidth; + h = s.height || e.style.height || e.offsetHeight; + t.orgDisplay = e.style.display; + re = /^[0-9\.]+(|px)$/i; + + if (re.test('' + w)) + w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100); + + if (re.test('' + h)) + h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100); + + // Render UI + o = t.theme.renderUI({ + targetNode : e, + width : w, + height : h, + deltaWidth : s.delta_width, + deltaHeight : s.delta_height + }); + + t.editorContainer = o.editorContainer; + } + + // #ifdef contentEditable + + // Content editable mode ends here + if (s.content_editable) { + e = n = o = null; // Fix IE leak + return t.setupContentEditable(); + } + + // #endif + + // User specified a document.domain value + if (document.domain && location.hostname != document.domain) + tinymce.relaxedDomain = document.domain; + + // Resize editor + DOM.setStyles(o.sizeContainer || o.editorContainer, { + width : w, + height : h + }); + + // Load specified content CSS last + if (s.content_css) { + tinymce.each(explode(s.content_css), function(u) { + t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); + }); + } + + h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); + if (h < 100) + h = 100; + + t.iframeHTML = s.doctype + ''; + + // We only need to override paths if we have to + // IE has a bug where it remove site absolute urls to relative ones if this is specified + if (s.document_base_url != tinymce.documentBaseURL) + t.iframeHTML += ''; + + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + + t.iframeHTML += ''; + + // Load the CSS by injecting them into the HTML this will reduce "flicker" + for (i = 0; i < t.contentCSS.length; i++) { + t.iframeHTML += ''; + } + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += '
    '; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; + } + + // Create iframe + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + allowTransparency : "true", + title : s.aria_label, + style : { + width : '100%', + height : h, + display : 'block' // Important for Gecko to render the iframe correctly + } + }); + + t.contentAreaContainer = o.iframeContainer; + DOM.get(o.editorContainer).style.display = t.orgDisplay; + DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); + + if (!tinymce.relaxedDomain || !u) + t.setupIframe(); + + e = n = o = null; // Cleanup + }, + + /** + * This method get called by the init method ones the iframe is loaded. + * It will fill the iframe with contents, setups DOM and selection objects for the iframe. + * This method should not be called directly. + * + * @method setupIframe + */ + setupIframe : function() { + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; + + // Setup iframe body + if (!isIE || !tinymce.relaxedDomain) { + d.open(); + d.write(t.iframeHTML); + d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; + } + + // It will not steal focus while setting contentEditable + b = t.getBody(); + b.disabled = true; + + if (!s.readonly) + b.contentEditable = true; + + b.disabled = false; + + /** + * Schema instance, enables you to validate elements and it's children. + * + * @property schema + * @type tinymce.html.Schema + */ + t.schema = new tinymce.html.Schema(s); + + /** + * DOM instance for the editor. + * + * @property dom + * @type tinymce.dom.DOMUtils + * @example + * // Adds a class to all paragraphs within the editor + * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass'); + */ + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + update_styles : 1, + fix_ie_paragraphs : 1, + schema : t.schema + }); + + /** + * HTML parser will be used when contents is inserted into the editor. + * + * @property parser + * @type tinymce.html.DomParser + */ + t.parser = new tinymce.html.DomParser(s, t.schema); + + // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. + if (!t.settings.allow_html_in_named_anchor) { + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + } + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + if (name === "style") + node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr(internalName, t.convertURL(value, name, node.name)); + } + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); + } + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + /** + * DOM serializer for the editor. Will be used when contents is extracted from the editor. + * + * @property serializer + * @type tinymce.dom.Serializer + * @example + * // Serializes the first paragraph in the editor into a string + * tinyMCE.activeEditor.serializer.serialize(tinyMCE.activeEditor.dom.select('p')[0]); + */ + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); + + /** + * Selection instance for the editor. + * + * @property selection + * @type tinymce.dom.Selection + * @example + * // Sets some contents to the current selection in the editor + * tinyMCE.activeEditor.selection.setContent('Some contents'); + * + * // Gets the current selection + * alert(tinyMCE.activeEditor.selection.getContent()); + * + * // Selects the first paragraph found + * tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]); + */ + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + + /** + * Formatter instance. + * + * @property formatter + * @type tinymce.Formatter + */ + t.formatter = new tinymce.Formatter(this); + + // Register default formats + t.formatter.register({ + alignleft : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} + ], + + bold : [ + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b', remove : 'all'} + ], + + italic : [ + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i', remove : 'all'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u', remove : 'all'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'strike', remove : 'all'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, + + link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, + onmatch : function(node) { + return true; + }, + + onformat : function(elm, fmt, vars) { + each(vars, function(value, key) { + t.dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + t.formatter.register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + t.formatter.register(t.settings.formats); + + /** + * Undo manager instance, responsible for handling undo levels. + * + * @property undoManager + * @type tinymce.UndoManager + * @example + * // Undoes the last modification to the editor + * tinyMCE.activeEditor.undoManager.undo(); + */ + t.undoManager = new tinymce.UndoManager(t); + + // Pass through + t.undoManager.onAdd.add(function(um, l) { + if (um.hasUndo()) + return t.onChange.dispatch(t, l, um); + }); + + t.undoManager.onUndo.add(function(um, l) { + return t.onUndo.dispatch(t, l, um); + }); + + t.undoManager.onRedo.add(function(um, l) { + return t.onRedo.dispatch(t, l, um); + }); + + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + + if (!s.gecko_spellcheck) + t.getBody().spellcheck = 0; + + if (!s.readonly) + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + t.quirks = new tinymce.util.Quirks(this); + + if (s.directionality) + t.getBody().dir = s.directionality; + + if (s.nowrap) + t.getBody().style.whiteSpace = "nowrap"; + + if (s.handle_node_change_callback) { + t.onNodeChange.add(function(ed, cm, n) { + t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); + }); + } + + if (s.save_callback) { + t.onSaveContent.add(function(ed, o) { + var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); + + if (h) + o.content = h; + }); + } + + if (s.onchange_callback) { + t.onChange.add(function(ed, l) { + t.execCallback('onchange_callback', t, l); + }); + } + + if (s.protect) { + t.onBeforeSetContent.add(function(ed, o) { + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } + }); + } + + if (s.convert_newlines_to_brs) { + t.onBeforeSetContent.add(function(ed, o) { + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
    '); + }); + } + + if (s.preformatted) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^\s*/, ''); + o.content = o.content.replace(/<\/pre>\s*$/, ''); + + if (o.set) + o.content = '

    ' + o.content + '
    '; + }); + } + + if (s.verify_css_classes) { + t.serializer.attribValueFilter = function(n, v) { + var s, cl; + + if (n == 'class') { + // Build regexp for classes + if (!t.classesRE) { + cl = t.dom.getClasses(); + + if (cl.length > 0) { + s = ''; + + each (cl, function(o) { + s += (s ? '|' : '') + o['class']; + }); + + t.classesRE = new RegExp('(' + s + ')', 'gi'); + } + } + + return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; + } + + return v; + }; + } + + if (s.cleanup_callback) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + t.onPreProcess.add(function(ed, o) { + if (o.set) + t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + t.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + if (s.save_callback) { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); + }); + } + + if (s.handle_event_callback) { + t.onEvent.add(function(ed, e, o) { + if (t.execCallback('handle_event_callback', e, ed, o) === false) + Event.cancel(e); + }); + } + + // Add visual aids when new contents is added + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + // Remove empty contents + if (s.padd_empty_editor) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); + }); + } + + if (isGecko) { + // Fix gecko link bug, when a link is placed at the end of block elements there is + // no way to move the caret behind the link. This fix adds a bogus br element after the link + function fixLinks(ed, o) { + each(ed.dom.select('a'), function(n) { + var pn = n.parentNode; + + if (ed.dom.isBlock(pn) && pn.lastChild === n) + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); + }); + }; + + t.onExecCommand.add(function(ed, cmd) { + if (cmd === 'CreateLink') + fixLinks(ed); + }); + + t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); + } + + t.load({initial : true, format : 'html'}); + t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); + t.initialized = true; + + t.onInit.dispatch(t); + t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); + t.execCallback('init_instance_callback', t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); + + // Handle auto focus + if (s.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(s.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getBody().focus(); + ed.getWin().focus(); + }, 100); + } + + e = null; + }, + + // #ifdef contentEditable + + /** + * Sets up the contentEditable mode. + * + * @method setupContentEditable + */ + setupContentEditable : function() { + var t = this, s = t.settings, e = t.getElement(); + + t.contentDocument = s.content_document || document; + t.contentWindow = s.content_window || window; + t.bodyElement = e; + + // Prevent leak in IE + s.content_document = s.content_window = null; + + DOM.hide(e); + e.contentEditable = t.getParam('content_editable_state', true); + DOM.show(e); + + if (!s.gecko_spellcheck) + t.getDoc().body.spellcheck = 0; + + // Setup objects + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + root_element : t.id, + fix_ie_paragraphs : 1, + update_styles : 1 + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, schema); + + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + //t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); + t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add({initial : true}); + t.initialized = true; + + t.onInit.dispatch(t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + if (s.content_css) { + each(explode(s.content_css), function(u) { + t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); + }); + } + + if (isIE) { + // Store away selection + t.dom.bind(t.getElement(), 'beforedeactivate', function() { + t.lastSelectionBookmark = t.selection.getBookmark(1); + }); + + t.onBeforeExecCommand.add(function(ed, cmd, ui, val, o) { + if (!DOM.getParent(ed.selection.getStart(), function(n) {return n == ed.getBody();})) + o.terminate = 1; + + if (!DOM.getParent(ed.selection.getEnd(), function(n) {return n == ed.getBody();})) + o.terminate = 1; + }); + } + + e = null; // Cleanup + }, + + // #endif + + /** + * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection + * it will also place DOM focus inside the editor. + * + * @method focus + * @param {Boolean} sf Skip DOM focus. Just set is as the active editor. + */ + focus : function(sf) { + var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); + + if (!sf) { + // Get selected control element + ieRng = selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + t._refreshContentEditable(); + selection.normalize(); + + // Is not content editable + if (!ce) + t.getWin().focus(); + + // Focus the body as well since it's contentEditable + if (tinymce.isGecko) { + t.getBody().focus(); + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + + // #ifdef contentEditable + + // Content editable mode ends here + if (ce) { + if (tinymce.isWebKit) + t.getWin().focus(); + else { + if (tinymce.isIE) + t.getElement().setActive(); + else + t.getElement().focus(); + } + } + + // #endif + } + + if (tinymce.activeEditor != t) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + tinymce._setActive(t); + }, + + /** + * Executes a legacy callback. This method is useful to call old 2.x option callbacks. + * There new event model is a better way to add callback so this method might be removed in the future. + * + * @method execCallback + * @param {String} n Name of the callback to execute. + * @return {Object} Return value passed from callback function. + */ + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + /** + * Translates the specified string by replacing variables with language pack items it will also check if there is + * a key mathcin the input. + * + * @method translate + * @param {String} s String to translate by the language pack data. + * @return {String} Translated string. + */ + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + /** + * Returns a language pack item by name/key. + * + * @method getLang + * @param {String} n Name/key to get from the language pack. + * @param {String} dv Optional default value to retrive. + */ + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + /** + * Returns a configuration parameter by name. + * + * @method getParam + * @param {String} n Configruation parameter to retrive. + * @param {String} dv Optional default value to return. + * @param {String} ty Optional type parameter. + * @return {String} Configuration parameter value or default value. + * @example + * // Returns a specific config value from the currently active editor + * var someval = tinyMCE.activeEditor.getParam('myvalue'); + * + * // Returns a specific config value from a specific editor instance by id + * var someval2 = tinyMCE.get('my_editor').getParam('myvalue'); + */ + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + /** + * Distpaches out a onNodeChange event to all observers. This method should be called when you + * need to update the UI states or element path etc. + * + * @method nodeChanged + * @param {Object} o Optional object to pass along for the node changed event. + */ + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getStart() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + o = o || {}; + n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + t.dom.getParent(n, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + n, + s.isCollapsed(), + o + ); + } + }, + + /** + * Adds a button that later gets created by the ControlManager. This is a shorter and easier method + * of adding buttons without the need to deal with the ControlManager directly. But it's also less + * powerfull if you need more control use the ControlManagers factory methods instead. + * + * @method addButton + * @param {String} n Button name to add. + * @param {Object} s Settings object with title, cmd etc. + * @example + * // Adds a custom button to the editor and when a user clicks the button it will open + * // an alert box with the selected contents as plain text. + * tinyMCE.init({ + * ... + * + * theme_advanced_buttons1 : 'example,..' + * + * setup : function(ed) { + * // Register example button + * ed.addButton('example', { + * title : 'example.desc', + * image : '../jscripts/tiny_mce/plugins/example/img/example.gif', + * onclick : function() { + * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format : 'text'})); + * } + * }); + * } + * }); + */ + addButton : function(n, s) { + var t = this; + + t.buttons = t.buttons || {}; + t.buttons[n] = s; + }, + + /** + * Adds a custom command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with execCommand. + * + * @method addCommand + * @param {String} name Command name to add/override. + * @param {addCommandCallback} callback Function to execute when the command occurs. + * @param {Object} scope Optional scope to execute the function in. + * @example + * // Adds a custom command that later can be executed using execCommand + * tinyMCE.init({ + * ... + * + * setup : function(ed) { + * // Register example command + * ed.addCommand('mycommand', function(ui, v) { + * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format : 'text'})); + * }); + * } + * }); + */ + addCommand : function(name, callback, scope) { + /** + * Callback function that gets called when a command is executed. + * + * @callback addCommandCallback + * @param {Boolean} ui Display UI state true/false. + * @param {Object} value Optional value for command. + * @return {Boolean} True/false state if the command was handled or not. + */ + this.execCommands[name] = {func : callback, scope : scope || this}; + }, + + /** + * Adds a custom query state command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with queryCommandState function. + * + * @method addQueryStateHandler + * @param {String} name Command name to add/override. + * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs. + * @param {Object} scope Optional scope to execute the function in. + */ + addQueryStateHandler : function(name, callback, scope) { + /** + * Callback function that gets called when a queryCommandState is executed. + * + * @callback addQueryStateHandlerCallback + * @return {Boolean} True/false state if the command is enabled or not like is it bold. + */ + this.queryStateCommands[name] = {func : callback, scope : scope || this}; + }, + + /** + * Adds a custom query value command to the editor, you can also override existing commands with this method. + * The command that you add can be executed with queryCommandValue function. + * + * @method addQueryValueHandler + * @param {String} name Command name to add/override. + * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs. + * @param {Object} scope Optional scope to execute the function in. + */ + addQueryValueHandler : function(name, callback, scope) { + /** + * Callback function that gets called when a queryCommandValue is executed. + * + * @callback addQueryValueHandlerCallback + * @return {Object} Value of the command or undefined. + */ + this.queryValueCommands[name] = {func : callback, scope : scope || this}; + }, + + /** + * Adds a keyboard shortcut for some command or function. + * + * @method addShortcut + * @param {String} pa Shortcut pattern. Like for example: ctrl+alt+o. + * @param {String} desc Text description for the command. + * @param {String/Function} cmd_func Command name string or function to execute when the key is pressed. + * @param {Object} sc Optional scope to execute the function in. + * @return {Boolean} true/false state if the shortcut was added or not. + */ + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (!t.settings.custom_shortcuts) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : desc, + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + /** + * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or + * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. + * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these + * return true it will handle the command as a internal browser command. + * + * @method execCommand + * @param {String} cmd Command name to execute, for example mceLink or Bold. + * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. + * @param {mixed} val Optional command value, this can be anything. + * @param {Object} a Optional arguments object. + * @return {Boolean} True/false if the command was executed or not. + */ + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + o = {}; + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); + if (o.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + /** + * Returns a command specific state, for example if bold is enabled or not. + * + * @method queryCommandState + * @param {string} cmd Command to query state from. + * @return {Boolean} Command specific state, for example if bold is enabled or not. + */ + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + /** + * Returns a command specific value, for example the current font size. + * + * @method queryCommandValue + * @param {string} c Command to query value from. + * @return {Object} Command specific value, for example the current font size. + */ + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + /** + * Shows the editor and hides any textarea/div that the editor is supposed to replace. + * + * @method show + */ + show : function() { + var t = this; + + DOM.show(t.getContainer()); + DOM.hide(t.id); + t.load(); + }, + + /** + * Hides the editor and shows any textarea/div that the editor is supposed to replace. + * + * @method hide + */ + hide : function() { + var t = this, d = t.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && d) + d.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + t.save(); + DOM.hide(t.getContainer()); + DOM.setStyle(t.id, 'display', t.orgDisplay); + }, + + /** + * Returns true/false if the editor is hidden or not. + * + * @method isHidden + * @return {Boolean} True/false if the editor is hidden or not. + */ + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + /** + * Sets the progress state, this will display a throbber/progess for the editor. + * This is ideal for asycronous operations like an AJAX save call. + * + * @method setProgressState + * @param {Boolean} b Boolean state if the progress should be shown or hidden. + * @param {Number} ti Optional time to wait before the progress gets shown. + * @param {Object} o Optional object to pass to the progress observers. + * @return {Boolean} Same as the input state. + * @example + * // Show progress for the active editor + * tinyMCE.activeEditor.setProgressState(true); + * + * // Hide progress for the active editor + * tinyMCE.activeEditor.setProgressState(false); + * + * // Show progress after 3 seconds + * tinyMCE.activeEditor.setProgressState(true, 3000); + */ + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + /** + * Loads contents from the textarea or div element that got converted into an editor instance. + * This method will move the contents from that textarea or div into the editor by using setContent + * so all events etc that method has will get dispatched as well. + * + * @method load + * @param {Object} o Optional content object, this gets passed around through the whole load process. + * @return {String} HTML string that got set into the editor. + */ + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + /** + * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. + * This method will move the HTML contents from the editor into that textarea or div by getContent + * so all events etc that method has will get dispatched as well. + * + * @method save + * @param {Object} o Optional content object, this gets passed around through the whole save process. + * @return {String} HTML string that got set into the textarea/div. + */ + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + // Add undo level will trigger onchange event + if (!o.no_events) { + t.undoManager.typing = false; + t.undoManager.add(); + } + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + /** + * Sets the specified content to the editor instance, this will cleanup the content before it gets set using + * the different cleanup rules options. + * + * @method setContent + * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. + * @param {Object} args Optional content object, this gets passed around through the whole set process. + * @return {String} HTML string that got set into the editor. + * @example + * // Sets the HTML contents of the activeEditor editor + * tinyMCE.activeEditor.setContent('some html'); + * + * // Sets the raw contents of the activeEditor editor + * tinyMCE.activeEditor.setContent('some html', {format : 'raw'}); + * + * // Sets the content of a specific editor (my_editor in this example) + * tinyMCE.get('my_editor').setContent(data); + * + * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added + * tinyMCE.activeEditor.setContent('[b]some[/b] html', {format : 'bbcode'}); + */ + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(), forcedRootBlockName; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + forcedRootBlockName = self.settings.forced_root_block; + if (forcedRootBlockName) + content = '<' + forcedRootBlockName + '>
    '; + else + content = '
    '; + + body.innerHTML = content; + self.selection.select(body, true); + self.selection.collapse(true); + return; + } + + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); + } + + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + self.selection.normalize(); + + return args.content; + }, + + /** + * Gets the content from the editor instance, this will cleanup the content before it gets returned using + * the different cleanup rules options. + * + * @method getContent + * @param {Object} args Optional content object, this gets passed around through the whole get process. + * @return {String} Cleaned content string, normally HTML contents. + * @example + * // Get the HTML contents of the currently active editor + * console.debug(tinyMCE.activeEditor.getContent()); + * + * // Get the raw contents of the currently active editor + * tinyMCE.activeEditor.getContent({format : 'raw'}); + * + * // Get content of a specific editor: + * tinyMCE.get('content id').getContent() + */ + getContent : function(args) { + var self = this, content; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; + + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); + + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); + + args.content = tinymce.trim(content); + + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); + + return args.content; + }, + + /** + * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. + * + * @method isDirty + * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. + * @example + * if (tinyMCE.activeEditor.isDirty()) + * alert("You must save your contents."); + */ + isDirty : function() { + var self = this; + + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; + }, + + /** + * Returns the editors container element. The container element wrappes in + * all the elements added to the page for the editor. Such as UI, iframe etc. + * + * @method getContainer + * @return {Element} HTML DOM element for the editor container. + */ + getContainer : function() { + var t = this; + + if (!t.container) + t.container = DOM.get(t.editorContainer || t.id + '_parent'); + + return t.container; + }, + + /** + * Returns the editors content area container element. The this element is the one who + * holds the iframe or the editable element. + * + * @method getContentAreaContainer + * @return {Element} HTML DOM element for the editor area container. + */ + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + /** + * Returns the target element/textarea that got replaced with a TinyMCE editor instance. + * + * @method getElement + * @return {Element} HTML DOM element for the replaced element. + */ + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + /** + * Returns the iframes window object. + * + * @method getWin + * @return {Window} Iframe DOM window object. + */ + getWin : function() { + var t = this, e; + + if (!t.contentWindow) { + e = DOM.get(t.id + "_ifr"); + + if (e) + t.contentWindow = e.contentWindow; + } + + return t.contentWindow; + }, + + /** + * Returns the iframes document object. + * + * @method getDoc + * @return {Document} Iframe DOM document object. + */ + getDoc : function() { + var t = this, w; + + if (!t.contentDocument) { + w = t.getWin(); + + if (w) + t.contentDocument = w.document; + } + + return t.contentDocument; + }, + + /** + * Returns the iframes body element. + * + * @method getBody + * @return {Element} Iframe body element. + */ + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + /** + * URL converter function this gets executed each time a user adds an img, a or + * any other element that has a URL in it. This will be called both by the DOM and HTML + * manipulation functions. + * + * @method convertURL + * @param {string} u URL to convert. + * @param {string} n Attribute name src, href etc. + * @param {string/HTMLElement} Tag name or HTML DOM element depending on HTML or DOM insert. + * @return {string} Converted URL string. + */ + convertURL : function(u, n, e) { + var t = this, s = t.settings; + + // Use callback instead + if (s.urlconverter_callback) + return t.execCallback('urlconverter_callback', u, e, true, n); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) + return u; + + // Convert to relative + if (s.relative_urls) + return t.documentBaseURI.toRelative(u); + + // Convert to absolute + u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); + + return u; + }, + + /** + * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. + * + * @method addVisual + * @param {Element} e Optional root element to loop though to find tables etc that needs the visual aid. + */ + addVisual : function(e) { + var t = this, s = t.settings; + + e = e || t.getBody(); + + if (!is(t.hasVisual)) + t.hasVisual = s.visual; + + each(t.dom.select('table,a', e), function(e) { + var v; + + switch (e.nodeName) { + case 'TABLE': + v = t.dom.getAttrib(e, 'border'); + + if (!v || v == '0') { + if (t.hasVisual) + t.dom.addClass(e, s.visual_table_class); + else + t.dom.removeClass(e, s.visual_table_class); + } + + return; + + case 'A': + v = t.dom.getAttrib(e, 'name'); + + if (v) { + if (t.hasVisual) + t.dom.addClass(e, 'mceItemAnchor'); + else + t.dom.removeClass(e, 'mceItemAnchor'); + } + + return; + } + }); + + t.onVisualAid.dispatch(t, e, t.hasVisual); + }, + + /** + * Removes the editor from the dom and tinymce collection. + * + * @method remove + */ + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + tinymce.remove(t); + DOM.remove(e); + }, + + /** + * Destroys the editor instance by removing all events, element references or other resources + * that could leak memory. This method will be called automatically when the page is unloaded + * but you can also call it directly if you know what you are doing. + * + * @method destroy + * @param {Boolean} s Optional state if the destroy is an automatic destroy or user called one. + */ + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _addEvents : function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var t = this, i, s = t.settings, dom = t.dom, lo = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + function eventHandler(e, o) { + var ty = e.type; + + // Don't fire events when it's removed + if (t.removed) + return; + + // Generic event handler + if (t.onEvent.dispatch(t, e, o) !== false) { + // Specific event handler + t[lo[e.fakeType || e.type]].dispatch(t, e, o); + } + }; + + // Add DOM events + each(lo, function(v, k) { + switch (k) { + case 'contextmenu': + dom.bind(t.getDoc(), k, eventHandler); + break; + + case 'paste': + dom.bind(t.getBody(), k, function(e) { + eventHandler(e); + }); + break; + + case 'submit': + case 'reset': + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + break; + + default: + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + } + }); + + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + t.focus(true); + }); + + // #ifdef contentEditable + + if (s.content_editable && tinymce.isOpera) { + // Opera doesn't support focus event for contentEditable elements so we need to fake it + function doFocus(e) { + t.focus(true); + }; + + dom.bind(t.getBody(), 'click', doFocus); + dom.bind(t.getBody(), 'keydown', doFocus); + } + + // #endif + + // Fixes bug where a specified document_base_uri could result in broken images + // This will also fix drag drop of images in Gecko + if (tinymce.isGecko) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + var v; + + e = e.target; + + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) + e.src = t.documentBaseURI.toAbsolute(v); + }); + } + + // Set various midas options in Gecko + if (isGecko) { + function setOpts() { + var t = this, d = t.getDoc(), s = t.settings; + + if (isGecko && !s.readonly) { + t._refreshContentEditable(); + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, false); + } catch (ex) { + // Use old method + if (!t._isHidden()) + try {d.execCommand("useCSS", 0, true);} catch (ex) {} + } + + if (!s.table_inline_editing) + try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} + + if (!s.object_resizing) + try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} + } + }; + + t.onBeforeExecCommand.add(setOpts); + t.onMouseDown.add(setOpts); + } + + // Add node change handlers + t.onMouseUp.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); + t.onKeyUp.add(function(ed, e) { + var c = e.keyCode; + + if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) + t.nodeChanged(); + }); + + + // Add block quote deletion handler + t.onKeyDown.add(function(ed, e) { + // Was the BACKSPACE key pressed? + if (e.keyCode != 8) + return; + + var n = ed.selection.getRng().startContainer; + var offset = ed.selection.getRng().startOffset; + + while (n && n.nodeType && n.nodeType != 1 && n.parentNode) + n = n.parentNode; + + // Is the cursor at the beginning of a blockquote? + if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) { + // Remove the blockquote + ed.formatter.toggle('blockquote', null, n.parentNode); + + // Move the caret to the beginning of n + var rng = ed.selection.getRng(); + rng.setStart(n, 0); + rng.setEnd(n, 0); + ed.selection.setRng(rng); + ed.selection.collapse(false); + } + }); + + + + // Add reset handler + t.onReset.add(function() { + t.setContent(t.startContent, {format : 'raw'}); + }); + + // Add shortcuts + if (s.custom_shortcuts) { + if (s.custom_undo_redo_keyboard_shortcuts) { + t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); + t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); + } + + // Add default shortcuts for gecko + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); + + // BlockFormat shortcuts keys + for (i=1; i<=6; i++) + t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + + t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); + t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); + + function find(e) { + var v = null; + + if (!e.altKey && !e.ctrlKey && !e.metaKey) + return v; + + each(t.shortcuts, function(o) { + if (tinymce.isMac && o.ctrl != e.metaKey) + return; + else if (!tinymce.isMac && o.ctrl != e.ctrlKey) + return; + + if (o.alt != e.altKey) + return; + + if (o.shift != e.shiftKey) + return; + + if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { + v = o; + return false; + } + }); + + return v; + }; + + t.onKeyUp.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyPress.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyDown.add(function(ed, e) { + var o = find(e); + + if (o) { + o.func.call(o.scope); + return Event.cancel(e); + } + }); + } + + if (tinymce.isIE) { + // Fix so resize will only update the width and height attributes not the styles of an image + // It will also block mceItemNoResize items + dom.bind(t.getDoc(), 'controlselect', function(e) { + var re = t.resizeInfo, cb; + + e = e.target; + + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + + if (re) + dom.unbind(re.node, re.ev, re.cb); + + if (!dom.hasClass(e, 'mceItemNoResize')) { + ev = 'resizeend'; + cb = dom.bind(e, ev, function(e) { + var v; + + e = e.target; + + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); + } + + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); + } + }); + } else { + ev = 'resizestart'; + cb = dom.bind(e, 'resizestart', Event.cancel, Event); + } + + re = t.resizeInfo = { + node : e, + ev : ev, + cb : cb + }; + }); + } + + if (tinymce.isOpera) { + t.onClick.add(function(ed, e) { + Event.prevent(e); + }); + } + + // Add custom undo/redo handlers + if (s.custom_undo_redo) { + function addUndo() { + t.undoManager.typing = false; + t.undoManager.add(); + }; + + dom.bind(t.getDoc(), 'focusout', function(e) { + if (!t.removed && t.undoManager.typing) + addUndo(); + }); + + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + + t.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey) + addUndo(); + }); + + t.onKeyDown.add(function(ed, e) { + var keyCode = e.keyCode, sel; + + if (keyCode == 8) { + sel = t.getDoc().selection; + + // Fix IE control + backspace browser bug + if (sel && sel.createRange && sel.createRange().item) { + t.undoManager.beforeChange(); + ed.dom.remove(sel.createRange().item(0)); + addUndo(); + + return Event.cancel(e); + } + } + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + + if (t.undoManager.typing) + addUndo(); + + return; + } + + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + }); + + t.onMouseDown.add(function() { + if (t.undoManager.typing) + addUndo(); + }); + } + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + + if (target !== t.getBody()) { + t.dom.setAttrib(target, "style", null); + + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + var s = t.selection; + + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } + + t.onKeyPress.add(function(ed, e) { + var applyAttributes; + + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); + + return Event.cancel(e); + } + }); + + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); + + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); + } + }); + } + }, + + _refreshContentEditable : function() { + var self = this, body, parent; + + // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again + if (self._isHidden()) { + body = self.getBody(); + parent = body.parentNode; + + parent.removeChild(body); + parent.appendChild(body); + + body.focus(); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/EditorCommands.js b/js/tiny_mce/classes/EditorCommands.js index a6df479c..6f6f3431 100644 --- a/js/tiny_mce/classes/EditorCommands.js +++ b/js/tiny_mce/classes/EditorCommands.js @@ -1,435 +1,577 @@ -/** - * EditorCommands.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Added for compression purposes - var each = tinymce.each, undefined, TRUE = true, FALSE = false; - - /** - * This class enables you to add custom editor commands and it contains - * overrides for native browser commands to address various bugs and issues. - * - * @class tinymce.EditorCommands - */ - tinymce.EditorCommands = function(editor) { - var dom = editor.dom, - selection = editor.selection, - commands = {state: {}, exec : {}, value : {}}, - settings = editor.settings, - bookmark; - - /** - * Executes the specified command. - * - * @method execCommand - * @param {String} command Command to execute. - * @param {Boolean} ui Optional user interface state. - * @param {Object} value Optional value for command. - * @return {Boolean} true/false if the command was found or not. - */ - function execCommand(command, ui, value) { - var func; - - command = command.toLowerCase(); - if (func = commands.exec[command]) { - func(command, ui, value); - return TRUE; - } - - return FALSE; - }; - - /** - * Queries the current state for a command for example if the current selection is "bold". - * - * @method queryCommandState - * @param {String} command Command to check the state of. - * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. - */ - function queryCommandState(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.state[command]) - return func(command); - - return -1; - }; - - /** - * Queries the command value for example the current fontsize. - * - * @method queryCommandValue - * @param {String} command Command to check the value of. - * @return {Object} Command value of false if it's not found. - */ - function queryCommandValue(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.value[command]) - return func(command); - - return FALSE; - }; - - /** - * Adds commands to the command collection. - * - * @method addCommands - * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated. - * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. - */ - function addCommands(command_list, type) { - type = type || 'exec'; - - each(command_list, function(callback, command) { - each(command.toLowerCase().split(','), function(command) { - commands[type][command] = callback; - }); - }); - }; - - // Expose public methods - tinymce.extend(this, { - execCommand : execCommand, - queryCommandState : queryCommandState, - queryCommandValue : queryCommandValue, - addCommands : addCommands - }); - - // Private methods - - function execNativeCommand(command, ui, value) { - if (ui === undefined) - ui = FALSE; - - if (value === undefined) - value = null; - - return editor.getDoc().execCommand(command, ui, value); - }; - - function isFormatMatch(name) { - return editor.formatter.match(name); - }; - - function toggleFormat(name, value) { - editor.formatter.toggle(name, value ? {value : value} : undefined); - }; - - function storeSelection(type) { - bookmark = selection.getBookmark(type); - }; - - function restoreSelection() { - selection.moveToBookmark(bookmark); - }; - - // Add execCommand overrides - addCommands({ - // Ignore these, added for compatibility - 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, - - // Add undo manager logic - 'mceEndUndoLevel,mceAddUndoLevel' : function() { - editor.undoManager.add(); - }, - - 'Cut,Copy,Paste' : function(command) { - var doc = editor.getDoc(), failed; - - // Try executing the native command - try { - execNativeCommand(command); - } catch (ex) { - // Command failed - failed = TRUE; - } - - // Present alert message about clipboard access not being available - if (failed || !doc.queryCommandEnabled(command)) { - if (tinymce.isGecko) { - editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { - if (state) - open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); - }); - } else - editor.windowManager.alert(editor.getLang('clipboard_no_support')); - } - }, - - // Override unlink command - unlink : function(command) { - if (selection.isCollapsed()) - selection.select(selection.getNode()); - - execNativeCommand(command); - selection.collapse(FALSE); - }, - - // Override justify commands to use the text formatter engine - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - var align = command.substring(7); - - // Remove all other alignments first - each('left,center,right,full'.split(','), function(name) { - if (align != name) - editor.formatter.remove('align' + name); - }); - - toggleFormat('align' + align); - }, - - // Override list commands to fix WebKit bug - 'InsertUnorderedList,InsertOrderedList' : function(command) { - var listElm, listParent; - - execNativeCommand(command); - - // WebKit produces lists within block elements so we need to split them - // we will replace the native list creation logic to custom logic later on - // TODO: Remove this when the list creation logic is removed - listElm = dom.getParent(selection.getNode(), 'ol,ul'); - if (listElm) { - listParent = listElm.parentNode; - - // If list is within a text block then split that block - if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { - storeSelection(); - dom.split(listParent, listElm); - restoreSelection(); - } - } - }, - - // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough' : function(command) { - toggleFormat(command); - }, - - // Override commands to use the text formatter engine - 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { - toggleFormat(command, value); - }, - - FontSize : function(command, ui, value) { - var fontClasses, fontSizes; - - // Convert font size 1-7 to styles - if (value >= 1 && value <= 7) { - fontSizes = tinymce.explode(settings.font_size_style_values); - fontClasses = tinymce.explode(settings.font_size_classes); - - if (fontClasses) - value = fontClasses[value - 1] || value; - else - value = fontSizes[value - 1] || value; - } - - toggleFormat(command, value); - }, - - RemoveFormat : function(command) { - editor.formatter.remove(command); - }, - - mceBlockQuote : function(command) { - toggleFormat('blockquote'); - }, - - FormatBlock : function(command, ui, value) { - return toggleFormat(value); - }, - - mceCleanup : function() { - storeSelection(); - editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); - restoreSelection(); - }, - - mceRemoveNode : function(command, ui, value) { - var node = value || selection.getNode(); - - // Make sure that the body node isn't removed - if (node != ed.getBody()) { - storeSelection(); - editor.dom.remove(node, TRUE); - restoreSelection(); - } - }, - - mceSelectNodeDepth : function(command, ui, value) { - var counter = 0; - - dom.getParent(selection.getNode(), function(node) { - if (node.nodeType == 1 && counter++ == value) { - selection.select(node); - return FALSE; - } - }, editor.getBody()); - }, - - mceSelectNode : function(command, ui, value) { - selection.select(value); - }, - - mceInsertContent : function(command, ui, value) { - selection.setContent(value); - }, - - mceInsertRawHTML : function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value)); - }, - - mceSetContent : function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent' : function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - each(selection.getSelectedBlocks(), function(element) { - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); - dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); - } else - dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); - }); - } else - execNativeCommand(command); - }, - - mceRepaint : function() { - var bookmark; - - if (tinymce.isGecko) { - try { - storeSelection(TRUE); - - if (selection.getSel()) - selection.getSel().selectAllChildren(editor.getBody()); - - selection.collapse(TRUE); - restoreSelection(); - } catch (ex) { - // Ignore - } - } - }, - - mceToggleFormat : function(command, ui, value) { - editor.formatter.toggle(value); - }, - - InsertHorizontalRule : function() { - selection.setContent('
    '); - }, - - mceToggleVisualAid : function() { - editor.hasVisual = !editor.hasVisual; - editor.addVisual(); - }, - - mceReplaceContent : function(command, ui, value) { - selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); - }, - - mceInsertLink : function(command, ui, value) { - var link = dom.getParent(selection.getNode(), 'a'); - - if (tinymce.is(value, 'string')) - value = {href : value}; - - if (!link) { - execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); - each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { - dom.setAttribs(link, value); - }); - } else { - if (value.href) - dom.setAttribs(link, value); - else - ed.dom.remove(link, TRUE); - } - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - return isFormatMatch('align' + command.substring(7)); - }, - - 'Bold,Italic,Underline,Strikethrough' : function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote : function() { - return isFormatMatch('blockquote'); - }, - - Outdent : function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - } - - return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); - }, - - 'InsertUnorderedList,InsertOrderedList' : function(command) { - return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName' : function(command) { - var value = 0, parent; - - if (parent = dom.getParent(selection.getNode(), 'span')) { - if (command == 'fontsize') - value = parent.style.fontSize; - else - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - - return value; - } - }, 'value'); - - // Add undo manager logic - if (settings.custom_undo_redo) { - addCommands({ - Undo : function() { - editor.undoManager.undo(); - }, - - Redo : function() { - editor.undoManager.redo(); - } - }); - } - }; -})(tinymce); \ No newline at end of file +/** + * EditorCommands.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undefined, TRUE = true, FALSE = false; + + /** + * This class enables you to add custom editor commands and it contains + * overrides for native browser commands to address various bugs and issues. + * + * @class tinymce.EditorCommands + */ + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + formatter = editor.formatter, + bookmark; + + /** + * Executes the specified command. + * + * @method execCommand + * @param {String} command Command to execute. + * @param {Boolean} ui Optional user interface state. + * @param {Object} value Optional value for command. + * @return {Boolean} true/false if the command was found or not. + */ + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + /** + * Queries the current state for a command for example if the current selection is "bold". + * + * @method queryCommandState + * @param {String} command Command to check the state of. + * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. + */ + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + /** + * Queries the command value for example the current fontsize. + * + * @method queryCommandValue + * @param {String} command Command to check the value of. + * @return {Object} Command value of false if it's not found. + */ + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + /** + * Adds commands to the command collection. + * + * @method addCommands + * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated. + * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. + */ + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) + ui = FALSE; + + if (value === undefined) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return formatter.match(name); + }; + + function toggleFormat(name, value) { + formatter.toggle(name, value ? {value : value} : undefined); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + execCommand('mceRepaint'); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + var parser, serializer, parentNode, rootNode, fragment, args, + marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; + + // Setup parser and serializer + parser = editor.parser; + serializer = new tinymce.html.Serializer({}, editor.schema); + bookmarkHtml = '\uFEFF'; + + // Run beforeSetContent handlers on the HTML to be inserted + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Replace the caret marker with a span bookmark element + value = value.replace(/\{\$caret\}/, bookmarkHtml); + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) + editor.getDoc().execCommand('Delete', false, null); + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + args = {context : parentNode.nodeName.toLowerCase()}; + fragment = parser.parse(value, args); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + node.parent.insert(marker, node, node.name === 'br'); + break; + } + } + } + + // If parser says valid we can insert the contents into that parent + if (!args.invalid) { + value = serializer.serialize(fragment); + + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + node = parentNode.firstChild; + node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) + dom.setHTML(parentNode, value); + else + selection.setContent(value); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = editor.selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) + parentNode = node = rootNode; + else + node = parentNode; + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function() { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) + dom.setHTML(rootNode, value); + else + dom.setOuterHTML(parentNode, value); + } + + marker = dom.get('mce_marker'); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + nodeRect = dom.getRect(marker); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); + viewportBodyElement.scrollLeft = nodeRect.x; + viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; + } + + // Move selection before marker and remove it + rng = dom.createRng(); + + // If previous sibling is a text node set the selection to the end of that node + node = marker.previousSibling; + if (node && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + } else { + // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node + rng.setStartBefore(marker); + rng.setEndBefore(marker); + } + + // Remove the marker node and set the new range + dom.remove(marker); + selection.setRng(rng); + + // Dispatch after event and add any visual elements needed + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + editor.execCommand('mceInsertContent', false, '
    '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var anchor; + + if (typeof(value) == 'string') + value = {href : value}; + + anchor = dom.getParent(selection.getNode(), 'a'); + + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + + // Remove existing links if there could be child links or that the href isn't specified + if (!anchor || !value.href) { + formatter.remove('link'); + } + + // Apply new link to selection + if (value.href) { + formatter.apply('link', value, anchor); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + editor.selection.setRng(rng); + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + return isFormatMatch('align' + command.substring(7)); + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + if (settings.custom_undo_redo) { + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + } + }; +})(tinymce); diff --git a/js/tiny_mce/classes/EditorManager.js b/js/tiny_mce/classes/EditorManager.js index 0b57455b..3574502b 100644 --- a/js/tiny_mce/classes/EditorManager.js +++ b/js/tiny_mce/classes/EditorManager.js @@ -1,453 +1,503 @@ -/** - * EditorManager.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - /** - * @class tinymce - */ - - // Shorten names - var each = tinymce.each, extend = tinymce.extend, - DOM = tinymce.DOM, Event = tinymce.dom.Event, - ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, - explode = tinymce.explode, - Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; - - // Setup some URLs where the editor API is located and where the document is - tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); - if (!/[\/\\]$/.test(tinymce.documentBaseURL)) - tinymce.documentBaseURL += '/'; - - tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); - - /** - * Absolute baseURI for the installation path of TinyMCE. - * - * @property baseURI - * @type tinymce.util.URI - */ - tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); - - // Add before unload listener - // This was required since IE was leaking memory if you added and removed beforeunload listeners - // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event - tinymce.onBeforeUnload = new Dispatcher(tinymce); - - // Must be on window or IE will leak if the editor is placed in frame or iframe - Event.add(window, 'beforeunload', function(e) { - tinymce.onBeforeUnload.dispatch(tinymce, e); - }); - - /** - * Fires when a new editor instance is added to the tinymce collection. - * - * @event onAddEditor - * @param {tinymce} sender TinyMCE root class/namespace. - * @param {tinymce.Editor} editor Editor instance. - */ - tinymce.onAddEditor = new Dispatcher(tinymce); - - /** - * Fires when an editor instance is removed from the tinymce collection. - * - * @event onRemoveEditor - * @param {tinymce} sender TinyMCE root class/namespace. - * @param {tinymce.Editor} editor Editor instance. - */ - tinymce.onRemoveEditor = new Dispatcher(tinymce); - - tinymce.EditorManager = extend(tinymce, { - /** - * Collection of editor instances. - * - * @property editors - * @type Object - */ - editors : [], - - /** - * Collection of language pack data. - * - * @property i18n - * @type Object - */ - i18n : {}, - - /** - * Currently active editor instance. - * - * @property activeEditor - * @type tinymce.Editor - */ - activeEditor : null, - - /** - * Initializes a set of editors. This method will create a bunch of editors based in the input. - * - * @method init - * @param {Object} s Settings object to be passed to each editor instance. - */ - init : function(s) { - var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; - - function execCallback(se, n, s) { - var f = se[n]; - - if (!f) - return; - - if (tinymce.is(f, 'string')) { - s = f.replace(/\.\w+$/, ''); - s = s ? tinymce.resolve(s) : 0; - f = tinymce.resolve(f); - } - - return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); - }; - - s = extend({ - theme : "simple", - language : "en" - }, s); - - t.settings = s; - - // Legacy call - Event.add(document, 'init', function() { - var l, co; - - execCallback(s, 'onpageload'); - - switch (s.mode) { - case "exact": - l = s.elements || ''; - - if(l.length > 0) { - each(explode(l), function(v) { - if (DOM.get(v)) { - ed = new tinymce.Editor(v, s); - el.push(ed); - ed.render(1); - } else { - each(document.forms, function(f) { - each(f.elements, function(e) { - if (e.name === v) { - v = 'mce_editor_' + instanceCounter++; - DOM.setAttrib(e, 'id', v); - - ed = new tinymce.Editor(v, s); - el.push(ed); - ed.render(1); - } - }); - }); - } - }); - } - break; - - case "textareas": - case "specific_textareas": - function hasClass(n, c) { - return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); - }; - - each(DOM.select('textarea'), function(v) { - if (s.editor_deselector && hasClass(v, s.editor_deselector)) - return; - - if (!s.editor_selector || hasClass(v, s.editor_selector)) { - // Can we use the name - e = DOM.get(v.name); - if (!v.id && !e) - v.id = v.name; - - // Generate unique name if missing or already exists - if (!v.id || t.get(v.id)) - v.id = DOM.uniqueId(); - - ed = new tinymce.Editor(v.id, s); - el.push(ed); - ed.render(1); - } - }); - break; - } - - // Call onInit when all editors are initialized - if (s.oninit) { - l = co = 0; - - each(el, function(ed) { - co++; - - if (!ed.initialized) { - // Wait for it - ed.onInit.add(function() { - l++; - - // All done - if (l == co) - execCallback(s, 'oninit'); - }); - } else - l++; - - // All done - if (l == co) - execCallback(s, 'oninit'); - }); - } - }); - }, - - /** - * Returns a editor instance by id. - * - * @method get - * @param {String/Number} id Editor instance id or index to return. - * @return {tinymce.Editor} Editor instance to return. - */ - get : function(id) { - if (id === undefined) - return this.editors; - - return this.editors[id]; - }, - - /** - * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. - * - * @method getInstanceById - * @param {String} id Editor instance id to return. - * @return {tinymce.Editor} Editor instance to return. - * @deprecated Use get method instead. - * @see #get - */ - getInstanceById : function(id) { - return this.get(id); - }, - - /** - * Adds an editor instance to the editor collection. This will also set it as the active editor. - * - * @method add - * @param {tinymce.Editor} editor Editor instance to add to the collection. - * @return {tinymce.Editor} The same instance that got passed in. - */ - add : function(editor) { - var self = this, editors = self.editors; - - // Add named and index editor instance - editors[editor.id] = editor; - editors.push(editor); - - self._setActive(editor); - self.onAddEditor.dispatch(self, editor); - - // #ifdef jquery - - // Patch the tinymce.Editor instance with jQuery adapter logic - if (tinymce.adapter) - tinymce.adapter.patchEditor(editor); - - // #endif - - return editor; - }, - - /** - * Removes a editor instance from the collection. - * - * @method remove - * @param {tinymce.Editor} e Editor instance to remove. - * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. - */ - remove : function(editor) { - var t = this, i, editors = t.editors; - - // Not in the collection - if (!editors[editor.id]) - return null; - - delete editors[editor.id]; - - for (i = 0; i < editors.length; i++) { - if (editors[i] == editor) { - editors.splice(i, 1); - break; - } - } - - // Select another editor since the active one was removed - if (t.activeEditor == editor) - t._setActive(editors[0]); - - editor.destroy(); - t.onRemoveEditor.dispatch(t, editor); - - return editor; - }, - - /** - * Executes a specific command on the currently active editor. - * - * @method execCommand - * @param {String} c Command to perform for example Bold. - * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. - * @param {String} v Optional value parameter like for example an URL to a link. - * @return {Boolean} true/false if the command was executed or not. - */ - execCommand : function(c, u, v) { - var t = this, ed = t.get(v), w; - - // Manager commands - switch (c) { - case "mceFocus": - ed.focus(); - return true; - - case "mceAddEditor": - case "mceAddControl": - if (!t.get(v)) - new tinymce.Editor(v, t.settings).render(); - - return true; - - case "mceAddFrameControl": - w = v.window; - - // Add tinyMCE global instance and tinymce namespace to specified window - w.tinyMCE = tinyMCE; - w.tinymce = tinymce; - - tinymce.DOM.doc = w.document; - tinymce.DOM.win = w; - - ed = new tinymce.Editor(v.element_id, v); - ed.render(); - - // Fix IE memory leaks - if (tinymce.isIE) { - function clr() { - ed.destroy(); - w.detachEvent('onunload', clr); - w = w.tinyMCE = w.tinymce = null; // IE leak - }; - - w.attachEvent('onunload', clr); - } - - v.page_window = null; - - return true; - - case "mceRemoveEditor": - case "mceRemoveControl": - if (ed) - ed.remove(); - - return true; - - case 'mceToggleEditor': - if (!ed) { - t.execCommand('mceAddControl', 0, v); - return true; - } - - if (ed.isHidden()) - ed.show(); - else - ed.hide(); - - return true; - } - - // Run command on active editor - if (t.activeEditor) - return t.activeEditor.execCommand(c, u, v); - - return false; - }, - - /** - * Executes a command on a specific editor by id. This method was added for compatibility with the 2.x branch. - * - * @deprecated Use the execCommand method of a editor instance instead. - * @method execInstanceCommand - * @param {String} id Editor id to perform the command on. - * @param {String} c Command to perform for example Bold. - * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. - * @param {String} v Optional value parameter like for example an URL to a link. - * @return {Boolean} true/false if the command was executed or not. - */ - execInstanceCommand : function(id, c, u, v) { - var ed = this.get(id); - - if (ed) - return ed.execCommand(c, u, v); - - return false; - }, - - /** - * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. - * - * @method triggerSave - */ - triggerSave : function() { - each(this.editors, function(e) { - e.save(); - }); - }, - - /** - * Adds a language pack, this gets called by the loaded language files like en.js. - * - * @method addI18n - * @param {String} p Prefix for the language items. For example en.myplugin - * @param {Object} o Name/Value collection with items to add to the language group. - */ - addI18n : function(p, o) { - var lo, i18n = this.i18n; - - if (!tinymce.is(p, 'string')) { - each(p, function(o, lc) { - each(o, function(o, g) { - each(o, function(o, k) { - if (g === 'common') - i18n[lc + '.' + k] = o; - else - i18n[lc + '.' + g + '.' + k] = o; - }); - }); - }); - } else { - each(o, function(o, k) { - i18n[p + '.' + k] = o; - }); - } - }, - - // Private methods - - _setActive : function(editor) { - this.selectedInstance = this.activeEditor = editor; - } - }); -})(tinymce); - -/** - * Alternative name for tinymce added for 2.x compatibility. - * - * @member - * @property tinyMCE - * @type tinymce - */ +/** + * EditorManager.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * @class tinymce + */ + + // Shorten names + var each = tinymce.each, extend = tinymce.extend, + DOM = tinymce.DOM, Event = tinymce.dom.Event, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode, + Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + + /** + * Absolute baseURI for the installation path of TinyMCE. + * + * @property baseURI + * @type tinymce.util.URI + */ + tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + tinymce.onBeforeUnload = new Dispatcher(tinymce); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + tinymce.onBeforeUnload.dispatch(tinymce, e); + }); + + /** + * Fires when a new editor instance is added to the tinymce collection. + * + * @event onAddEditor + * @param {tinymce} sender TinyMCE root class/namespace. + * @param {tinymce.Editor} editor Editor instance. + * @example + * tinyMCE.execCommand("mceAddControl", false, "some_textarea"); + * tinyMCE.onAddEditor.add(function(mgr,ed) { + * console.debug('A new editor is available' + ed.id); + * }); + */ + tinymce.onAddEditor = new Dispatcher(tinymce); + + /** + * Fires when an editor instance is removed from the tinymce collection. + * + * @event onRemoveEditor + * @param {tinymce} sender TinyMCE root class/namespace. + * @param {tinymce.Editor} editor Editor instance. + */ + tinymce.onRemoveEditor = new Dispatcher(tinymce); + + tinymce.EditorManager = extend(tinymce, { + /** + * Collection of editor instances. + * + * @property editors + * @type Object + * @example + * for (edId in tinyMCE.editors) + * tinyMCE.editors[edId].save(); + */ + editors : [], + + /** + * Collection of language pack data. + * + * @property i18n + * @type Object + */ + i18n : {}, + + /** + * Currently active editor instance. + * + * @property activeEditor + * @type tinymce.Editor + * @example + * tinyMCE.activeEditor.selection.getContent(); + * tinymce.EditorManager.activeEditor.selection.getContent(); + */ + activeEditor : null, + + /** + * Initializes a set of editors. This method will create a bunch of editors based in the input. + * + * @method init + * @param {Object} s Settings object to be passed to each editor instance. + * @example + * // Initializes a editor using the longer method + * tinymce.EditorManager.init({ + * some_settings : 'some value' + * }); + * + * // Initializes a editor instance using the shorter version + * tinyMCE.init({ + * some_settings : 'some value' + * }); + */ + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; + + function execCallback(se, n, s) { + var f = se[n]; + + if (!f) + return; + + if (tinymce.is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + } + + return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); + }; + + s = extend({ + theme : "simple", + language : "en" + }, s); + + t.settings = s; + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + switch (s.mode) { + case "exact": + l = s.elements || ''; + + if(l.length > 0) { + each(explode(l), function(v) { + if (DOM.get(v)) { + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } else { + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + instanceCounter++; + DOM.setAttrib(e, 'id', v); + + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } + }); + }); + } + }); + } + break; + + case "textareas": + case "specific_textareas": + function hasClass(n, c) { + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); + }; + + each(DOM.select('textarea'), function(v) { + if (s.editor_deselector && hasClass(v, s.editor_deselector)) + return; + + if (!s.editor_selector || hasClass(v, s.editor_selector)) { + // Can we use the name + e = DOM.get(v.name); + if (!v.id && !e) + v.id = v.name; + + // Generate unique name if missing or already exists + if (!v.id || t.get(v.id)) + v.id = DOM.uniqueId(); + + ed = new tinymce.Editor(v.id, s); + el.push(ed); + ed.render(1); + } + }); + break; + } + + // Call onInit when all editors are initialized + if (s.oninit) { + l = co = 0; + + each(el, function(ed) { + co++; + + if (!ed.initialized) { + // Wait for it + ed.onInit.add(function() { + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } else + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } + }); + }, + + /** + * Returns a editor instance by id. + * + * @method get + * @param {String/Number} id Editor instance id or index to return. + * @return {tinymce.Editor} Editor instance to return. + * @example + * // Adds an onclick event to an editor by id (shorter version) + * tinyMCE.get('mytextbox').onClick.add(function(ed, e) { + * ed.windowManager.alert('Hello world!'); + * }); + * + * // Adds an onclick event to an editor by id (longer version) + * tinymce.EditorManager.get('mytextbox').onClick.add(function(ed, e) { + * ed.windowManager.alert('Hello world!'); + * }); + */ + get : function(id) { + if (id === undefined) + return this.editors; + + return this.editors[id]; + }, + + /** + * Returns a editor instance by id. This method was added for compatibility with the 2.x branch. + * + * @method getInstanceById + * @param {String} id Editor instance id to return. + * @return {tinymce.Editor} Editor instance to return. + * @deprecated Use get method instead. + * @see #get + */ + getInstanceById : function(id) { + return this.get(id); + }, + + /** + * Adds an editor instance to the editor collection. This will also set it as the active editor. + * + * @method add + * @param {tinymce.Editor} editor Editor instance to add to the collection. + * @return {tinymce.Editor} The same instance that got passed in. + */ + add : function(editor) { + var self = this, editors = self.editors; + + // Add named and index editor instance + editors[editor.id] = editor; + editors.push(editor); + + self._setActive(editor); + self.onAddEditor.dispatch(self, editor); + + // #ifdef jquery + + // Patch the tinymce.Editor instance with jQuery adapter logic + if (tinymce.adapter) + tinymce.adapter.patchEditor(editor); + + // #endif + + return editor; + }, + + /** + * Removes a editor instance from the collection. + * + * @method remove + * @param {tinymce.Editor} e Editor instance to remove. + * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. + */ + remove : function(editor) { + var t = this, i, editors = t.editors; + + // Not in the collection + if (!editors[editor.id]) + return null; + + delete editors[editor.id]; + + for (i = 0; i < editors.length; i++) { + if (editors[i] == editor) { + editors.splice(i, 1); + break; + } + } + + // Select another editor since the active one was removed + if (t.activeEditor == editor) + t._setActive(editors[0]); + + editor.destroy(); + t.onRemoveEditor.dispatch(t, editor); + + return editor; + }, + + /** + * Executes a specific command on the currently active editor. + * + * @method execCommand + * @param {String} c Command to perform for example Bold. + * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. + * @param {String} v Optional value parameter like for example an URL to a link. + * @return {Boolean} true/false if the command was executed or not. + */ + execCommand : function(c, u, v) { + var t = this, ed = t.get(v), w; + + // Manager commands + switch (c) { + case "mceFocus": + ed.focus(); + return true; + + case "mceAddEditor": + case "mceAddControl": + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + + return true; + + case "mceAddFrameControl": + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE) { + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + w.attachEvent('onunload', clr); + } + + v.page_window = null; + + return true; + + case "mceRemoveEditor": + case "mceRemoveControl": + if (ed) + ed.remove(); + + return true; + + case 'mceToggleEditor': + if (!ed) { + t.execCommand('mceAddControl', 0, v); + return true; + } + + if (ed.isHidden()) + ed.show(); + else + ed.hide(); + + return true; + } + + // Run command on active editor + if (t.activeEditor) + return t.activeEditor.execCommand(c, u, v); + + return false; + }, + + /** + * Executes a command on a specific editor by id. This method was added for compatibility with the 2.x branch. + * + * @deprecated Use the execCommand method of a editor instance instead. + * @method execInstanceCommand + * @param {String} id Editor id to perform the command on. + * @param {String} c Command to perform for example Bold. + * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not. + * @param {String} v Optional value parameter like for example an URL to a link. + * @return {Boolean} true/false if the command was executed or not. + */ + execInstanceCommand : function(id, c, u, v) { + var ed = this.get(id); + + if (ed) + return ed.execCommand(c, u, v); + + return false; + }, + + /** + * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. + * + * @method triggerSave + * @example + * // Saves all contents + * tinyMCE.triggerSave(); + */ + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + /** + * Adds a language pack, this gets called by the loaded language files like en.js. + * + * @method addI18n + * @param {String} p Prefix for the language items. For example en.myplugin + * @param {Object} o Name/Value collection with items to add to the language group. + */ + addI18n : function(p, o) { + var lo, i18n = this.i18n; + + if (!tinymce.is(p, 'string')) { + each(p, function(o, lc) { + each(o, function(o, g) { + each(o, function(o, k) { + if (g === 'common') + i18n[lc + '.' + k] = o; + else + i18n[lc + '.' + g + '.' + k] = o; + }); + }); + }); + } else { + each(o, function(o, k) { + i18n[p + '.' + k] = o; + }); + } + }, + + // Private methods + + _setActive : function(editor) { + this.selectedInstance = this.activeEditor = editor; + } + }); +})(tinymce); + +/** + * Alternative name for tinymce added for 2.x compatibility. + * + * @member + * @property tinyMCE + * @type tinymce + * @example + * // To initialize editor instances + * tinyMCE.init({ + * ... + * }); + */ + +/** + * Alternative name for tinymce added for compatibility. + * + * @member tinymce + * @property EditorManager + * @type tinymce + * @example + * // To initialize editor instances + * tinymce.EditorManager.get('editor'); + */ diff --git a/js/tiny_mce/classes/ForceBlocks.js b/js/tiny_mce/classes/ForceBlocks.js index b47e1599..818a2a85 100644 --- a/js/tiny_mce/classes/ForceBlocks.js +++ b/js/tiny_mce/classes/ForceBlocks.js @@ -1,696 +1,635 @@ -/** - * ForceBlocks.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Shorten names - var Event = tinymce.dom.Event, - isIE = tinymce.isIE, - isGecko = tinymce.isGecko, - isOpera = tinymce.isOpera, - each = tinymce.each, - extend = tinymce.extend, - TRUE = true, - FALSE = false; - - // Checks if the selection/caret is at the end of the specified block element - function isAtEnd(rng, par) { - var rng2 = par.ownerDocument.createRange(); - - rng2.setStart(rng.endContainer, rng.endOffset); - rng2.setEndAfter(par); - - // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element - return rng2.cloneContents().textContent.length == 0; - }; - - function isEmpty(n) { - n = n.innerHTML; - - n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars - n = n.replace(/<[^>]+>/g, ''); // Remove all tags - - return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; - }; - - function splitList(selection, dom, li) { - var listBlock, block; - - if (isEmpty(li)) { - listBlock = dom.getParent(li, 'ul,ol'); - - if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { - dom.split(listBlock, li); - block = dom.create('p', 0, '
    '); - dom.replace(block, li); - selection.select(block, 1); - } - - return FALSE; - } - - return TRUE; - }; - - /** - * This is a internal class and no method in this class should be called directly form the out side. - */ - tinymce.create('tinymce.ForceBlocks', { - ForceBlocks : function(ed) { - var t = this, s = ed.settings, elm; - - t.editor = ed; - t.dom = ed.dom; - elm = (s.forced_root_block || 'p').toLowerCase(); - s.element = elm.toUpperCase(); - - ed.onPreInit.add(t.setup, t); - - t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

    <\\\/p>||

    \\s+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

    [\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); - t.reBR2Nbsp = new RegExp(']+)>\\s*
    \\s*<\\\/p>|

    \\s*
    \\s*<\\\/p>'.replace(/p/g, elm), 'gi'); - - function padd(ed, o) { - if (isOpera) - o.content = o.content.replace(t.reOpera, ''); - - o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0'); - - if (!isIE && !isOpera && o.set) { - // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
    '); - o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
    '); - } else - o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0'); - }; - - ed.onBeforeSetContent.add(padd); - ed.onPostProcess.add(padd); - - if (s.forced_root_block) { - ed.onInit.add(t.forceRoots, t); - ed.onSetContent.add(t.forceRoots, t); - ed.onBeforeGetContent.add(t.forceRoots, t); - } - }, - - setup : function() { - var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection; - - // Force root blocks when typing and when getting output - if (s.forced_root_block) { - ed.onBeforeExecCommand.add(t.forceRoots, t); - ed.onKeyUp.add(t.forceRoots, t); - ed.onPreProcess.add(t.forceRoots, t); - } - - if (s.force_br_newlines) { - // Force IE to produce BRs on enter - if (isIE) { - ed.onKeyPress.add(function(ed, e) { - var n; - - if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { - selection.setContent('
    ', {format : 'raw'}); - n = dom.get('__'); - n.removeAttribute('id'); - selection.select(n); - selection.collapse(); - return Event.cancel(e); - } - }); - } - } - - if (!isIE && s.force_p_newlines) { - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) - Event.cancel(e); - }); - - if (isGecko) { - ed.onKeyDown.add(function(ed, e) { - if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) - t.backspaceDelete(e, e.keyCode == 8); - }); - } - } - - // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 - if (tinymce.isWebKit) { - function insertBr(ed) { - var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; - - // Insert BR element - rng.insertNode(br = dom.create('br')); - - // Place caret after BR - rng.setStartAfter(br); - rng.setEndAfter(br); - selection.setRng(rng); - - // Could not place caret after BR then insert an nbsp entity and move the caret - if (selection.getSel().focusNode == br.previousSibling) { - selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); - selection.collapse(TRUE); - } - - // Create a temporary DIV after the BR and get the position as it - // seems like getPos() returns 0 for text nodes and BR elements. - dom.insertAfter(div, br); - divYPos = dom.getPos(div).y; - dom.remove(div); - - // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 - if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. - ed.getWin().scrollTo(0, divYPos); - }; - - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) { - insertBr(ed); - Event.cancel(e); - } - }); - } - - // Padd empty inline elements within block elements - // For example:

    becomes

     

    - ed.onPreProcess.add(function(ed, o) { - each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { - if (isEmpty(p)) { - each(dom.select('span,em,strong,b,i', o.node), function(n) { - if (!n.hasChildNodes()) { - n.appendChild(ed.getDoc().createTextNode('\u00a0')); - return FALSE; // Break the loop one padding is enough - } - }); - } - }); - }); - - // IE specific fixes - if (isIE) { - // Replaces IE:s auto generated paragraphs with the specified element name - if (s.element != 'P') { - ed.onKeyPress.add(function(ed, e) { - t.lastElm = selection.getNode().nodeName; - }); - - ed.onKeyUp.add(function(ed, e) { - var bl, n = selection.getNode(), b = ed.getBody(); - - if (b.childNodes.length === 1 && n.nodeName == 'P') { - n = dom.rename(n, s.element); - selection.select(n); - selection.collapse(); - ed.nodeChanged(); - } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { - bl = dom.getParent(n, 'p'); - - if (bl) { - dom.rename(bl, s.element); - ed.nodeChanged(); - } - } - }); - } - } - }, - - find : function(n, t, s) { - var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1; - - while (n = w.nextNode()) { - c++; - - // Index by node - if (t == 0 && n == s) - return c; - - // Node by index - if (t == 1 && c == s) - return n; - } - - return -1; - }, - - forceRoots : function(ed, e) { - var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF; - var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid; - - // Fix for bug #1863847 - //if (e && e.keyCode == 13) - // return TRUE; - - // Wrap non blocks into blocks - for (i = nl.length - 1; i >= 0; i--) { - nx = nl[i]; - - // Ignore internal elements - if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { - bl = null; - continue; - } - - // Is text or non block element - if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) { - if (!bl) { - // Create new block but ignore whitespace - if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { - // Store selection - if (si == -2 && r) { - if (!isIE) { - // If selection is element then mark it - if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { - // Save the id of the selected element - eid = n.getAttribute("id"); - n.setAttribute("id", "__mce"); - } else { - // If element is inside body, might not be the case in contentEdiable mode - if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) { - so = r.startOffset; - eo = r.endOffset; - si = t.find(b, 0, r.startContainer); - ei = t.find(b, 0, r.endContainer); - } - } - } else { - tr = d.body.createTextRange(); - tr.moveToElementText(b); - tr.collapse(1); - bp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(1); - sp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(0); - le = (tr.move('character', c) * -1) - sp; - - si = sp - bp; - ei = le; - } - } - - // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE - // See: http://support.microsoft.com/kb/829907 - bl = ed.dom.create(ed.settings.forced_root_block); - nx.parentNode.replaceChild(bl, nx); - bl.appendChild(nx); - } - } else { - if (bl.hasChildNodes()) - bl.insertBefore(nx, bl.firstChild); - else - bl.appendChild(nx); - } - } else - bl = null; // Time to create new block - } - - // Restore selection - if (si != -2) { - if (!isIE) { - bl = b.getElementsByTagName(ed.settings.element)[0]; - r = d.createRange(); - - // Select last location or generated block - if (si != -1) - r.setStart(t.find(b, 1, si), so); - else - r.setStart(bl, 0); - - // Select last location or generated block - if (ei != -1) - r.setEnd(t.find(b, 1, ei), eo); - else - r.setEnd(bl, 0); - - if (s) { - s.removeAllRanges(); - s.addRange(r); - } - } else { - try { - r = s.createRange(); - r.moveToElementText(b); - r.collapse(1); - r.moveStart('character', si); - r.moveEnd('character', ei); - r.select(); - } catch (ex) { - // Ignore - } - } - } else if (!isIE && (n = ed.dom.get('__mce'))) { - // Restore the id of the selected element - if (eid) - n.setAttribute('id', eid); - else - n.removeAttribute('id'); - - // Move caret before selected element - r = d.createRange(); - r.setStartBefore(n); - r.setEndBefore(n); - se.setRng(r); - } - }, - - getParentBlock : function(n) { - var d = this.dom; - - return d.getParent(n, d.isBlock); - }, - - insertPara : function(e) { - var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; - var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; - - // If root blocks are forced then use Operas default behavior since it's really good -// Removed due to bug: #1853816 -// if (se.forced_root_block && isOpera) -// return TRUE; - - // Setup before range - rb = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - rb.setStart(s.anchorNode, s.anchorOffset); - rb.collapse(TRUE); - - // Setup after range - ra = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - ra.setStart(s.focusNode, s.focusOffset); - ra.collapse(TRUE); - - // Setup start/end points - dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; - sn = dir ? s.anchorNode : s.focusNode; - so = dir ? s.anchorOffset : s.focusOffset; - en = dir ? s.focusNode : s.anchorNode; - eo = dir ? s.focusOffset : s.anchorOffset; - - // If selection is in empty table cell - if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { - if (sn.firstChild.nodeName == 'BR') - dom.remove(sn.firstChild); // Remove BR - - // Create two new block elements - if (sn.childNodes.length == 0) { - ed.dom.add(sn, se.element, null, '
    '); - aft = ed.dom.add(sn, se.element, null, '
    '); - } else { - n = sn.innerHTML; - sn.innerHTML = ''; - ed.dom.add(sn, se.element, null, n); - aft = ed.dom.add(sn, se.element, null, '
    '); - } - - // Move caret into the last one - r = d.createRange(); - r.selectNodeContents(aft); - r.collapse(1); - ed.selection.setRng(r); - - return FALSE; - } - - // If the caret is in an invalid location in FF we need to move it into the first block - if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { - sn = en = sn.firstChild; - so = eo = 0; - rb = d.createRange(); - rb.setStart(sn, 0); - ra = d.createRange(); - ra.setStart(en, 0); - } - - // Never use body as start or end node - sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - sn = sn.nodeName == "BODY" ? sn.firstChild : sn; - en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - en = en.nodeName == "BODY" ? en.firstChild : en; - - // Get start and end blocks - sb = t.getParentBlock(sn); - eb = t.getParentBlock(en); - bn = sb ? sb.nodeName : se.element; // Get block name to create - - // Return inside list use default browser behavior - if (n = t.dom.getParent(sb, 'li,pre')) { - if (n.nodeName == 'LI') - return splitList(ed.selection, t.dom, n); - - return TRUE; - } - - // If caption or absolute layers then always generate new blocks within - if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - sb = null; - } - - // If caption or absolute layers then always generate new blocks within - if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - eb = null; - } - - // Use P instead - if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { - bn = se.element; - sb = eb = null; - } - - // Setup new before and after blocks - bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); - aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); - - // Remove id from after clone - aft.removeAttribute('id'); - - // Is header and cursor is at the end, then force paragraph under - if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) - aft = ed.dom.create(se.element); - - // Find start chop node - n = sc = sn; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - sc = n; - } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); - - // Find end chop node - n = ec = en; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - ec = n; - } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); - - // Place first chop part into before block element - if (sc.nodeName == bn) - rb.setStart(sc, 0); - else - rb.setStartBefore(sc); - - rb.setEnd(sn, so); - bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Place secnd chop part within new block element - try { - ra.setEndAfter(ec); - } catch(ex) { - //console.debug(s.focusNode, s.focusOffset); - } - - ra.setStart(en, eo); - aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Create range around everything - r = d.createRange(); - if (!sc.previousSibling && sc.parentNode.nodeName == bn) { - r.setStartBefore(sc.parentNode); - } else { - if (rb.startContainer.nodeName == bn && rb.startOffset == 0) - r.setStartBefore(rb.startContainer); - else - r.setStart(rb.startContainer, rb.startOffset); - } - - if (!ec.nextSibling && ec.parentNode.nodeName == bn) - r.setEndAfter(ec.parentNode); - else - r.setEnd(ra.endContainer, ra.endOffset); - - // Delete and replace it with new block elements - r.deleteContents(); - - if (isOpera) - ed.getWin().scrollTo(0, vp.y); - - // Never wrap blocks in blocks - if (bef.firstChild && bef.firstChild.nodeName == bn) - bef.innerHTML = bef.firstChild.innerHTML; - - if (aft.firstChild && aft.firstChild.nodeName == bn) - aft.innerHTML = aft.firstChild.innerHTML; - - // Padd empty blocks - if (isEmpty(bef)) - bef.innerHTML = '
    '; - - function appendStyles(e, en) { - var nl = [], nn, n, i; - - e.innerHTML = ''; - - // Make clones of style elements - if (se.keep_styles) { - n = en; - do { - // We only want style specific elements - if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { - nn = n.cloneNode(FALSE); - dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique - nl.push(nn); - } - } while (n = n.parentNode); - } - - // Append style elements to aft - if (nl.length > 0) { - for (i = nl.length - 1, nn = e; i >= 0; i--) - nn = nn.appendChild(nl[i]); - - // Padd most inner style element - nl[0].innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - return nl[0]; // Move caret to most inner element - } else - e.innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - }; - - // Fill empty afterblook with current style - if (isEmpty(aft)) - car = appendStyles(aft, en); - - // Opera needs this one backwards for older versions - if (isOpera && parseFloat(opera.version()) < 9.5) { - r.insertNode(bef); - r.insertNode(aft); - } else { - r.insertNode(aft); - r.insertNode(bef); - } - - // Normalize - aft.normalize(); - bef.normalize(); - - function first(n) { - return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n; - }; - - // Move cursor and scroll into view - r = d.createRange(); - r.selectNodeContents(isGecko ? first(car || aft) : car || aft); - r.collapse(1); - s.removeAllRanges(); - s.addRange(r); - - // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs - y = ed.dom.getPos(aft).y; - ch = aft.clientHeight; - - // Is element within viewport - if (y < vp.y || y + ch > vp.y + vp.h) { - ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks - //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); - } - - return FALSE; - }, - - backspaceDelete : function(e, bs) { - var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn; - - // The caret sometimes gets stuck in Gecko if you delete empty paragraphs - // This workaround removes the element by hand and moves the caret to the previous element - if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { - if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { - // Find previous block element - n = sc; - while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; - - if (n) { - if (sc != b.firstChild) { - // Find last text node - w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); - while (tn = w.nextNode()) - n = tn; - - // Place caret at the end of last text node - r = ed.getDoc().createRange(); - r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); - r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); - se.setRng(r); - - // Remove the target container - ed.dom.remove(sc); - } - - return Event.cancel(e); - } - } - } - - // Gecko generates BR elements here and there, we don't like those so lets remove them - function handler(e) { - var pr; - - e = e.target; - - // A new BR was created in a block element, remove it - if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) { - pr = e.previousSibling; - - Event.remove(b, 'DOMNodeInserted', handler); - - // Is there whitespace at the end of the node before then we might need the pesky BR - // to place the caret at a correct location see bug: #2013943 - if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue)) - return; - - // Only remove BR elements that got inserted in the middle of the text - if (e.previousSibling || e.nextSibling) - ed.dom.remove(e); - } - }; - - // Listen for new nodes - Event._add(b, 'DOMNodeInserted', handler); - - // Remove listener - window.setTimeout(function() { - Event._remove(b, 'DOMNodeInserted', handler); - }, 1); - } - }); -})(tinymce); +/** + * ForceBlocks.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Shorten names + var Event = tinymce.dom.Event, + isIE = tinymce.isIE, + isGecko = tinymce.isGecko, + isOpera = tinymce.isOpera, + each = tinymce.each, + extend = tinymce.extend, + TRUE = true, + FALSE = false; + + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + + // Checks if the selection/caret is at the end of the specified block element + function isAtEnd(rng, par) { + var rng2 = par.ownerDocument.createRange(); + + rng2.setStart(rng.endContainer, rng.endOffset); + rng2.setEndAfter(par); + + // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element + return rng2.cloneContents().textContent.length == 0; + }; + + function splitList(selection, dom, li) { + var listBlock, block; + + if (dom.isEmpty(li)) { + listBlock = dom.getParent(li, 'ul,ol'); + + if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { + dom.split(listBlock, li); + block = dom.create('p', 0, '
    '); + dom.replace(block, li); + selection.select(block, 1); + } + + return FALSE; + } + + return TRUE; + }; + + /** + * This is a internal class and no method in this class should be called directly form the out side. + */ + tinymce.create('tinymce.ForceBlocks', { + ForceBlocks : function(ed) { + var t = this, s = ed.settings, elm; + + t.editor = ed; + t.dom = ed.dom; + elm = (s.forced_root_block || 'p').toLowerCase(); + s.element = elm.toUpperCase(); + + ed.onPreInit.add(t.setup, t); + }, + + setup : function() { + var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements(); + + // Force root blocks + if (s.forced_root_block) { + function addRootBlocks() { + var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF; + + if (!node || node.nodeType !== 1) + return; + + // Check if node is wrapped in block + while (node != rootNode) { + if (blockElements[node.nodeName]) + return; + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + } else { + // Force control range into text range + if (rng.item) { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rng.item(0)); + } + + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + for (node = rootNode.firstChild; node; node) { + if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { + if (!rootBlockNode) { + rootBlockNode = dom.create(s.forced_root_block); + node.parentNode.insertBefore(rootBlockNode, node); + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + try { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) + rng.moveEnd('character', endOffset); + + rng.select(); + } catch (ex) { + // Ignore + } + } + + ed.nodeChanged(); + }; + + ed.onKeyUp.add(addRootBlocks); + ed.onClick.add(addRootBlocks); + } + + if (s.force_br_newlines) { + // Force IE to produce BRs on enter + if (isIE) { + ed.onKeyPress.add(function(ed, e) { + var n; + + if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { + selection.setContent('
    ', {format : 'raw'}); + n = dom.get('__'); + n.removeAttribute('id'); + selection.select(n); + selection.collapse(); + return Event.cancel(e); + } + }); + } + } + + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + selection.collapse(true); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } + + if (isGecko) { + ed.onKeyDown.add(function(ed, e) { + if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) + t.backspaceDelete(e, e.keyCode == 8); + }); + } + } + + // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 + if (tinymce.isWebKit) { + function insertBr(ed) { + var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; + + // Insert BR element + rng.insertNode(br = dom.create('br')); + + // Place caret after BR + rng.setStartAfter(br); + rng.setEndAfter(br); + selection.setRng(rng); + + // Could not place caret after BR then insert an nbsp entity and move the caret + if (selection.getSel().focusNode == br.previousSibling) { + selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); + selection.collapse(TRUE); + } + + // Create a temporary DIV after the BR and get the position as it + // seems like getPos() returns 0 for text nodes and BR elements. + dom.insertAfter(div, br); + divYPos = dom.getPos(div).y; + dom.remove(div); + + // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 + if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. + ed.getWin().scrollTo(0, divYPos); + }; + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { + insertBr(ed); + Event.cancel(e); + } + }); + } + + // IE specific fixes + if (isIE) { + // Replaces IE:s auto generated paragraphs with the specified element name + if (s.element != 'P') { + ed.onKeyPress.add(function(ed, e) { + t.lastElm = selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, n = selection.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = dom.rename(n, s.element); + selection.select(n); + selection.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = dom.getParent(n, 'p'); + + if (bl) { + dom.rename(bl, s.element); + ed.nodeChanged(); + } + } + }); + } + } + }, + + getParentBlock : function(n) { + var d = this.dom; + + return d.getParent(n, d.isBlock); + }, + + insertPara : function(e) { + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + + ed.undoManager.beforeChange(); + + // If root blocks are forced then use Operas default behavior since it's really good +// Removed due to bug: #1853816 +// if (se.forced_root_block && isOpera) +// return TRUE; + + // Setup before range + rb = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + rb.setStart(s.anchorNode, s.anchorOffset); + rb.collapse(TRUE); + + // Setup after range + ra = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + ra.setStart(s.focusNode, s.focusOffset); + ra.collapse(TRUE); + + // Setup start/end points + dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; + sn = dir ? s.anchorNode : s.focusNode; + so = dir ? s.anchorOffset : s.focusOffset; + en = dir ? s.focusNode : s.anchorNode; + eo = dir ? s.focusOffset : s.anchorOffset; + + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + if (sn.firstChild.nodeName == 'BR') + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + if (sn.childNodes.length == 0) { + ed.dom.add(sn, se.element, null, '
    '); + aft = ed.dom.add(sn, se.element, null, '
    '); + } else { + n = sn.innerHTML; + sn.innerHTML = ''; + ed.dom.add(sn, se.element, null, n); + aft = ed.dom.add(sn, se.element, null, '
    '); + } + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return FALSE; + } + + // If the caret is in an invalid location in FF we need to move it into the first block + if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { + sn = en = sn.firstChild; + so = eo = 0; + rb = d.createRange(); + rb.setStart(sn, 0); + ra = d.createRange(); + ra.setStart(en, 0); + } + + // If the body is totally empty add a BR element this might happen on webkit + if (!d.body.hasChildNodes()) { + d.body.appendChild(dom.create('br')); + } + + // Never use body as start or end node + sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + sn = sn.nodeName == "BODY" ? sn.firstChild : sn; + en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + en = en.nodeName == "BODY" ? en.firstChild : en; + + // Get start and end blocks + sb = t.getParentBlock(sn); + eb = t.getParentBlock(en); + bn = sb ? sb.nodeName : se.element; // Get block name to create + + // Return inside list use default browser behavior + if (n = t.dom.getParent(sb, 'li,pre')) { + if (n.nodeName == 'LI') + return splitList(ed.selection, t.dom, n); + + return TRUE; + } + + // If caption or absolute layers then always generate new blocks within + if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + sb = null; + } + + // If caption or absolute layers then always generate new blocks within + if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + eb = null; + } + + // Use P instead + if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { + bn = se.element; + sb = eb = null; + } + + // Setup new before and after blocks + bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); + aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); + + // Remove id from after clone + aft.removeAttribute('id'); + + // Is header and cursor is at the end, then force paragraph under + if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) + aft = ed.dom.create(se.element); + + // Find start chop node + n = sc = sn; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + sc = n; + } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); + + // Find end chop node + n = ec = en; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + ec = n; + } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); + + // Place first chop part into before block element + if (sc.nodeName == bn) + rb.setStart(sc, 0); + else + rb.setStartBefore(sc); + + rb.setEnd(sn, so); + bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Place secnd chop part within new block element + try { + ra.setEndAfter(ec); + } catch(ex) { + //console.debug(s.focusNode, s.focusOffset); + } + + ra.setStart(en, eo); + aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Create range around everything + r = d.createRange(); + if (!sc.previousSibling && sc.parentNode.nodeName == bn) { + r.setStartBefore(sc.parentNode); + } else { + if (rb.startContainer.nodeName == bn && rb.startOffset == 0) + r.setStartBefore(rb.startContainer); + else + r.setStart(rb.startContainer, rb.startOffset); + } + + if (!ec.nextSibling && ec.parentNode.nodeName == bn) + r.setEndAfter(ec.parentNode); + else + r.setEnd(ra.endContainer, ra.endOffset); + + // Delete and replace it with new block elements + r.deleteContents(); + + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + + // Never wrap blocks in blocks + if (bef.firstChild && bef.firstChild.nodeName == bn) + bef.innerHTML = bef.firstChild.innerHTML; + + if (aft.firstChild && aft.firstChild.nodeName == bn) + aft.innerHTML = aft.firstChild.innerHTML; + + function appendStyles(e, en) { + var nl = [], nn, n, i; + + e.innerHTML = ''; + + // Make clones of style elements + if (se.keep_styles) { + n = en; + do { + // We only want style specific elements + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { + nn = n.cloneNode(FALSE); + dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique + nl.push(nn); + } + } while (n = n.parentNode); + } + + // Append style elements to aft + if (nl.length > 0) { + for (i = nl.length - 1, nn = e; i >= 0; i--) + nn = nn.appendChild(nl[i]); + + // Padd most inner style element + nl[0].innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + return nl[0]; // Move caret to most inner element + } else + e.innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + }; + + // Padd empty blocks + if (dom.isEmpty(bef)) + appendStyles(bef, sn); + + // Fill empty afterblook with current style + if (dom.isEmpty(aft)) + car = appendStyles(aft, en); + + // Opera needs this one backwards for older versions + if (isOpera && parseFloat(opera.version()) < 9.5) { + r.insertNode(bef); + r.insertNode(aft); + } else { + r.insertNode(aft); + r.insertNode(bef); + } + + // Normalize + aft.normalize(); + bef.normalize(); + + // Move cursor and scroll into view + ed.selection.select(aft, true); + ed.selection.collapse(true); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + //ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + 25 > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ + } + + ed.undoManager.add(); + + return FALSE; + }, + + backspaceDelete : function(e, bs) { + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } + + // The caret sometimes gets stuck in Gecko if you delete empty paragraphs + // This workaround removes the element by hand and moves the caret to the previous element + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + + if (n) { + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + + return Event.cancel(e); + } + } + } + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/Formatter.js b/js/tiny_mce/classes/Formatter.js index bc8c2742..4ef13239 100644 --- a/js/tiny_mce/classes/Formatter.js +++ b/js/tiny_mce/classes/Formatter.js @@ -1,1497 +1,2018 @@ -/** - * Formatter.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - /** - * Text formatter engine class. This class is used to apply formats like bold, italic, font size - * etc to the current selection or specific nodes. This engine was build to replace the browsers - * default formatting logic for execCommand due to it's inconsistant and buggy behavior. - * - * @class tinymce.Formatter - * @example - * tinymce.activeEditor.formatter.register('mycustomformat', { - * inline : 'span', - * styles : {color : '#ff0000'} - * }); - * - * tinymce.activeEditor.formatter.apply('mycustomformat'); - */ - - /** - * Constructs a new formatter instance. - * - * @constructor Formatter - * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. - */ - tinymce.Formatter = function(ed) { - var formats = {}, - each = tinymce.each, - dom = ed.dom, - selection = ed.selection, - TreeWalker = tinymce.dom.TreeWalker, - rangeUtils = new tinymce.dom.RangeUtils(dom), - isValid = ed.schema.isValid, - isBlock = dom.isBlock, - forcedRootBlock = ed.settings.forced_root_block, - nodeIndex = dom.nodeIndex, - INVISIBLE_CHAR = '\uFEFF', - MCE_ATTR_RE = /^(src|href|style)$/, - FALSE = false, - TRUE = true, - undefined, - pendingFormats = {apply : [], remove : []}; - - function isArray(obj) { - return obj instanceof Array; - }; - - function getParents(node, selector) { - return dom.getParents(node, selector, dom.getRoot()); - }; - - function isCaretNode(node) { - return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); - }; - - // Public functions - - /** - * Returns the format by name or all formats if no name is specified. - * - * @method get - * @param {String} name Optional name to retrive by. - * @return {Array/Object} Array/Object with all registred formats or a specific format. - */ - function get(name) { - return name ? formats[name] : formats; - }; - - /** - * Registers a specific format by name. - * - * @method register - * @param {Object/String} name Name of the format for example "bold". - * @param {Object/Array} format Optional format object or array of format variants can only be omitted if the first arg is an object. - */ - function register(name, format) { - if (name) { - if (typeof(name) !== 'string') { - each(name, function(format, name) { - register(name, format); - }); - } else { - // Force format into array and add it to internal collection - format = format.length ? format : [format]; - - each(format, function(format) { - // Set deep to false by default on selector formats this to avoid removing - // alignment on images inside paragraphs when alignment is changed on paragraphs - if (format.deep === undefined) - format.deep = !format.selector; - - // Default to true - if (format.split === undefined) - format.split = !format.selector || format.inline; - - // Default to true - if (format.remove === undefined && format.selector && !format.inline) - format.remove = 'none'; - - // Mark format as a mixed format inline + block level - if (format.selector && format.inline) { - format.mixed = true; - format.block_expand = true; - } - - // Split classes if needed - if (typeof(format.classes) === 'string') - format.classes = format.classes.split(/\s+/); - }); - - formats[name] = format; - } - } - }; - - /** - * Applies the specified format to the current selection or specified node. - * - * @method apply - * @param {String} name Name of format to apply. - * @param {Object} vars Optional list of variables to replace within format before applying it. - * @param {Node} node Optional node to apply the format to defaults to current selection. - */ - function apply(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, rng, i; - - /** - * Moves the start to the first suitable text node. - */ - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, - walker, node; - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1 || container.nodeValue === "") { - walker = new TreeWalker(container.childNodes[offset]); - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { - rng.setStart(node, 0); - break; - } - } - } - - return rng; - }; - - function setElementFormat(elm, fmt) { - fmt = fmt || format; - - if (elm) { - each(fmt.styles, function(value, name) { - dom.setStyle(elm, name, replaceVars(value, vars)); - }); - - each(fmt.attributes, function(value, name) { - dom.setAttrib(elm, name, replaceVars(value, vars)); - }); - - each(fmt.classes, function(value) { - value = replaceVars(value, vars); - - if (!dom.hasClass(elm, value)) - dom.addClass(elm, value); - }); - } - }; - - function applyRngStyle(rng) { - var newWrappers = [], wrapName, wrapElm; - - // Setup wrapper element - wrapName = format.inline || format.block; - wrapElm = dom.create(wrapName); - setElementFormat(wrapElm); - - rangeUtils.walk(rng, function(nodes) { - var currentWrapElm; - - /** - * Process a list of nodes wrap them. - */ - function process(node) { - var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; - - // Stop wrapping on br elements - if (isEq(nodeName, 'br')) { - currentWrapElm = 0; - - // Remove any br elements when we wrap things - if (format.block) - dom.remove(node); - - return; - } - - // If node is wrapper type - if (format.wrapper && matchNode(node, name, vars)) { - currentWrapElm = 0; - return; - } - - // Can we rename the block - if (format.block && !format.wrapper && isTextBlock(nodeName)) { - node = dom.rename(node, wrapName); - setElementFormat(node); - newWrappers.push(node); - currentWrapElm = 0; - return; - } - - // Handle selector patterns - if (format.selector) { - // Look for matching formats - each(formatList, function(format) { - if (dom.is(node, format.selector) && !isCaretNode(node)) { - setElementFormat(node, format); - found = true; - } - }); - - // Contine processing if a selector match wasn't found and a inline element is defined - if (!format.inline || found) { - currentWrapElm = 0; - return; - } - } - - // Is it valid to wrap this item - if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { - // Start wrapping - if (!currentWrapElm) { - // Wrap the node - currentWrapElm = wrapElm.cloneNode(FALSE); - node.parentNode.insertBefore(currentWrapElm, node); - newWrappers.push(currentWrapElm); - } - - currentWrapElm.appendChild(node); - } else { - // Start a new wrapper for possible children - currentWrapElm = 0; - - each(tinymce.grep(node.childNodes), process); - - // End the last wrapper - currentWrapElm = 0; - } - }; - - // Process siblings from range - each(nodes, process); - }); - - // Cleanup - each(newWrappers, function(node) { - var childCount; - - function getChildCount(node) { - var count = 0; - - each(node.childNodes, function(node) { - if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) - count++; - }); - - return count; - }; - - function mergeStyles(node) { - var child, clone; - - each(node.childNodes, function(node) { - if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { - child = node; - return FALSE; // break loop - } - }); - - // If child was found and of the same type as the current node - if (child && matchName(child, format)) { - clone = child.cloneNode(FALSE); - setElementFormat(clone); - - dom.replace(clone, node, TRUE); - dom.remove(child, 1); - } - - return clone || node; - }; - - childCount = getChildCount(node); - - // Remove empty nodes - if (childCount === 0) { - dom.remove(node, 1); - return; - } - - if (format.inline || format.wrapper) { - // Merges the current node with it's children of similar type to reduce the number of elements - if (!format.exact && childCount === 1) - node = mergeStyles(node); - - // Remove/merge children - each(formatList, function(format) { - // Merge all children of similar type will move styles from child to parent - // this: text - // will become: text - each(dom.select(format.inline, node), function(child) { - removeFormat(format, vars, child, format.exact ? child : null); - }); - }); - - // Look for parent with similar style format - dom.getParent(node.parentNode, function(parent) { - if (matchNode(parent, name, vars)) { - dom.remove(node, 1); - node = 0; - return TRUE; - } - }); - - // Merge next and previous siblings if they are similar texttext becomes texttext - if (node) { - node = mergeSiblings(getNonWhiteSpaceSibling(node), node); - node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); - } - } - }); - }; - - if (format) { - if (node) { - rng = dom.createRng(); - - rng.setStartBefore(node); - rng.setEndAfter(node); - - applyRngStyle(rng); - } else { - if (!selection.isCollapsed() || !format.inline) { - // Apply formatting to selection - bookmark = selection.getBookmark(); - applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); - - selection.moveToBookmark(bookmark); - selection.setRng(moveStart(selection.getRng(TRUE))); - ed.nodeChanged(); - } else - performCaretAction('apply', name, vars); - } - } - }; - - /** - * Removes the specified format from the current selection or specified node. - * - * @method remove - * @param {String} name Name of format to remove. - * @param {Object} vars Optional list of variables to replace within format before removing it. - * @param {Node} node Optional node to remove the format from defaults to current selection. - */ - function remove(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, i, rng; - - // Merges the styles for each node - function process(node) { - var children, i, l; - - // Grab the children first since the nodelist might be changed - children = tinymce.grep(node.childNodes); - - // Process current node - for (i = 0, l = formatList.length; i < l; i++) { - if (removeFormat(formatList[i], vars, node, node)) - break; - } - - // Process the children - if (format.deep) { - for (i = 0, l = children.length; i < l; i++) - process(children[i]); - } - }; - - function findFormatRoot(container) { - var formatRoot; - - // Find format root - each(getParents(container.parentNode).reverse(), function(parent) { - var format; - - // Find format root element - if (!formatRoot && parent.id != '_start' && parent.id != '_end') { - // Is the node matching the format we are looking for - format = matchNode(parent, name, vars); - if (format && format.split !== false) - formatRoot = parent; - } - }); - - return formatRoot; - }; - - function wrapAndSplit(format_root, container, target, split) { - var parent, clone, lastClone, firstClone, i, formatRootParent; - - // Format root found then clone formats and split it - if (format_root) { - formatRootParent = format_root.parentNode; - - for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { - clone = parent.cloneNode(FALSE); - - for (i = 0; i < formatList.length; i++) { - if (removeFormat(formatList[i], vars, clone, clone)) { - clone = 0; - break; - } - } - - // Build wrapper node - if (clone) { - if (lastClone) - clone.appendChild(lastClone); - - if (!firstClone) - firstClone = clone; - - lastClone = clone; - } - } - - // Never split block elements if the format is mixed - if (split && (!format.mixed || !isBlock(format_root))) - container = dom.split(format_root, container); - - // Wrap container in cloned formats - if (lastClone) { - target.parentNode.insertBefore(lastClone, target); - firstClone.appendChild(target); - } - } - - return container; - }; - - function splitToFormatRoot(container) { - return wrapAndSplit(findFormatRoot(container), container, container, true); - }; - - function unwrap(start) { - var node = dom.get(start ? '_start' : '_end'), - out = node[start ? 'firstChild' : 'lastChild']; - - dom.remove(node, 1); - - return out; - }; - - function removeRngStyle(rng) { - var startContainer, endContainer; - - rng = expandRng(rng, formatList, TRUE); - - if (format.split) { - startContainer = getContainer(rng, TRUE); - endContainer = getContainer(rng); - - if (startContainer != endContainer) { - // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); - - // Split start/end - splitToFormatRoot(startContainer); - splitToFormatRoot(endContainer); - - // Unwrap start/end to get real elements again - startContainer = unwrap(TRUE); - endContainer = unwrap(); - } else - startContainer = endContainer = splitToFormatRoot(startContainer); - - // Update range positions since they might have changed after the split operations - rng.startContainer = startContainer.parentNode; - rng.startOffset = nodeIndex(startContainer); - rng.endContainer = endContainer.parentNode; - rng.endOffset = nodeIndex(endContainer) + 1; - } - - // Remove items between start/end - rangeUtils.walk(rng, function(nodes) { - each(nodes, function(node) { - process(node); - }); - }); - }; - - // Handle node - if (node) { - rng = dom.createRng(); - rng.setStartBefore(node); - rng.setEndAfter(node); - removeRngStyle(rng); - return; - } - - if (!selection.isCollapsed() || !format.inline) { - bookmark = selection.getBookmark(); - removeRngStyle(selection.getRng(TRUE)); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } else - performCaretAction('remove', name, vars); - }; - - /** - * Toggles the specifed format on/off. - * - * @method toggle - * @param {String} name Name of format to apply/remove. - * @param {Object} vars Optional list of variables to replace within format before applying/removing it. - * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. - */ - function toggle(name, vars, node) { - if (match(name, vars, node)) - remove(name, vars, node); - else - apply(name, vars, node); - }; - - /** - * Return true/false if the specified node has the specified format. - * - * @method matchNode - * @param {Node} node Node to check the format on. - * @param {String} name Format name to check. - * @param {Object} vars Optional list of variables to replace before checking it. - * @return {Object} Returns the format object it matches or undefined if it doesn't match. - */ - function matchNode(node, name, vars) { - var formatList = get(name), format, i, classes; - - function matchItems(node, format, item_name) { - var key, value, items = format[item_name], i; - - // Check all items - if (items) { - // Non indexed object - if (items.length === undefined) { - for (key in items) { - if (items.hasOwnProperty(key)) { - if (item_name === 'attributes') - value = dom.getAttrib(node, key); - else - value = getStyle(node, key); - - if (!isEq(value, replaceVars(items[key], vars))) - return; - } - } - } else { - // Only one match needed for indexed arrays - for (i = 0; i < items.length; i++) { - if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) - return format; - } - } - } - - return format; - }; - - if (formatList && node) { - // Check each format in list - for (i = 0; i < formatList.length; i++) { - format = formatList[i]; - - // Name name, attributes, styles and classes - if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { - // Match classes - if (classes = format.classes) { - for (i = 0; i < classes.length; i++) { - if (!dom.hasClass(node, classes[i])) - return; - } - } - - return format; - } - } - } - }; - - /** - * Matches the current selection or specifed node against the specified format name. - * - * @method match - * @param {String} name Name of format to match. - * @param {Object} vars Optional list of variables to replace before checking it. - * @param {Node} node Optional node to check. - * @return {boolean} true/false if the specified selection/node matches the format. - */ - function match(name, vars, node) { - var startNode, i; - - function matchParents(node) { - // Find first node with similar format settings - node = dom.getParent(node, function(node) { - return !!matchNode(node, name, vars); - }); - - // Do an exact check on the similar format element - return matchNode(node, name, vars); - }; - - // Check specified node - if (node) - return matchParents(node); - - // Check pending formats - if (selection.isCollapsed()) { - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - if (pendingFormats.apply[i].name == name) - return true; - } - - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - if (pendingFormats.remove[i].name == name) - return false; - } - - return matchParents(selection.getNode()); - } - - // Check selected node - node = selection.getNode(); - if (matchParents(node)) - return TRUE; - - // Check start node if it's different - startNode = selection.getStart(); - if (startNode != node) { - if (matchParents(startNode)) - return TRUE; - } - - return FALSE; - }; - - /** - * Matches the current selection against the array of formats and returns a new array with matching formats. - * - * @method matchAll - * @param {Array} names Name of format to match. - * @param {Object} vars Optional list of variables to replace before checking it. - * @return {Array} Array with matched formats. - */ - function matchAll(names, vars) { - var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; - - // If the selection is collapsed then check pending formats - if (selection.isCollapsed()) { - for (ni = 0; ni < names.length; ni++) { - // If the name is to be removed, then stop it from being added - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - name = names[ni]; - - if (pendingFormats.remove[i].name == name) { - checkedMap[name] = true; - break; - } - } - } - - // If the format is to be applied - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - for (ni = 0; ni < names.length; ni++) { - name = names[ni]; - - if (!checkedMap[name] && pendingFormats.apply[i].name == name) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - } - } - - // Check start of selection for formats - startElement = selection.getStart(); - dom.getParent(startElement, function(node) { - var i, name; - - for (i = 0; i < names.length; i++) { - name = names[i]; - - if (!checkedMap[name] && matchNode(node, name, vars)) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - }); - - return matchedFormatNames; - }; - - /** - * Returns true/false if the specified format can be applied to the current selection or not. It will currently only check the state for selector formats, it returns true on all other format types. - * - * @method canApply - * @param {String} name Name of format to check. - * @return {boolean} true/false if the specified format can be applied to the current selection/node. - */ - function canApply(name) { - var formatList = get(name), startNode, parents, i, x, selector; - - if (formatList) { - startNode = selection.getStart(); - parents = getParents(startNode); - - for (x = formatList.length - 1; x >= 0; x--) { - selector = formatList[x].selector; - - // Format is not selector based, then always return TRUE - if (!selector) - return TRUE; - - for (i = parents.length - 1; i >= 0; i--) { - if (dom.is(parents[i], selector)) - return TRUE; - } - } - } - - return FALSE; - }; - - // Expose to public - tinymce.extend(this, { - get : get, - register : register, - apply : apply, - remove : remove, - toggle : toggle, - match : match, - matchAll : matchAll, - matchNode : matchNode, - canApply : canApply - }); - - // Private functions - - /** - * Checks if the specified nodes name matches the format inline/block or selector. - * - * @private - * @param {Node} node Node to match agains the specified format. - * @param {Object} format Format object o match with. - * @return {boolean} true/false if the format matches. - */ - function matchName(node, format) { - // Check for inline match - if (isEq(node, format.inline)) - return TRUE; - - // Check for block match - if (isEq(node, format.block)) - return TRUE; - - // Check for selector match - if (format.selector) - return dom.is(node, format.selector); - }; - - /** - * Compares two string/nodes regardless of their case. - * - * @private - * @param {String/Node} Node or string to compare. - * @param {String/Node} Node or string to compare. - * @return {boolean} True/false if they match. - */ - function isEq(str1, str2) { - str1 = str1 || ''; - str2 = str2 || ''; - - str1 = '' + (str1.nodeName || str1); - str2 = '' + (str2.nodeName || str2); - - return str1.toLowerCase() == str2.toLowerCase(); - }; - - /** - * Returns the style by name on the specified node. This method modifies the style - * contents to make it more easy to match. This will resolve a few browser issues. - * - * @private - * @param {Node} node to get style from. - * @param {String} name Style name to get. - * @return {String} Style item value. - */ - function getStyle(node, name) { - var styleVal = dom.getStyle(node, name); - - // Force the format to hex - if (name == 'color' || name == 'backgroundColor') - styleVal = dom.toHex(styleVal); - - // Opera will return bold as 700 - if (name == 'fontWeight' && styleVal == 700) - styleVal = 'bold'; - - return '' + styleVal; - }; - - /** - * Replaces variables in the value. The variable format is %var. - * - * @private - * @param {String} value Value to replace variables in. - * @param {Object} vars Name/value array with variables to replace. - * @return {String} New value with replaced variables. - */ - function replaceVars(value, vars) { - if (typeof(value) != "string") - value = value(vars); - else if (vars) { - value = value.replace(/%(\w+)/g, function(str, name) { - return vars[name] || str; - }); - } - - return value; - }; - - function isWhiteSpaceNode(node) { - return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue); - }; - - function wrap(node, name, attrs) { - var wrapper = dom.create(name, attrs); - - node.parentNode.insertBefore(wrapper, node); - wrapper.appendChild(node); - - return wrapper; - }; - - /** - * Expands the specified range like object to depending on format. - * - * For example on block formats it will move the start/end position - * to the beginning of the current block. - * - * @private - * @param {Object} rng Range like object. - * @param {Array} formats Array with formats to expand by. - * @return {Object} Expanded range like object. - */ - function expandRng(rng, format, remove) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, sibling, lastIdx; - - // This function walks up the tree if there is no siblings before/after the node - function findParentContainer(container, child_name, sibling_name, root) { - var parent, child; - - root = root || dom.getRoot(); - - for (;;) { - // Check if we can move up are we at root level or body level - parent = container.parentNode; - - // Stop expanding on block elements or root depending on format - if (parent == root || (!format[0].block_expand && isBlock(parent))) - return container; - - for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return container; - - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return container; - } - - container = container.parentNode; - } - - return container; - }; - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - lastIdx = startContainer.childNodes.length - 1; - startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; - - if (startContainer.nodeType == 3) - startOffset = 0; - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - lastIdx = endContainer.childNodes.length - 1; - endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; - - if (endContainer.nodeType == 3) - endOffset = endContainer.nodeValue.length; - } - - // Exclude bookmark nodes if possible - if (isBookmarkNode(startContainer.parentNode)) - startContainer = startContainer.parentNode; - - if (isBookmarkNode(startContainer)) - startContainer = startContainer.nextSibling || startContainer; - - if (isBookmarkNode(endContainer.parentNode)) - endContainer = endContainer.parentNode; - - if (isBookmarkNode(endContainer)) - endContainer = endContainer.previousSibling || endContainer; - - // Move start/end point up the tree if the leaves are sharp and if we are in different containers - // Example * becomes !: !

    *texttext*

    ! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - function findSelectorEndPoint(container, sibling_name) { - var parents, i, y; - - if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) - container = container[sibling_name]; - - parents = getParents(container); - for (i = 0; i < parents.length; i++) { - for (y = 0; y < format.length; y++) { - if (dom.is(parents[i], format[y].selector)) - return parents[i]; - } - } - - return container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - function findBlockEndPoint(container, sibling_name, sibling_name2) { - var node; - - // Expand to block of similar type - if (!format[0].wrapper) - node = dom.getParent(container, format[0].block); - - // Expand to first wrappable block element or any block element - if (!node) - node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); - - // Exclude inner lists from wrapping - if (node && format[0].wrapper) - node = getParents(node, 'ul,ol').reverse()[0] || node; - - // Didn't find a block element look for first/last wrappable element - if (!node) { - node = container; - - while (node[sibling_name] && !isBlock(node[sibling_name])) { - node = node[sibling_name]; - - // Break on BR but include it will be removed later on - // we can't remove it now since we need to check if it can be wrapped - if (isEq(node, 'br')) - break; - } - } - - return node || container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - - if (!isBlock(endContainer)) - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer : startContainer, - startOffset : startOffset, - endContainer : endContainer, - endOffset : endOffset - }; - } - - /** - * Removes the specified format for the specified node. It will also remove the node if it doesn't have - * any attributes if the format specifies it to do so. - * - * @private - * @param {Object} format Format object with items to remove from node. - * @param {Object} vars Name/value object with variables to apply to format. - * @param {Node} node Node to remove the format styles on. - * @param {Node} compare_node Optional compare node, if specidied the styles will be compared to that node. - * @return {Boolean} True/false if the node was removed or not. - */ - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format)) - return FALSE; - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(getStyle(compare_node, name), value)) - dom.setStyle(node, name, ''); - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') == '') { - node.removeAttribute('style'); - node.removeAttribute('_mce_style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\w+/.test(cls)) - valueOut += (valueOut ? ' ' : '') + cls; - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") - node.removeAttribute('className'); - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) - node.removeAttribute('_mce_' + name); - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) - dom.removeClass(node, value); - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName.indexOf('_') !== 0) - return FALSE; - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - }; - - /** - * Removes the node and wrap it's children in paragraphs before doing so or - * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. - * - * If the div in the node below gets removed: - * text
    text
    text - * - * Output becomes: - * text

    text
    text - * - * So when the div is removed the result is: - * text
    text
    text - * - * @private - * @param {Node} node Node to remove + apply BR/P elements to. - * @param {Object} format Format rule. - * @return {Node} Input node. - */ - function removeNode(node, format) { - var parentNode = node.parentNode, rootBlockElm; - - if (format.block) { - if (!forcedRootBlock) { - function find(node, next, inc) { - node = getNonWhiteSpaceSibling(node, next, inc); - - return !node || (node.nodeName == 'BR' || isBlock(node)); - }; - - // Append BR elements if needed before we remove the block - if (isBlock(node) && !isBlock(parentNode)) { - if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) - node.insertBefore(dom.create('br'), node.firstChild); - - if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) - node.appendChild(dom.create('br')); - } - } else { - // Wrap the block in a forcedRootBlock if we are at the root of document - if (parentNode == dom.getRoot()) { - if (!format.list_block || !isEq(node, format.list_block)) { - each(tinymce.grep(node.childNodes), function(node) { - if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { - if (!rootBlockElm) - rootBlockElm = wrap(node, forcedRootBlock); - else - rootBlockElm.appendChild(node); - } else - rootBlockElm = 0; - }); - } - } - } - } - - // Never remove nodes that isn't the specified inline element if a selector is specified too - if (format.selector && format.inline && !isEq(format.inline, node)) - return; - - dom.remove(node, 1); - }; - - /** - * Returns the next/previous non whitespace node. - * - * @private - * @param {Node} node Node to start at. - * @param {boolean} next (Optional) Include next or previous node defaults to previous. - * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false. - * @return {Node} Next or previous node or undefined if it wasn't found. - */ - function getNonWhiteSpaceSibling(node, next, inc) { - if (node) { - next = next ? 'nextSibling' : 'previousSibling'; - - for (node = inc ? node : node[next]; node; node = node[next]) { - if (node.nodeType == 1 || !isWhiteSpaceNode(node)) - return node; - } - } - }; - - /** - * Checks if the specified node is a bookmark node or not. - * - * @param {Node} node Node to check if it's a bookmark node or not. - * @return {Boolean} true/false if the node is a bookmark node. - */ - function isBookmarkNode(node) { - return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; - }; - - /** - * Merges the next/previous sibling element if they match. - * - * @private - * @param {Node} prev Previous node to compare/merge. - * @param {Node} next Next node to compare/merge. - * @return {Node} Next node if we didn't merge and prev node if we did. - */ - function mergeSiblings(prev, next) { - var marker, sibling, tmpSibling; - - /** - * Compares two nodes and checks if it's attributes and styles matches. - * This doesn't compare classes as items since their order is significant. - * - * @private - * @param {Node} node1 First node to compare with. - * @param {Node} node2 Secont node to compare with. - * @return {boolean} True/false if the nodes are the same or not. - */ - function compareElements(node1, node2) { - // Not the same name - if (node1.nodeName != node2.nodeName) - return FALSE; - - /** - * Returns all the nodes attributes excluding internal ones, styles and classes. - * - * @private - * @param {Node} node Node to get attributes from. - * @return {Object} Name/value object with attributes and attribute values. - */ - function getAttribs(node) { - var attribs = {}; - - each(dom.getAttribs(node), function(attr) { - var name = attr.nodeName.toLowerCase(); - - // Don't compare internal attributes or style - if (name.indexOf('_') !== 0 && name !== 'style') - attribs[name] = dom.getAttrib(node, name); - }); - - return attribs; - }; - - /** - * Compares two objects checks if it's key + value exists in the other one. - * - * @private - * @param {Object} obj1 First object to compare. - * @param {Object} obj2 Second object to compare. - * @return {boolean} True/false if the objects matches or not. - */ - function compareObjects(obj1, obj2) { - var value, name; - - for (name in obj1) { - // Obj1 has item obj2 doesn't have - if (obj1.hasOwnProperty(name)) { - value = obj2[name]; - - // Obj2 doesn't have obj1 item - if (value === undefined) - return FALSE; - - // Obj2 item has a different value - if (obj1[name] != value) - return FALSE; - - // Delete similar value - delete obj2[name]; - } - } - - // Check if obj 2 has something obj 1 doesn't have - for (name in obj2) { - // Obj2 has item obj1 doesn't have - if (obj2.hasOwnProperty(name)) - return FALSE; - } - - return TRUE; - }; - - // Attribs are not the same - if (!compareObjects(getAttribs(node1), getAttribs(node2))) - return FALSE; - - // Styles are not the same - if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) - return FALSE; - - return TRUE; - }; - - // Check if next/prev exists and that they are elements - if (prev && next) { - function findElementSibling(node, sibling_name) { - for (sibling = node; sibling; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return node; - - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return sibling; - } - - return node; - }; - - // If previous sibling is empty then jump over it - prev = findElementSibling(prev, 'previousSibling'); - next = findElementSibling(next, 'nextSibling'); - - // Compare next and previous nodes - if (compareElements(prev, next)) { - // Append nodes between - for (sibling = prev.nextSibling; sibling && sibling != next;) { - tmpSibling = sibling; - sibling = sibling.nextSibling; - prev.appendChild(tmpSibling); - } - - // Remove next node - dom.remove(next); - - // Move children into prev node - each(tinymce.grep(next.childNodes), function(node) { - prev.appendChild(node); - }); - - return prev; - } - } - - return next; - }; - - /** - * Returns true/false if the specified node is a text block or not. - * - * @private - * @param {Node} node Node to check. - * @return {boolean} True/false if the node is a text block. - */ - function isTextBlock(name) { - return /^(h[1-6]|p|div|pre|address)$/.test(name); - }; - - function getContainer(rng, start) { - var container, offset, lastIdx; - - container = rng[start ? 'startContainer' : 'endContainer']; - offset = rng[start ? 'startOffset' : 'endOffset']; - - if (container.nodeType == 1) { - lastIdx = container.childNodes.length - 1; - - if (!start && offset) - offset--; - - container = container.childNodes[offset > lastIdx ? lastIdx : offset]; - } - - return container; - }; - - function performCaretAction(type, name, vars) { - var i, currentPendingFormats = pendingFormats[type], - otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply']; - - function hasPending() { - return pendingFormats.apply.length || pendingFormats.remove.length; - }; - - function resetPending() { - pendingFormats.apply = []; - pendingFormats.remove = []; - }; - - function perform(caret_node) { - // Apply pending formats - each(pendingFormats.apply.reverse(), function(item) { - apply(item.name, item.vars, caret_node); - }); - - // Remove pending formats - each(pendingFormats.remove.reverse(), function(item) { - remove(item.name, item.vars, caret_node); - }); - - dom.remove(caret_node, 1); - resetPending(); - }; - - // Check if it already exists then ignore it - for (i = currentPendingFormats.length - 1; i >= 0; i--) { - if (currentPendingFormats[i].name == name) - return; - } - - currentPendingFormats.push({name : name, vars : vars}); - - // Check if it's in the other type, then remove it - for (i = otherPendingFormats.length - 1; i >= 0; i--) { - if (otherPendingFormats[i].name == name) - otherPendingFormats.splice(i, 1); - } - - // Pending apply or remove formats - if (hasPending()) { - ed.getDoc().execCommand('FontName', false, 'mceinline'); - - // IE will convert the current word - each(dom.select('font,span'), function(node) { - var bookmark; - - if (isCaretNode(node)) { - bookmark = selection.getBookmark(); - perform(node); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } - }); - - // Only register listeners once if we need to - if (!pendingFormats.isListening && hasPending()) { - pendingFormats.isListening = true; - - each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { - ed[event].addToTop(function(ed, e) { - if (hasPending()) { - each(dom.select('font,span'), function(node) { - var bookmark, textNode, rng; - - // Look for marker - if (isCaretNode(node)) { - textNode = node.firstChild; - - perform(node); - - rng = dom.createRng(); - rng.setStart(textNode, textNode.nodeValue.length); - rng.setEnd(textNode, textNode.nodeValue.length); - selection.setRng(rng); - ed.nodeChanged(); - } - }); - - // Always unbind and clear pending styles on keyup - if (e.type == 'keyup' || e.type == 'mouseup') - resetPending(); - } - }); - }); - } - } - }; - }; -})(tinymce); +/** + * Formatter.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * Text formatter engine class. This class is used to apply formats like bold, italic, font size + * etc to the current selection or specific nodes. This engine was build to replace the browsers + * default formatting logic for execCommand due to it's inconsistant and buggy behavior. + * + * @class tinymce.Formatter + * @example + * tinymce.activeEditor.formatter.register('mycustomformat', { + * inline : 'span', + * styles : {color : '#ff0000'} + * }); + * + * tinymce.activeEditor.formatter.apply('mycustomformat'); + */ + + /** + * Constructs a new formatter instance. + * + * @constructor Formatter + * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. + */ + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValidChild, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + undefined; + + function isArray(obj) { + return obj instanceof Array; + }; + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); + }; + + // Public functions + + /** + * Returns the format by name or all formats if no name is specified. + * + * @method get + * @param {String} name Optional name to retrive by. + * @return {Array/Object} Array/Object with all registred formats or a specific format. + */ + function get(name) { + return name ? formats[name] : formats; + }; + + /** + * Registers a specific format by name. + * + * @method register + * @param {Object/String} name Name of the format for example "bold". + * @param {Object/Array} format Optional format object or array of format variants can only be omitted if the first arg is an object. + */ + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undefined) + format.deep = !format.selector; + + // Default to true + if (format.split === undefined) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undefined && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + + /** + * Applies the specified format to the current selection or specified node. + * + * @method apply + * @param {String} name Name of format to apply. + * @param {Object} vars Optional list of variables to replace within format before applying it. + * @param {Node} node Optional node to apply the format to defaults to current selection. + */ + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); + + /** + * Moves the start to the first suitable text node. + */ + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node; + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1 || container.nodeValue === "") { + container = container.nodeType == 1 ? container.childNodes[offset] : container; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } + } + } + } + + return rng; + }; + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.current(); node; node = walker.prev()) { + if (node.childNodes.length > 1 || node == start) { + return node; + } + } + }; + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset == 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ + var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; + + // find the index of the first child list. + each(node.childNodes, function(n, index) { + if (n.nodeName === "UL" || n.nodeName === "OL") { + listIndex = index; + list = n; + return false; + } + }); + + // get the index of the bookmarks + each(node.childNodes, function(n, index) { + if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { + if (n.id == bookmark.id + "_start") { + startIndex = index; + } else if (n.id == bookmark.id + "_end") { + endIndex = index; + } + } + }); + + // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally + if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { + each(tinymce.grep(node.childNodes), process); + return 0; + } else { + currentWrapElm = wrapElm.cloneNode(FALSE); + + // create a list of the nodes on the same side of the list as the selection + each(tinymce.grep(node.childNodes), function(n, index) { + if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { + nodes.push(n); + n.parentNode.removeChild(n); + } + }); + + // insert the wrapping element either before or after the list. + if (startIndex < listIndex) { + node.insertBefore(currentWrapElm, list); + } else if (startIndex > listIndex) { + node.insertBefore(currentWrapElm, list.nextSibling); + } + + // add the new nodes to the list. + newWrappers.push(currentWrapElm); + + each(nodes, function(node) { + currentWrapElm.appendChild(node); + }); + + return currentWrapElm; + } + }; + + function applyRngStyle(rng, bookmark, node_specific) { + var newWrappers = [], wrapName, wrapElm; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + /** + * Process a list of nodes wrap them. + */ + function process(node) { + var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && node.id !== '_mce_caret') { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = wrapElm.cloneNode(FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else if (nodeName == 'li' && bookmark) { + // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. + currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + + // Cleanup + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = child.cloneNode(FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + selection.moveToBookmark(bookmark); + selection.setRng(moveStart(selection.getRng(TRUE))); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + /** + * Removes the specified format from the current selection or specified node. + * + * @method remove + * @param {String} name Name of format to remove. + * @param {Object} vars Optional list of variables to replace within format before removing it. + * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. + */ + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng; + /** + * Moves the start to the first suitable text node. + */ + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + + // Merges the styles for each node + function process(node) { + var children, i, l; + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + + // Process the children + if (format.deep) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = parent.cloneNode(FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } + }); + }); + }; + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + + // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width + if (tinymce.isWebKit) { + ed.execCommand('mceCleanup'); + } + }; + + /** + * Toggles the specified format on/off. + * + * @method toggle + * @param {String} name Name of format to apply/remove. + * @param {Object} vars Optional list of variables to replace within format before applying/removing it. + * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. + */ + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + /** + * Return true/false if the specified node has the specified format. + * + * @method matchNode + * @param {Node} node Node to check the format on. + * @param {String} name Format name to check. + * @param {Object} vars Optional list of variables to replace before checking it. + * @param {Boolean} similar Match format that has similar properties. + * @return {Object} Returns the format object it matches or undefined if it doesn't match. + */ + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, item_name); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undefined) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + /** + * Matches the current selection or specified node against the specified format name. + * + * @method match + * @param {String} name Name of format to match. + * @param {Object} vars Optional list of variables to replace before checking it. + * @param {Node} node Optional node to check. + * @return {boolean} true/false if the specified selection/node matches the format. + */ + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + /** + * Matches the current selection against the array of formats and returns a new array with matching formats. + * + * @method matchAll + * @param {Array} names Name of format to match. + * @param {Object} vars Optional list of variables to replace before checking it. + * @return {Array} Array with matched formats. + */ + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }); + + return matchedFormatNames; + }; + + /** + * Returns true/false if the specified format can be applied to the current selection or not. It will currently only check the state for selector formats, it returns true on all other format types. + * + * @method canApply + * @param {String} name Name of format to check. + * @return {boolean} true/false if the specified format can be applied to the current selection/node. + */ + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply + }); + + // Private functions + + /** + * Checks if the specified nodes name matches the format inline/block or selector. + * + * @private + * @param {Node} node Node to match against the specified format. + * @param {Object} format Format object o match with. + * @return {boolean} true/false if the format matches. + */ + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + /** + * Compares two string/nodes regardless of their case. + * + * @private + * @param {String/Node} Node or string to compare. + * @param {String/Node} Node or string to compare. + * @return {boolean} True/false if they match. + */ + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + /** + * Returns the style by name on the specified node. This method modifies the style + * contents to make it more easy to match. This will resolve a few browser issues. + * + * @private + * @param {Node} node to get style from. + * @param {String} name Style name to get. + * @return {String} Style item value. + */ + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + /** + * Replaces variables in the value. The variable format is %var. + * + * @private + * @param {String} value Value to replace variables in. + * @param {Object} vars Name/value array with variables to replace. + * @return {String} New value with replaced variables. + */ + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + /** + * Expands the specified range like object to depending on format. + * + * For example on block formats it will move the start/end position + * to the beginning of the current block. + * + * @private + * @param {Object} rng Range like object. + * @param {Array} formats Array with formats to expand by. + * @return {Object} Expanded range like object. + */ + function expandRng(rng, format, remove) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, child, sibling, siblingName; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + for (;;) { + // Stop expanding on block elements or root depending on format + if (parent == root || (!format[0].block_expand && isBlock(parent))) + return parent; + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + parent = parent.parentNode; + } + + return container; + }; + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) + endOffset = endContainer.length; + } + + if (format[0].inline) { + if (rng.collapsed) { + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof(offset) == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + }; + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return {container : container, offset : pos}; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while (node = walker[start ? 'prev' : 'next']()) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return {container : node, offset : pos}; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return {container: lastTextNode, offset: offset}; + } + } + + // Expand left to closest word boundery + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundery + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + // TODO: Figure out why this is in here + //endContainer = leaf.node.previousSibling; + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

    *texttext*

    ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) + return parents[i]; + } + } + + return container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(true); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + /** + * Removes the specified format for the specified node. It will also remove the node if it doesn't have + * any attributes if the format specifies it to do so. + * + * @private + * @param {Object} format Format object with items to remove from node. + * @param {Object} vars Name/value object with variables to apply to format. + * @param {Node} node Node to remove the format styles on. + * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. + * @return {Boolean} True/false if the node was removed or not. + */ + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('data-mce-' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + /** + * Removes the node and wrap it's children in paragraphs before doing so or + * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. + * + * If the div in the node below gets removed: + * text
    text
    text + * + * Output becomes: + * text

    text
    text + * + * So when the div is removed the result is: + * text
    text
    text + * + * @private + * @param {Node} node Node to remove + apply BR/P elements to. + * @param {Object} format Format rule. + * @return {Node} Input node. + */ + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + if (format.block) { + if (!forcedRootBlock) { + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + /** + * Returns the next/previous non whitespace node. + * + * @private + * @param {Node} node Node to start at. + * @param {boolean} next (Optional) Include next or previous node defaults to previous. + * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false. + * @return {Node} Next or previous node or undefined if it wasn't found. + */ + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + /** + * Checks if the specified node is a bookmark node or not. + * + * @param {Node} node Node to check if it's a bookmark node or not. + * @return {Boolean} true/false if the node is a bookmark node. + */ + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; + }; + + /** + * Merges the next/previous sibling element if they match. + * + * @private + * @param {Node} prev Previous node to compare/merge. + * @param {Node} next Next node to compare/merge. + * @return {Node} Next node if we didn't merge and prev node if we did. + */ + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + /** + * Compares two nodes and checks if it's attributes and styles matches. + * This doesn't compare classes as items since their order is significant. + * + * @private + * @param {Node} node1 First node to compare with. + * @param {Node} node2 Second node to compare with. + * @return {boolean} True/false if the nodes are the same or not. + */ + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + /** + * Returns all the nodes attributes excluding internal ones, styles and classes. + * + * @private + * @param {Node} node Node to get attributes from. + * @return {Object} Name/value object with attributes and attribute values. + */ + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + /** + * Compares two objects checks if it's key + value exists in the other one. + * + * @private + * @param {Object} obj1 First object to compare. + * @param {Object} obj2 Second object to compare. + * @return {boolean} True/false if the objects matches or not. + */ + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undefined) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + /** + * Returns true/false if the specified node is a text block or not. + * + * @private + * @param {Node} node Node to check. + * @return {boolean} True/false if the node is a text block. + */ + function isTextBlock(name) { + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); + }; + + function getContainer(rng, start) { + var container, offset, lastIdx, walker; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + // If start text node is excluded then walk to the next node + if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { + container = new TreeWalker(container, ed.getBody()).next() || container; + } + + // If end text node is excluded then walk to the previous node + if (container.nodeType === 3 && !start && offset == 0) { + container = new TreeWalker(container, ed.getBody()).prev() || container; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; + + // Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container + invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR; + + // Creates a caret container bogus element + function createCaretContainer(fill) { + var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); + + if (fill) { + caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar)); + } + + return caretContainer; + }; + + function isCaretContainerEmpty(node, nodes) { + while (node) { + if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) { + return false; + } + + // Collect nodes + if (nodes && node.nodeType === 1) { + nodes.push(node); + } + + node = node.firstChild; + } + + return true; + }; + + // Returns any parent caret container element + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Removes the caret container for the specified node or all on the current document + function removeCaretContainer(node, move_caret) { + var child, rng; + + if (!node) { + node = getParentCaretContainer(selection.getStart()); + + if (!node) { + while (node = dom.get(caretContainerId)) { + removeCaretContainer(node, false); + } + } + } else { + rng = selection.getRng(true); + + if (isCaretContainerEmpty(node)) { + if (move_caret !== false) { + rng.setStartBefore(node); + rng.setEndBefore(node); + } + + dom.remove(node); + } else { + child = findFirstTextNode(node); + child = child.deleteData(0, 1); + dom.remove(node, 1); + } + + selection.setRng(rng); + } + }; + + // Applies formatting to the caret postion + function applyCaretFormat() { + var rng, caretContainer, textNode, offset, bookmark, container, text; + + rng = selection.getRng(true); + offset = rng.startOffset; + container = rng.startContainer; + text = container.nodeValue; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer) { + textNode = findFirstTextNode(caretContainer); + } + + // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character + if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name)); + rng = rangeUtils.split(rng); + + // Apply the format to the range + apply(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + if (!caretContainer || textNode.nodeValue !== invisibleChar) { + caretContainer = createCaretContainer(true); + textNode = caretContainer.firstChild; + + rng.insertNode(caretContainer); + offset = 1; + + apply(name, vars, caretContainer); + } else { + apply(name, vars, caretContainer); + } + + // Move selection to text node + selection.setCursorLocation(textNode, offset); + } + }; + + function removeCaretFormat() { + var rng = selection.getRng(true), container, offset, bookmark, + hasContentAfter, node, formatNode, parents = [], i, caretContainer; + + container = rng.startContainer; + offset = rng.startOffset; + node = container; + + if (container.nodeType == 3) { + if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) { + hasContentAfter = true; + } + + node = node.parentNode; + } + + while (node) { + if (matchNode(node, name, vars)) { + formatNode = node; + break; + } + + if (node.nextSibling) { + hasContentAfter = true; + } + + parents.push(node); + node = node.parentNode; + } + + // Node doesn't have the specified format + if (!formatNode) { + return; + } + + // Is there contents after the caret then remove the format on the element + if (hasContentAfter) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name), true); + rng = rangeUtils.split(rng); + + // Remove the format from the range + remove(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + caretContainer = createCaretContainer(); + + node = caretContainer; + for (i = parents.length - 1; i >= 0; i--) { + node.appendChild(parents[i].cloneNode(false)); + node = node.firstChild; + } + + // Insert invisible character into inner most format element + node.appendChild(dom.doc.createTextNode(invisibleChar)); + node = node.firstChild; + + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + + // Move selection to text node + selection.setCursorLocation(node, 1); + } + }; + + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + ed.onBeforeGetContent.addToTop(function() { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }); + + // Remove caret container on mouse up and on key up + tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { + ed[name].addToTop(function() { + removeCaretContainer(); + }); + }); + + // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys + ed.onKeyDown.addToTop(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == 8 || keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + }); + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + }; + }; +})(tinymce); diff --git a/js/tiny_mce/classes/LegacyInput.js b/js/tiny_mce/classes/LegacyInput.js index 6f936707..f3c5021a 100644 --- a/js/tiny_mce/classes/LegacyInput.js +++ b/js/tiny_mce/classes/LegacyInput.js @@ -1,62 +1,66 @@ -/** - * LegacyInput.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -tinymce.onAddEditor.add(function(tinymce, ed) { - var filters, fontSizes, dom, settings = ed.settings; - - if (settings.inline_styles) { - fontSizes = tinymce.explode(settings.font_size_style_values); - - function replaceWithSpan(node, styles) { - dom.replace(dom.create('span', { - style : styles - }), node, 1); - }; - - filters = { - font : function(dom, node) { - replaceWithSpan(node, { - backgroundColor : node.style.backgroundColor, - color : node.color, - fontFamily : node.face, - fontSize : fontSizes[parseInt(node.size) - 1] - }); - }, - - u : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'underline' - }); - }, - - strike : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'line-through' - }); - } - }; - - function convert(editor, params) { - dom = editor.dom; - - if (settings.convert_fonts_to_spans) { - tinymce.each(dom.select('font,u,strike', params.node), function(node) { - filters[node.nodeName.toLowerCase()](ed.dom, node); - }); - } - }; - - ed.onPreProcess.add(convert); - - ed.onInit.add(function() { - ed.selection.onSetContent.add(convert); - }); - } -}); +/** + * LegacyInput.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_legacy_values); + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); diff --git a/js/tiny_mce/classes/Popup.js b/js/tiny_mce/classes/Popup.js index 36310c08..0537c7c7 100644 --- a/js/tiny_mce/classes/Popup.js +++ b/js/tiny_mce/classes/Popup.js @@ -1,438 +1,456 @@ -/** - * Popup.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -// Some global instances -var tinymce = null, tinyMCEPopup, tinyMCE; - -/** - * TinyMCE popup/dialog helper class. This gives you easy access to the - * parent editor instance and a bunch of other things. It's higly recommended - * that you load this script into your dialogs. - * - * @static - * @class tinyMCEPopup - */ -tinyMCEPopup = { - /** - * Initializes the popup this will be called automatically. - * - * @method init - */ - init : function() { - var t = this, w, ti; - - // Find window & API - w = t.getWin(); - tinymce = w.tinymce; - tinyMCE = w.tinyMCE; - t.editor = tinymce.EditorManager.activeEditor; - t.params = t.editor.windowManager.params; - t.features = t.editor.windowManager.features; - - // Setup local DOM - t.dom = t.editor.windowManager.createInstance('tinymce.dom.DOMUtils', document); - - // Enables you to skip loading the default css - if (t.features.popup_css !== false) - t.dom.loadCSS(t.features.popup_css || t.editor.settings.popup_css); - - // Setup on init listeners - t.listeners = []; - t.onInit = { - add : function(f, s) { - t.listeners.push({func : f, scope : s}); - } - }; - - t.isWindow = !t.getWindowArg('mce_inline'); - t.id = t.getWindowArg('mce_window_id'); - t.editor.windowManager.onOpen.dispatch(t.editor.windowManager, window); - }, - - /** - * Returns the reference to the parent window that opened the dialog. - * - * @method getWin - * @return {Window} Reference to the parent window that opened the dialog. - */ - getWin : function() { - // Added frameElement check to fix bug: #2817583 - return (!window.frameElement && window.dialogArguments) || opener || parent || top; - }, - - /** - * Returns a window argument/parameter by name. - * - * @method getWindowArg - * @param {String} n Name of the window argument to retrive. - * @param {String} dv Optional default value to return. - * @return {String} Argument value or default value if it wasn't found. - */ - getWindowArg : function(n, dv) { - var v = this.params[n]; - - return tinymce.is(v) ? v : dv; - }, - - /** - * Returns a editor parameter/config option value. - * - * @method getParam - * @param {String} n Name of the editor config option to retrive. - * @param {String} dv Optional default value to return. - * @return {String} Parameter value or default value if it wasn't found. - */ - getParam : function(n, dv) { - return this.editor.getParam(n, dv); - }, - - /** - * Returns a language item by key. - * - * @method getLang - * @param {String} n Language item like mydialog.something. - * @param {String} dv Optional default value to return. - * @return {String} Language value for the item like "my string" or the default value if it wasn't found. - */ - getLang : function(n, dv) { - return this.editor.getLang(n, dv); - }, - - /** - * Executed a command on editor that opened the dialog/popup. - * - * @method execCommand - * @param {String} cmd Command to execute. - * @param {Boolean} ui Optional boolean value if the UI for the command should be presented or not. - * @param {Object} val Optional value to pass with the comman like an URL. - * @param {Object} a Optional arguments object. - */ - execCommand : function(cmd, ui, val, a) { - a = a || {}; - a.skip_focus = 1; - - this.restoreSelection(); - return this.editor.execCommand(cmd, ui, val, a); - }, - - /** - * Resizes the dialog to the inner size of the window. This is needed since various browsers - * have different border sizes on windows. - * - * @method resizeToInnerSize - */ - resizeToInnerSize : function() { - var t = this; - - // Detach it to workaround a Chrome specific bug - // https://sourceforge.net/tracker/?func=detail&atid=635682&aid=2926339&group_id=103281 - setTimeout(function() { - var vp = t.dom.getViewPort(window); - - t.editor.windowManager.resizeBy( - t.getWindowArg('mce_width') - vp.w, - t.getWindowArg('mce_height') - vp.h, - t.id || window - ); - }, 0); - }, - - /** - * Will executed the specified string when the page has been loaded. This function - * was added for compatibility with the 2.x branch. - * - * @method executeOnLoad - * @param {String} s String to evalutate on init. - */ - executeOnLoad : function(s) { - this.onInit.add(function() { - eval(s); - }); - }, - - /** - * Stores the current editor selection for later restoration. This can be useful since some browsers - * looses it's selection if a control element is selected/focused inside the dialogs. - * - * @method storeSelection - */ - storeSelection : function() { - this.editor.windowManager.bookmark = tinyMCEPopup.editor.selection.getBookmark(1); - }, - - /** - * Restores any stored selection. This can be useful since some browsers - * looses it's selection if a control element is selected/focused inside the dialogs. - * - * @method restoreSelection - */ - restoreSelection : function() { - var t = tinyMCEPopup; - - if (!t.isWindow && tinymce.isIE) - t.editor.selection.moveToBookmark(t.editor.windowManager.bookmark); - }, - - /** - * Loads a specific dialog language pack. If you pass in plugin_url as a arugment - * when you open the window it will load the /langs/_dlg.js lang pack file. - * - * @method requireLangPack - */ - requireLangPack : function() { - var t = this, u = t.getWindowArg('plugin_url') || t.getWindowArg('theme_url'); - - if (u && t.editor.settings.language && t.features.translate_i18n !== false) { - u += '/langs/' + t.editor.settings.language + '_dlg.js'; - - if (!tinymce.ScriptLoader.isDone(u)) { - document.write(''); - tinymce.ScriptLoader.markDone(u); - } - } - }, - - /** - * Executes a color picker on the specified element id. When the user - * then selects a color it will be set as the value of the specified element. - * - * @method pickColor - * @param {DOMEvent} e DOM event object. - * @param {string} element_id Element id to be filled with the color value from the picker. - */ - pickColor : function(e, element_id) { - this.execCommand('mceColorPicker', true, { - color : document.getElementById(element_id).value, - func : function(c) { - document.getElementById(element_id).value = c; - - try { - document.getElementById(element_id).onchange(); - } catch (ex) { - // Try fire event, ignore errors - } - } - }); - }, - - /** - * Opens a filebrowser/imagebrowser this will set the output value from - * the browser as a value on the specified element. - * - * @method openBrowser - * @param {string} element_id Id of the element to set value in. - * @param {string} type Type of browser to open image/file/flash. - * @param {string} option Option name to get the file_broswer_callback function name from. - */ - openBrowser : function(element_id, type, option) { - tinyMCEPopup.restoreSelection(); - this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window); - }, - - /** - * Creates a confirm dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method confirm - * @param {String} t Title for the new confirm dialog. - * @param {function} cb Callback function to be executed after the user has selected ok or cancel. - * @param {Object} s Optional scope to execute the callback in. - */ - confirm : function(t, cb, s) { - this.editor.windowManager.confirm(t, cb, s, window); - }, - - /** - * Creates a alert dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method alert - * @param {String} t Title for the new alert dialog. - * @param {function} cb Callback function to be executed after the user has selected ok. - * @param {Object} s Optional scope to execute the callback in. - */ - alert : function(tx, cb, s) { - this.editor.windowManager.alert(tx, cb, s, window); - }, - - /** - * Closes the current window. - * - * @method close - */ - close : function() { - var t = this; - - // To avoid domain relaxing issue in Opera - function close() { - t.editor.windowManager.close(window); - tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup - }; - - if (tinymce.isOpera) - t.getWin().setTimeout(close, 0); - else - close(); - }, - - // Internal functions - - _restoreSelection : function() { - var e = window.event.srcElement; - - if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) - tinyMCEPopup.restoreSelection(); - }, - -/* _restoreSelection : function() { - var e = window.event.srcElement; - - // If user focus a non text input or textarea - if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text') - tinyMCEPopup.restoreSelection(); - },*/ - - _onDOMLoaded : function() { - var t = tinyMCEPopup, ti = document.title, bm, h, nv; - - if (t.domLoaded) - return; - - t.domLoaded = 1; - - // Translate page - if (t.features.translate_i18n !== false) { - h = document.body.innerHTML; - - // Replace a=x with a="x" in IE - if (tinymce.isIE) - h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"') - - document.dir = t.editor.getParam('directionality',''); - - if ((nv = t.editor.translate(h)) && nv != h) - document.body.innerHTML = nv; - - if ((nv = t.editor.translate(ti)) && nv != ti) - document.title = ti = nv; - } - - document.body.style.display = ''; - - // Restore selection in IE when focus is placed on a non textarea or input element of the type text - if (tinymce.isIE) { - document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection); - - // Add base target element for it since it would fail with modal dialogs - t.dom.add(t.dom.select('head')[0], 'base', {target : '_self'}); - } - - t.restoreSelection(); - t.resizeToInnerSize(); - - // Set inline title - if (!t.isWindow) - t.editor.windowManager.setTitle(window, ti); - else - window.focus(); - - if (!tinymce.isIE && !t.isWindow) { - tinymce.dom.Event._add(document, 'focus', function() { - t.editor.windowManager.focus(t.id); - }); - } - - // Patch for accessibility - tinymce.each(t.dom.select('select'), function(e) { - e.onkeydown = tinyMCEPopup._accessHandler; - }); - - // Call onInit - // Init must be called before focus so the selection won't get lost by the focus call - tinymce.each(t.listeners, function(o) { - o.func.call(o.scope, t.editor); - }); - - // Move focus to window - if (t.getWindowArg('mce_auto_focus', true)) { - window.focus(); - - // Focus element with mceFocus class - tinymce.each(document.forms, function(f) { - tinymce.each(f.elements, function(e) { - if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) { - e.focus(); - return false; // Break loop - } - }); - }); - } - - document.onkeyup = tinyMCEPopup._closeWinKeyHandler; - }, - - _accessHandler : function(e) { - e = e || window.event; - - if (e.keyCode == 13 || e.keyCode == 32) { - e = e.target || e.srcElement; - - if (e.onchange) - e.onchange(); - - return tinymce.dom.Event.cancel(e); - } - }, - - _closeWinKeyHandler : function(e) { - e = e || window.event; - - if (e.keyCode == 27) - tinyMCEPopup.close(); - }, - - _wait : function() { - // Use IE method - if (document.attachEvent) { - document.attachEvent("onreadystatechange", function() { - if (document.readyState === "complete") { - document.detachEvent("onreadystatechange", arguments.callee); - tinyMCEPopup._onDOMLoaded(); - } - }); - - if (document.documentElement.doScroll && window == window.top) { - (function() { - if (tinyMCEPopup.domLoaded) - return; - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(arguments.callee, 0); - return; - } - - tinyMCEPopup._onDOMLoaded(); - })(); - } - - document.attachEvent('onload', tinyMCEPopup._onDOMLoaded); - } else if (document.addEventListener) { - window.addEventListener('DOMContentLoaded', tinyMCEPopup._onDOMLoaded, false); - window.addEventListener('load', tinyMCEPopup._onDOMLoaded, false); - } - } -}; - -tinyMCEPopup.init(); -tinyMCEPopup._wait(); // Wait for DOM Content Loaded +/** + * Popup.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +// Some global instances +var tinymce = null, tinyMCEPopup, tinyMCE; + +/** + * TinyMCE popup/dialog helper class. This gives you easy access to the + * parent editor instance and a bunch of other things. It's higly recommended + * that you load this script into your dialogs. + * + * @static + * @class tinyMCEPopup + */ +tinyMCEPopup = { + /** + * Initializes the popup this will be called automatically. + * + * @method init + */ + init : function() { + var t = this, w, ti; + + // Find window & API + w = t.getWin(); + tinymce = w.tinymce; + tinyMCE = w.tinyMCE; + t.editor = tinymce.EditorManager.activeEditor; + t.params = t.editor.windowManager.params; + t.features = t.editor.windowManager.features; + + // Setup local DOM + t.dom = t.editor.windowManager.createInstance('tinymce.dom.DOMUtils', document); + + // Enables you to skip loading the default css + if (t.features.popup_css !== false) + t.dom.loadCSS(t.features.popup_css || t.editor.settings.popup_css); + + // Setup on init listeners + t.listeners = []; + + /** + * Fires when the popup is initialized. + * + * @event onInit + * @param {tinymce.Editor} editor Editor instance. + * @example + * // Alerts the selected contents when the dialog is loaded + * tinyMCEPopup.onInit.add(function(ed) { + * alert(ed.selection.getContent()); + * }); + * + * // Executes the init method on page load in some object using the SomeObject scope + * tinyMCEPopup.onInit.add(SomeObject.init, SomeObject); + */ + t.onInit = { + add : function(f, s) { + t.listeners.push({func : f, scope : s}); + } + }; + + t.isWindow = !t.getWindowArg('mce_inline'); + t.id = t.getWindowArg('mce_window_id'); + t.editor.windowManager.onOpen.dispatch(t.editor.windowManager, window); + }, + + /** + * Returns the reference to the parent window that opened the dialog. + * + * @method getWin + * @return {Window} Reference to the parent window that opened the dialog. + */ + getWin : function() { + // Added frameElement check to fix bug: #2817583 + return (!window.frameElement && window.dialogArguments) || opener || parent || top; + }, + + /** + * Returns a window argument/parameter by name. + * + * @method getWindowArg + * @param {String} n Name of the window argument to retrive. + * @param {String} dv Optional default value to return. + * @return {String} Argument value or default value if it wasn't found. + */ + getWindowArg : function(n, dv) { + var v = this.params[n]; + + return tinymce.is(v) ? v : dv; + }, + + /** + * Returns a editor parameter/config option value. + * + * @method getParam + * @param {String} n Name of the editor config option to retrive. + * @param {String} dv Optional default value to return. + * @return {String} Parameter value or default value if it wasn't found. + */ + getParam : function(n, dv) { + return this.editor.getParam(n, dv); + }, + + /** + * Returns a language item by key. + * + * @method getLang + * @param {String} n Language item like mydialog.something. + * @param {String} dv Optional default value to return. + * @return {String} Language value for the item like "my string" or the default value if it wasn't found. + */ + getLang : function(n, dv) { + return this.editor.getLang(n, dv); + }, + + /** + * Executed a command on editor that opened the dialog/popup. + * + * @method execCommand + * @param {String} cmd Command to execute. + * @param {Boolean} ui Optional boolean value if the UI for the command should be presented or not. + * @param {Object} val Optional value to pass with the comman like an URL. + * @param {Object} a Optional arguments object. + */ + execCommand : function(cmd, ui, val, a) { + a = a || {}; + a.skip_focus = 1; + + this.restoreSelection(); + return this.editor.execCommand(cmd, ui, val, a); + }, + + /** + * Resizes the dialog to the inner size of the window. This is needed since various browsers + * have different border sizes on windows. + * + * @method resizeToInnerSize + */ + resizeToInnerSize : function() { + var t = this; + + // Detach it to workaround a Chrome specific bug + // https://sourceforge.net/tracker/?func=detail&atid=635682&aid=2926339&group_id=103281 + setTimeout(function() { + var vp = t.dom.getViewPort(window); + + t.editor.windowManager.resizeBy( + t.getWindowArg('mce_width') - vp.w, + t.getWindowArg('mce_height') - vp.h, + t.id || window + ); + }, 10); + }, + + /** + * Will executed the specified string when the page has been loaded. This function + * was added for compatibility with the 2.x branch. + * + * @method executeOnLoad + * @param {String} s String to evalutate on init. + */ + executeOnLoad : function(s) { + this.onInit.add(function() { + eval(s); + }); + }, + + /** + * Stores the current editor selection for later restoration. This can be useful since some browsers + * looses it's selection if a control element is selected/focused inside the dialogs. + * + * @method storeSelection + */ + storeSelection : function() { + this.editor.windowManager.bookmark = tinyMCEPopup.editor.selection.getBookmark(1); + }, + + /** + * Restores any stored selection. This can be useful since some browsers + * looses it's selection if a control element is selected/focused inside the dialogs. + * + * @method restoreSelection + */ + restoreSelection : function() { + var t = tinyMCEPopup; + + if (!t.isWindow && tinymce.isIE) + t.editor.selection.moveToBookmark(t.editor.windowManager.bookmark); + }, + + /** + * Loads a specific dialog language pack. If you pass in plugin_url as a arugment + * when you open the window it will load the /langs/_dlg.js lang pack file. + * + * @method requireLangPack + */ + requireLangPack : function() { + var t = this, u = t.getWindowArg('plugin_url') || t.getWindowArg('theme_url'); + + if (u && t.editor.settings.language && t.features.translate_i18n !== false && t.editor.settings.language_load !== false) { + u += '/langs/' + t.editor.settings.language + '_dlg.js'; + + if (!tinymce.ScriptLoader.isDone(u)) { + document.write(''); + tinymce.ScriptLoader.markDone(u); + } + } + }, + + /** + * Executes a color picker on the specified element id. When the user + * then selects a color it will be set as the value of the specified element. + * + * @method pickColor + * @param {DOMEvent} e DOM event object. + * @param {string} element_id Element id to be filled with the color value from the picker. + */ + pickColor : function(e, element_id) { + this.execCommand('mceColorPicker', true, { + color : document.getElementById(element_id).value, + func : function(c) { + document.getElementById(element_id).value = c; + + try { + document.getElementById(element_id).onchange(); + } catch (ex) { + // Try fire event, ignore errors + } + } + }); + }, + + /** + * Opens a filebrowser/imagebrowser this will set the output value from + * the browser as a value on the specified element. + * + * @method openBrowser + * @param {string} element_id Id of the element to set value in. + * @param {string} type Type of browser to open image/file/flash. + * @param {string} option Option name to get the file_broswer_callback function name from. + */ + openBrowser : function(element_id, type, option) { + tinyMCEPopup.restoreSelection(); + this.editor.execCallback('file_browser_callback', element_id, document.getElementById(element_id).value, type, window); + }, + + /** + * Creates a confirm dialog. Please don't use the blocking behavior of this + * native version use the callback method instead then it can be extended. + * + * @method confirm + * @param {String} t Title for the new confirm dialog. + * @param {function} cb Callback function to be executed after the user has selected ok or cancel. + * @param {Object} s Optional scope to execute the callback in. + */ + confirm : function(t, cb, s) { + this.editor.windowManager.confirm(t, cb, s, window); + }, + + /** + * Creates a alert dialog. Please don't use the blocking behavior of this + * native version use the callback method instead then it can be extended. + * + * @method alert + * @param {String} t Title for the new alert dialog. + * @param {function} cb Callback function to be executed after the user has selected ok. + * @param {Object} s Optional scope to execute the callback in. + */ + alert : function(tx, cb, s) { + this.editor.windowManager.alert(tx, cb, s, window); + }, + + /** + * Closes the current window. + * + * @method close + */ + close : function() { + var t = this; + + // To avoid domain relaxing issue in Opera + function close() { + t.editor.windowManager.close(window); + tinymce = tinyMCE = t.editor = t.params = t.dom = t.dom.doc = null; // Cleanup + }; + + if (tinymce.isOpera) + t.getWin().setTimeout(close, 0); + else + close(); + }, + + // Internal functions + + _restoreSelection : function() { + var e = window.event.srcElement; + + if (e.nodeName == 'INPUT' && (e.type == 'submit' || e.type == 'button')) + tinyMCEPopup.restoreSelection(); + }, + +/* _restoreSelection : function() { + var e = window.event.srcElement; + + // If user focus a non text input or textarea + if ((e.nodeName != 'INPUT' && e.nodeName != 'TEXTAREA') || e.type != 'text') + tinyMCEPopup.restoreSelection(); + },*/ + + _onDOMLoaded : function() { + var t = tinyMCEPopup, ti = document.title, bm, h, nv; + + if (t.domLoaded) + return; + + t.domLoaded = 1; + + // Translate page + if (t.features.translate_i18n !== false) { + h = document.body.innerHTML; + + // Replace a=x with a="x" in IE + if (tinymce.isIE) + h = h.replace(/ (value|title|alt)=([^"][^\s>]+)/gi, ' $1="$2"') + + document.dir = t.editor.getParam('directionality',''); + + if ((nv = t.editor.translate(h)) && nv != h) + document.body.innerHTML = nv; + + if ((nv = t.editor.translate(ti)) && nv != ti) + document.title = ti = nv; + } + + if (!t.editor.getParam('browser_preferred_colors', false) || !t.isWindow) + t.dom.addClass(document.body, 'forceColors'); + + document.body.style.display = ''; + + // Restore selection in IE when focus is placed on a non textarea or input element of the type text + if (tinymce.isIE) { + document.attachEvent('onmouseup', tinyMCEPopup._restoreSelection); + + // Add base target element for it since it would fail with modal dialogs + t.dom.add(t.dom.select('head')[0], 'base', {target : '_self'}); + } + + t.restoreSelection(); + t.resizeToInnerSize(); + + // Set inline title + if (!t.isWindow) + t.editor.windowManager.setTitle(window, ti); + else + window.focus(); + + if (!tinymce.isIE && !t.isWindow) { + tinymce.dom.Event._add(document, 'focus', function() { + t.editor.windowManager.focus(t.id); + }); + } + + // Patch for accessibility + tinymce.each(t.dom.select('select'), function(e) { + e.onkeydown = tinyMCEPopup._accessHandler; + }); + + // Call onInit + // Init must be called before focus so the selection won't get lost by the focus call + tinymce.each(t.listeners, function(o) { + o.func.call(o.scope, t.editor); + }); + + // Move focus to window + if (t.getWindowArg('mce_auto_focus', true)) { + window.focus(); + + // Focus element with mceFocus class + tinymce.each(document.forms, function(f) { + tinymce.each(f.elements, function(e) { + if (t.dom.hasClass(e, 'mceFocus') && !e.disabled) { + e.focus(); + return false; // Break loop + } + }); + }); + } + + document.onkeyup = tinyMCEPopup._closeWinKeyHandler; + }, + + _accessHandler : function(e) { + e = e || window.event; + + if (e.keyCode == 13 || e.keyCode == 32) { + e = e.target || e.srcElement; + + if (e.onchange) + e.onchange(); + + return tinymce.dom.Event.cancel(e); + } + }, + + _closeWinKeyHandler : function(e) { + e = e || window.event; + + if (e.keyCode == 27) + tinyMCEPopup.close(); + }, + + _wait : function() { + // Use IE method + if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + tinyMCEPopup._onDOMLoaded(); + } + }); + + if (document.documentElement.doScroll && window == window.top) { + (function() { + if (tinyMCEPopup.domLoaded) + return; + + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + tinyMCEPopup._onDOMLoaded(); + })(); + } + + document.attachEvent('onload', tinyMCEPopup._onDOMLoaded); + } else if (document.addEventListener) { + window.addEventListener('DOMContentLoaded', tinyMCEPopup._onDOMLoaded, false); + window.addEventListener('load', tinyMCEPopup._onDOMLoaded, false); + } + } +}; + +tinyMCEPopup.init(); +tinyMCEPopup._wait(); // Wait for DOM Content Loaded diff --git a/js/tiny_mce/classes/UndoManager.js b/js/tiny_mce/classes/UndoManager.js index a52f5328..045aa839 100644 --- a/js/tiny_mce/classes/UndoManager.js +++ b/js/tiny_mce/classes/UndoManager.js @@ -1,163 +1,200 @@ -/** - * UndoManager.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher; - - /** - * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed. - * - * @class tinymce.UndoManager - */ - tinymce.UndoManager = function(editor) { - var self, index = 0, data = []; - - function getContent() { - return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); - }; - - return self = { - typing : 0, - - onAdd : new Dispatcher(self), - onUndo : new Dispatcher(self), - onRedo : new Dispatcher(self), - - /** - * Adds a new undo level/snapshot to the undo list. - * - * @method add - * @param {Object} l Optional undo level object to add. - * @return {Object} Undo level that got added or null it a level wasn't needed. - */ - add : function(level) { - var i, settings = editor.settings, lastLevel; - - level = level || {}; - level.content = getContent(); - - // Add undo level if needed - lastLevel = data[index]; - if (lastLevel && lastLevel.content == level.content) { - if (index > 0 || data.length == 1) - return null; - } - - // Time to compress - if (settings.custom_undo_redo_levels) { - if (data.length > settings.custom_undo_redo_levels) { - for (i = 0; i < data.length - 1; i++) - data[i] = data[i + 1]; - - data.length--; - index = data.length; - } - } - - // Get a non intrusive normalized bookmark - level.bookmark = editor.selection.getBookmark(2, true); - - // Crop array if needed - if (index < data.length - 1) { - // Treat first level as initial - if (index == 0) - data = []; - else - data.length = index + 1; - } - - data.push(level); - index = data.length - 1; - - self.onAdd.dispatch(self, level); - editor.isNotDirty = 0; - - return level; - }, - - /** - * Undoes the last action. - * - * @method undo - * @return {Object} Undo level or null if no undo was performed. - */ - undo : function() { - var level, i; - - if (self.typing) { - self.add(); - self.typing = 0; - } - - if (index > 0) { - level = data[--index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onUndo.dispatch(self, level); - } - - return level; - }, - - /** - * Redoes the last action. - * - * @method redo - * @return {Object} Redo level or null if no redo was performed. - */ - redo : function() { - var level; - - if (index < data.length - 1) { - level = data[++index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onRedo.dispatch(self, level); - } - - return level; - }, - - /** - * Removes all undo levels. - * - * @method clear - */ - clear : function() { - data = []; - index = self.typing = 0; - }, - - /** - * Returns true/false if the undo manager has any undo levels. - * - * @method hasUndo - * @return {Boolean} true/false if the undo manager has any undo levels. - */ - hasUndo : function() { - return index > 0 || self.typing; - }, - - /** - * Returns true/false if the undo manager has any redo levels. - * - * @method hasRedo - * @return {Boolean} true/false if the undo manager has any redo levels. - */ - hasRedo : function() { - return index < data.length - 1; - } - }; - }; -})(tinymce); +/** + * UndoManager.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + /** + * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed. + * + * @class tinymce.UndoManager + */ + tinymce.UndoManager = function(editor) { + var self, index = 0, data = [], beforeBookmark; + + function getContent() { + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); + }; + + return self = { + /** + * State if the user is currently typing or not. This will add a typing operation into one undo + * level instead of one new level for each keystroke. + * + * @field {Boolean} typing + */ + typing : false, + + /** + * This event will fire each time a new undo level is added to the undo manager. + * + * @event onAdd + * @param {tinymce.UndoManager} sender UndoManager instance that got the new level. + * @param {Object} level The new level object containing a bookmark and contents. + */ + onAdd : new Dispatcher(self), + + /** + * This event will fire when the user make an undo of a change. + * + * @event onUndo + * @param {tinymce.UndoManager} sender UndoManager instance that got the new level. + * @param {Object} level The old level object containing a bookmark and contents. + */ + onUndo : new Dispatcher(self), + + /** + * This event will fire when the user make an redo of a change. + * + * @event onRedo + * @param {tinymce.UndoManager} sender UndoManager instance that got the new level. + * @param {Object} level The old level object containing a bookmark and contents. + */ + onRedo : new Dispatcher(self), + + /** + * Stores away a bookmark to be used when performing an undo action so that the selection is before + * the change has been made. + * + * @method beforeChange + */ + beforeChange : function() { + beforeBookmark = editor.selection.getBookmark(2, true); + }, + + /** + * Adds a new undo level/snapshot to the undo list. + * + * @method add + * @param {Object} l Optional undo level object to add. + * @return {Object} Undo level that got added or null it a level wasn't needed. + */ + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) + return null; + + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = beforeBookmark; + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) + data.length = index + 1; + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + /** + * Undoes the last action. + * + * @method undo + * @return {Object} Undo level or null if no undo was performed. + */ + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = false; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.beforeBookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + /** + * Redoes the last action. + * + * @method redo + * @return {Object} Redo level or null if no redo was performed. + */ + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + /** + * Removes all undo levels. + * + * @method clear + */ + clear : function() { + data = []; + index = 0; + self.typing = false; + }, + + /** + * Returns true/false if the undo manager has any undo levels. + * + * @method hasUndo + * @return {Boolean} true/false if the undo manager has any undo levels. + */ + hasUndo : function() { + return index > 0 || this.typing; + }, + + /** + * Returns true/false if the undo manager has any redo levels. + * + * @method hasRedo + * @return {Boolean} true/false if the undo manager has any redo levels. + */ + hasRedo : function() { + return index < data.length - 1 && !this.typing; + } + }; + }; +})(tinymce); diff --git a/js/tiny_mce/classes/WindowManager.js b/js/tiny_mce/classes/WindowManager.js index b3c9e99f..fac07260 100644 --- a/js/tiny_mce/classes/WindowManager.js +++ b/js/tiny_mce/classes/WindowManager.js @@ -1,183 +1,231 @@ -/** - * WindowManager.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; - - /** - * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. - * @class tinymce.WindowManager - */ - tinymce.create('tinymce.WindowManager', { - /** - * Constructs a new window manager instance. - * - * @constructor - * @method WindowManager - * @param {tinymce.Editor} ed Editor instance that the windows are bound to. - */ - WindowManager : function(ed) { - var t = this; - - t.editor = ed; - t.onOpen = new Dispatcher(t); - t.onClose = new Dispatcher(t); - t.params = {}; - t.features = {}; - }, - - /** - * Opens a new window. - * - * @method open - * @param {Object} s Optional name/value settings collection contains things like width/height/url etc. - * @param {Object} p Optional parameters/arguments collection can be used by the dialogs to retrive custom parameters. - */ - open : function(s, p) { - var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; - - // Default some options - s = s || {}; - p = p || {}; - sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window - sh = isOpera ? vp.h : screen.height; - s.name = s.name || 'mc_' + new Date().getTime(); - s.width = parseInt(s.width || 320); - s.height = parseInt(s.height || 240); - s.resizable = true; - s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); - s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); - p.inline = false; - p.mce_width = s.width; - p.mce_height = s.height; - p.mce_auto_focus = s.auto_focus; - - if (mo) { - if (isIE) { - s.center = true; - s.help = false; - s.dialogWidth = s.width + 'px'; - s.dialogHeight = s.height + 'px'; - s.scroll = s.scrollbars || false; - } - } - - // Build features string - each(s, function(v, k) { - if (tinymce.is(v, 'boolean')) - v = v ? 'yes' : 'no'; - - if (!/^(name|url)$/.test(k)) { - if (isIE && mo) - f += (f ? ';' : '') + k + ':' + v; - else - f += (f ? ',' : '') + k + '=' + v; - } - }); - - t.features = s; - t.params = p; - t.onOpen.dispatch(t, s, p); - - u = s.url || s.file; - u = tinymce._addVer(u); - - try { - if (isIE && mo) { - w = 1; - window.showModalDialog(u, window, f); - } else - w = window.open(u, s.name, f); - } catch (ex) { - // Ignore - } - - if (!w) - alert(t.editor.getLang('popup_blocked')); - }, - - /** - * Closes the specified window. This will also dispatch out a onClose event. - * - * @method close - * @param {Window} w Native window object to close. - */ - close : function(w) { - w.close(); - this.onClose.dispatch(this); - }, - - /** - * Creates a instance of a class. This method was needed since IE can't create instances - * of classes from a parent window due to some reference problem. Any arguments passed after the class name - * will be passed as arguments to the constructor. - * - * @method createInstance - * @param {String} cl Class name to create an instance of. - * @return {Object} Instance of the specified class. - */ - createInstance : function(cl, a, b, c, d, e) { - var f = tinymce.resolve(cl); - - return new f(a, b, c, d, e); - }, - - /** - * Creates a confirm dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method confirm - * @param {String} t Title for the new confirm dialog. - * @param {function} cb Callback function to be executed after the user has selected ok or cancel. - * @param {Object} s Optional scope to execute the callback in. - */ - confirm : function(t, cb, s, w) { - w = w || window; - - cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); - }, - - /** - * Creates a alert dialog. Please don't use the blocking behavior of this - * native version use the callback method instead then it can be extended. - * - * @method alert - * @param {String} t Title for the new alert dialog. - * @param {function} cb Callback function to be executed after the user has selected ok. - * @param {Object} s Optional scope to execute the callback in. - */ - alert : function(tx, cb, s, w) { - var t = this; - - w = w || window; - w.alert(t._decode(t.editor.getLang(tx, tx))); - - if (cb) - cb.call(s || t); - }, - - /** - * Resizes the specified window or id. - * - * @param {Number} dw Delta width. - * @param {Number} dh Delta height. - * @param {window/id} win Window if the dialog isn't inline. Id if the dialog is inline. - */ - resizeBy : function(dw, dh, win) { - win.resizeBy(dw, dh); - }, - - // Internal functions - - _decode : function(s) { - return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); - } - }); +/** + * WindowManager.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + /** + * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. + * + * @class tinymce.WindowManager + * @example + * // Opens a new dialog with the file.htm file and the size 320x240 + * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. + * tinyMCE.activeEditor.windowManager.open({ + * url : 'file.htm', + * width : 320, + * height : 240 + * }, { + * custom_param : 1 + * }); + * + * // Displays an alert box using the active editors window manager instance + * tinyMCE.activeEditor.windowManager.alert('Hello world!'); + * + * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm + * tinyMCE.activeEditor.windowManager.confirm("Do you want to do something", function(s) { + * if (s) + * tinyMCE.activeEditor.windowManager.alert("Ok"); + * else + * tinyMCE.activeEditor.windowManager.alert("Cancel"); + * }); + */ + tinymce.create('tinymce.WindowManager', { + /** + * Constructs a new window manager instance. + * + * @constructor + * @method WindowManager + * @param {tinymce.Editor} ed Editor instance that the windows are bound to. + */ + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + /** + * Opens a new window. + * + * @method open + * @param {Object} s Optional name/value settings collection contains things like width/height/url etc. + * @option {String} title Window title. + * @option {String} file URL of the file to open in the window. + * @option {Number} width Width in pixels. + * @option {Number} height Height in pixels. + * @option {Boolean} resizable Specifies whether the popup window is resizable or not. + * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not. + * @option {Boolean} inline Specifies whether to display in-line (set to 1 or true for in-line display; requires inlinepopups plugin). + * @option {String/Boolean} popup_css Optional CSS to use in the popup. Set to false to remove the default one. + * @option {Boolean} translate_i18n Specifies whether translation should occur or not of i18 key strings. Default is true. + * @option {String/bool} close_previous Specifies whether a previously opened popup window is to be closed or not (like when calling the file browser window over the advlink popup). + * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content larger than the popup size specified). + * @param {Object} p Optional parameters/arguments collection can be used by the dialogs to retrive custom parameters. + * @option {String} plugin_url url to plugin if opening plugin window that calls tinyMCEPopup.requireLangPack() and needs access to the plugin language js files + */ + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + /** + * Closes the specified window. This will also dispatch out a onClose event. + * + * @method close + * @param {Window} w Native window object to close. + */ + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + /** + * Creates a instance of a class. This method was needed since IE can't create instances + * of classes from a parent window due to some reference problem. Any arguments passed after the class name + * will be passed as arguments to the constructor. + * + * @method createInstance + * @param {String} cl Class name to create an instance of. + * @return {Object} Instance of the specified class. + * @example + * var uri = tinyMCEPopup.editor.windowManager.createInstance('tinymce.util.URI', 'http://www.somesite.com'); + * alert(uri.getURI()); + */ + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + /** + * Creates a confirm dialog. Please don't use the blocking behavior of this + * native version use the callback method instead then it can be extended. + * + * @method confirm + * @param {String} t Title for the new confirm dialog. + * @param {function} cb Callback function to be executed after the user has selected ok or cancel. + * @param {Object} s Optional scope to execute the callback in. + * @example + * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm + * tinyMCE.activeEditor.windowManager.confirm("Do you want to do something", function(s) { + * if (s) + * tinyMCE.activeEditor.windowManager.alert("Ok"); + * else + * tinyMCE.activeEditor.windowManager.alert("Cancel"); + * }); + */ + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + /** + * Creates a alert dialog. Please don't use the blocking behavior of this + * native version use the callback method instead then it can be extended. + * + * @method alert + * @param {String} t Title for the new alert dialog. + * @param {function} cb Callback function to be executed after the user has selected ok. + * @param {Object} s Optional scope to execute the callback in. + * @example + * // Displays an alert box using the active editors window manager instance + * tinyMCE.activeEditor.windowManager.alert('Hello world!'); + */ + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + /** + * Resizes the specified window or id. + * + * @param {Number} dw Delta width. + * @param {Number} dh Delta height. + * @param {window/id} win Window if the dialog isn't inline. Id if the dialog is inline. + */ + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); }(tinymce)); \ No newline at end of file diff --git a/js/tiny_mce/classes/adapter/jquery/adapter.js b/js/tiny_mce/classes/adapter/jquery/adapter.js index 69c621e8..adae9dc7 100644 --- a/js/tiny_mce/classes/adapter/jquery/adapter.js +++ b/js/tiny_mce/classes/adapter/jquery/adapter.js @@ -1,336 +1,337 @@ -/** - * adapter.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -// #ifdef jquery_adapter - -(function($, tinymce) { - var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined; - - // jQuery is undefined - if (!$) - return alert("Load jQuery first!"); - - // Stick jQuery into the tinymce namespace - tinymce.$ = $; - - // Setup adapter - tinymce.adapter = { - patchEditor : function(editor) { - var fn = $.fn; - - // Adapt the css function to make sure that the _mce_style - // attribute gets updated with the new style information - function css(name, value) { - var self = this; - - // Remove _mce_style when set operation occurs - if (value) - self.removeAttr('_mce_style'); - - return fn.css.apply(self, arguments); - }; - - // Apapt the attr function to make sure that it uses the _mce_ prefixed variants - function attr(name, value) { - var self = this; - - // Update/retrive _mce_ attribute variants - if (attrRegExp.test(name)) { - if (value !== undefined) { - // Use TinyMCE behavior when setting the specifc attributes - self.each(function(i, node) { - editor.dom.setAttrib(node, name, value); - }); - - return self; - } else - return self.attr('_mce_' + name); - } - - // Default behavior - return fn.attr.apply(self, arguments); - }; - - function htmlPatchFunc(func) { - // Returns a modified function that processes - // the HTML before executing the action this makes sure - // that href/src etc gets moved into the _mce_ variants - return function(content) { - if (content) - content = editor.dom.processHTML(content); - - return func.call(this, content); - }; - }; - - // Patch various jQuery functions to handle tinymce specific attribute and content behavior - // we don't patch the jQuery.fn directly since it will most likely break compatibility - // with other jQuery logic on the page. Only instances created by TinyMCE should be patched. - function patch(jq) { - // Patch some functions, only patch the object once - if (jq.css !== css) { - // Patch css/attr to use the _mce_ prefixed attribute variants - jq.css = css; - jq.attr = attr; - - // Patch HTML functions to use the DOMUtils.processHTML filter logic - jq.html = htmlPatchFunc(fn.html); - jq.append = htmlPatchFunc(fn.append); - jq.prepend = htmlPatchFunc(fn.prepend); - jq.after = htmlPatchFunc(fn.after); - jq.before = htmlPatchFunc(fn.before); - jq.replaceWith = htmlPatchFunc(fn.replaceWith); - jq.tinymce = editor; - - // Each pushed jQuery instance needs to be patched - // as well for example when traversing the DOM - jq.pushStack = function() { - return patch(fn.pushStack.apply(this, arguments)); - }; - } - - return jq; - }; - - // Add a $ function on each editor instance this one is scoped for the editor document object - // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red'); - editor.$ = function(selector, scope) { - var doc = editor.getDoc(); - - return patch($(selector || doc, doc || scope)); - }; - } - }; - - // Patch in core NS functions - tinymce.extend = $.extend; - tinymce.extend(tinymce, { - map : $.map, - grep : function(a, f) {return $.grep(a, f || function(){return 1;});}, - inArray : function(a, v) {return $.inArray(v, a || []);} - - /* Didn't iterate stylesheets - each : function(o, cb, s) { - if (!o) - return 0; - - var r = 1; - - $.each(o, function(nr, el){ - if (cb.call(s, el, nr, o) === false) { - r = 0; - return false; - } - }); - - return r; - }*/ - }); - - // Patch in functions in various clases - // Add a "#ifndefjquery" statement around each core API function you add below - var patches = { - 'tinymce.dom.DOMUtils' : { - /* - addClass : function(e, c) { - if (is(e, 'array') && is(e[0], 'string')) - e = e.join(',#'); - return (e && $(is(e, 'string') ? '#' + e : e) - .addClass(c) - .attr('class')) || false; - }, - - hasClass : function(n, c) { - return $(is(n, 'string') ? '#' + n : n).hasClass(c); - }, - - removeClass : function(e, c) { - if (!e) - return false; - - var r = []; - - $(is(e, 'string') ? '#' + e : e) - .removeClass(c) - .each(function(){ - r.push(this.className); - }); - - return r.length == 1 ? r[0] : r; - }, - */ - - select : function(pattern, scope) { - var t = this; - - return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []); - }, - - is : function(n, patt) { - return $(this.get(n)).is(patt); - } - - /* - show : function(e) { - if (is(e, 'array') && is(e[0], 'string')) - e = e.join(',#'); - - $(is(e, 'string') ? '#' + e : e).css('display', 'block'); - }, - - hide : function(e) { - if (is(e, 'array') && is(e[0], 'string')) - e = e.join(',#'); - - $(is(e, 'string') ? '#' + e : e).css('display', 'none'); - }, - - isHidden : function(e) { - return $(is(e, 'string') ? '#' + e : e).is(':hidden'); - }, - - insertAfter : function(n, e) { - return $(is(e, 'string') ? '#' + e : e).after(n); - }, - - replace : function(o, n, k) { - n = $(is(n, 'string') ? '#' + n : n); - - if (k) - n.children().appendTo(o); - - n.replaceWith(o); - }, - - setStyle : function(n, na, v) { - if (is(n, 'array') && is(n[0], 'string')) - n = n.join(',#'); - - $(is(n, 'string') ? '#' + n : n).css(na, v); - }, - - getStyle : function(n, na, c) { - return $(is(n, 'string') ? '#' + n : n).css(na); - }, - - setStyles : function(e, o) { - if (is(e, 'array') && is(e[0], 'string')) - e = e.join(',#'); - $(is(e, 'string') ? '#' + e : e).css(o); - }, - - setAttrib : function(e, n, v) { - var t = this, s = t.settings; - - if (is(e, 'array') && is(e[0], 'string')) - e = e.join(',#'); - - e = $(is(e, 'string') ? '#' + e : e); - - switch (n) { - case "style": - e.each(function(i, v){ - if (s.keep_values) - $(v).attr('_mce_style', v); - - v.style.cssText = v; - }); - break; - - case "class": - e.each(function(){ - this.className = v; - }); - break; - - case "src": - case "href": - e.each(function(i, v){ - if (s.keep_values) { - if (s.url_converter) - v = s.url_converter.call(s.url_converter_scope || t, v, n, v); - - t.setAttrib(v, '_mce_' + n, v); - } - }); - - break; - } - - if (v !== null && v.length !== 0) - e.attr(n, '' + v); - else - e.removeAttr(n); - }, - - setAttribs : function(e, o) { - var t = this; - - $.each(o, function(n, v){ - t.setAttrib(e,n,v); - }); - } - */ - } - -/* - 'tinymce.dom.Event' : { - add : function (o, n, f, s) { - var lo, cb; - - cb = function(e) { - e.target = e.target || this; - f.call(s || this, e); - }; - - if (is(o, 'array') && is(o[0], 'string')) - o = o.join(',#'); - o = $(is(o, 'string') ? '#' + o : o); - if (n == 'init') { - o.ready(cb, s); - } else { - if (s) { - o.bind(n, s, cb); - } else { - o.bind(n, cb); - } - } - - lo = this._jqLookup || (this._jqLookup = []); - lo.push({func : f, cfunc : cb}); - - return cb; - }, - - remove : function(o, n, f) { - // Find cfunc - $(this._jqLookup).each(function() { - if (this.func === f) - f = this.cfunc; - }); - - if (is(o, 'array') && is(o[0], 'string')) - o = o.join(',#'); - - $(is(o, 'string') ? '#' + o : o).unbind(n,f); - - return true; - } - } -*/ - }; - - // Patch functions after a class is created - tinymce.onCreate = function(ty, c, p) { - tinymce.extend(p, patches[c]); - }; -})(window.jQuery, tinymce); - -// #endif +/** + * adapter.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +// #ifdef jquery_adapter + +(function($, tinymce) { + var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined; + + // jQuery is undefined + if (!$ && window.console) { + return console.log("Load jQuery first!"); + } + + // Stick jQuery into the tinymce namespace + tinymce.$ = $; + + // Setup adapter + tinymce.adapter = { + patchEditor : function(editor) { + var fn = $.fn; + + // Adapt the css function to make sure that the data-mce-style + // attribute gets updated with the new style information + function css(name, value) { + var self = this; + + // Remove data-mce-style when set operation occurs + if (value) + self.removeAttr('data-mce-style'); + + return fn.css.apply(self, arguments); + }; + + // Apapt the attr function to make sure that it uses the data-mce- prefixed variants + function attr(name, value) { + var self = this; + + // Update/retrive data-mce- attribute variants + if (attrRegExp.test(name)) { + if (value !== undefined) { + // Use TinyMCE behavior when setting the specifc attributes + self.each(function(i, node) { + editor.dom.setAttrib(node, name, value); + }); + + return self; + } else + return self.attr('data-mce-' + name); + } + + // Default behavior + return fn.attr.apply(self, arguments); + }; + + function htmlPatchFunc(func) { + // Returns a modified function that processes + // the HTML before executing the action this makes sure + // that href/src etc gets moved into the data-mce- variants + return function(content) { + if (content) + content = editor.dom.processHTML(content); + + return func.call(this, content); + }; + }; + + // Patch various jQuery functions to handle tinymce specific attribute and content behavior + // we don't patch the jQuery.fn directly since it will most likely break compatibility + // with other jQuery logic on the page. Only instances created by TinyMCE should be patched. + function patch(jq) { + // Patch some functions, only patch the object once + if (jq.css !== css) { + // Patch css/attr to use the data-mce- prefixed attribute variants + jq.css = css; + jq.attr = attr; + + // Patch HTML functions to use the DOMUtils.processHTML filter logic + jq.html = htmlPatchFunc(fn.html); + jq.append = htmlPatchFunc(fn.append); + jq.prepend = htmlPatchFunc(fn.prepend); + jq.after = htmlPatchFunc(fn.after); + jq.before = htmlPatchFunc(fn.before); + jq.replaceWith = htmlPatchFunc(fn.replaceWith); + jq.tinymce = editor; + + // Each pushed jQuery instance needs to be patched + // as well for example when traversing the DOM + jq.pushStack = function() { + return patch(fn.pushStack.apply(this, arguments)); + }; + } + + return jq; + }; + + // Add a $ function on each editor instance this one is scoped for the editor document object + // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red'); + editor.$ = function(selector, scope) { + var doc = editor.getDoc(); + + return patch($(selector || doc, doc || scope)); + }; + } + }; + + // Patch in core NS functions + tinymce.extend = $.extend; + tinymce.extend(tinymce, { + map : $.map, + grep : function(a, f) {return $.grep(a, f || function(){return 1;});}, + inArray : function(a, v) {return $.inArray(v, a || []);} + + /* Didn't iterate stylesheets + each : function(o, cb, s) { + if (!o) + return 0; + + var r = 1; + + $.each(o, function(nr, el){ + if (cb.call(s, el, nr, o) === false) { + r = 0; + return false; + } + }); + + return r; + }*/ + }); + + // Patch in functions in various clases + // Add a "#ifndefjquery" statement around each core API function you add below + var patches = { + 'tinymce.dom.DOMUtils' : { + /* + addClass : function(e, c) { + if (is(e, 'array') && is(e[0], 'string')) + e = e.join(',#'); + return (e && $(is(e, 'string') ? '#' + e : e) + .addClass(c) + .attr('class')) || false; + }, + + hasClass : function(n, c) { + return $(is(n, 'string') ? '#' + n : n).hasClass(c); + }, + + removeClass : function(e, c) { + if (!e) + return false; + + var r = []; + + $(is(e, 'string') ? '#' + e : e) + .removeClass(c) + .each(function(){ + r.push(this.className); + }); + + return r.length == 1 ? r[0] : r; + }, + */ + + select : function(pattern, scope) { + var t = this; + + return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []); + }, + + is : function(n, patt) { + return $(this.get(n)).is(patt); + } + + /* + show : function(e) { + if (is(e, 'array') && is(e[0], 'string')) + e = e.join(',#'); + + $(is(e, 'string') ? '#' + e : e).css('display', 'block'); + }, + + hide : function(e) { + if (is(e, 'array') && is(e[0], 'string')) + e = e.join(',#'); + + $(is(e, 'string') ? '#' + e : e).css('display', 'none'); + }, + + isHidden : function(e) { + return $(is(e, 'string') ? '#' + e : e).is(':hidden'); + }, + + insertAfter : function(n, e) { + return $(is(e, 'string') ? '#' + e : e).after(n); + }, + + replace : function(o, n, k) { + n = $(is(n, 'string') ? '#' + n : n); + + if (k) + n.children().appendTo(o); + + n.replaceWith(o); + }, + + setStyle : function(n, na, v) { + if (is(n, 'array') && is(n[0], 'string')) + n = n.join(',#'); + + $(is(n, 'string') ? '#' + n : n).css(na, v); + }, + + getStyle : function(n, na, c) { + return $(is(n, 'string') ? '#' + n : n).css(na); + }, + + setStyles : function(e, o) { + if (is(e, 'array') && is(e[0], 'string')) + e = e.join(',#'); + $(is(e, 'string') ? '#' + e : e).css(o); + }, + + setAttrib : function(e, n, v) { + var t = this, s = t.settings; + + if (is(e, 'array') && is(e[0], 'string')) + e = e.join(',#'); + + e = $(is(e, 'string') ? '#' + e : e); + + switch (n) { + case "style": + e.each(function(i, v){ + if (s.keep_values) + $(v).attr('data-mce-style', v); + + v.style.cssText = v; + }); + break; + + case "class": + e.each(function(){ + this.className = v; + }); + break; + + case "src": + case "href": + e.each(function(i, v){ + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, v); + + t.setAttrib(v, 'data-mce-' + n, v); + } + }); + + break; + } + + if (v !== null && v.length !== 0) + e.attr(n, '' + v); + else + e.removeAttr(n); + }, + + setAttribs : function(e, o) { + var t = this; + + $.each(o, function(n, v){ + t.setAttrib(e,n,v); + }); + } + */ + } + +/* + 'tinymce.dom.Event' : { + add : function (o, n, f, s) { + var lo, cb; + + cb = function(e) { + e.target = e.target || this; + f.call(s || this, e); + }; + + if (is(o, 'array') && is(o[0], 'string')) + o = o.join(',#'); + o = $(is(o, 'string') ? '#' + o : o); + if (n == 'init') { + o.ready(cb, s); + } else { + if (s) { + o.bind(n, s, cb); + } else { + o.bind(n, cb); + } + } + + lo = this._jqLookup || (this._jqLookup = []); + lo.push({func : f, cfunc : cb}); + + return cb; + }, + + remove : function(o, n, f) { + // Find cfunc + $(this._jqLookup).each(function() { + if (this.func === f) + f = this.cfunc; + }); + + if (is(o, 'array') && is(o[0], 'string')) + o = o.join(',#'); + + $(is(o, 'string') ? '#' + o : o).unbind(n,f); + + return true; + } + } +*/ + }; + + // Patch functions after a class is created + tinymce.onCreate = function(ty, c, p) { + tinymce.extend(p, patches[c]); + }; +})(window.jQuery, tinymce); + +// #endif diff --git a/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js b/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js index 7ea2c6c5..4187dc28 100644 --- a/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js +++ b/js/tiny_mce/classes/adapter/jquery/jquery.tinymce.js @@ -1,323 +1,336 @@ -/** - * jquery.tinymce.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function($) { - var undefined, - lazyLoading, - delayedInits = [], - win = window; - - $.fn.tinymce = function(settings) { - var self = this, url, ed, base, pos, lang, query = "", suffix = ""; - - // No match then just ignore the call - if (!self.length) - return; - - // Get editor instance - if (!settings) - return tinyMCE.get(self[0].id); - - function init() { - var editors = [], initCount = 0; - - // Apply patches to the jQuery object, only once - if (applyPatch) { - applyPatch(); - applyPatch = null; - } - - // Create an editor instance for each matched node - self.each(function(i, node) { - var ed, id = node.id, oninit = settings.oninit; - - // Generate unique id for target element if needed - if (!id) - node.id = id = tinymce.DOM.uniqueId(); - - // Create editor instance and render it - ed = new tinymce.Editor(id, settings); - editors.push(ed); - - // Add onInit event listener if the oninit setting is defined - // this logic will fire the oninit callback ones each - // matched editor instance is initialized - if (oninit) { - ed.onInit.add(function() { - var scope, func = oninit; - - // Fire the oninit event ones each editor instance is initialized - if (++initCount == editors.length) { - if (tinymce.is(func, "string")) { - scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, "")); - func = tinymce.resolve(func); - } - - // Call the oninit function with the object - func.apply(scope || tinymce, editors); - } - }); - } - }); - - // Render the editor instances in a separate loop since we - // need to have the full editors array used in the onInit calls - $.each(editors, function(i, ed) { - ed.render(); - }); - } - - // Load TinyMCE on demand, if we need to - if (!win["tinymce"] && !lazyLoading && (url = settings.script_url)) { - lazyLoading = 1; - base = url.substring(0, url.lastIndexOf("/")); - - // Check if it's a dev/src version they want to load then - // make sure that all plugins, themes etc are loaded in source mode aswell - if (/_(src|dev)\.js/g.test(url)) - suffix = "_src"; - - // Parse out query part, this will be appended to all scripts, css etc to clear browser cache - pos = url.lastIndexOf("?"); - if (pos != -1) - query = url.substring(pos + 1); - - // Setup tinyMCEPreInit object this will later be used by the TinyMCE - // core script to locate other resources like CSS files, dialogs etc - win.tinyMCEPreInit = { - base : base, - suffix : suffix, - query : query - }; - - // url contains gzip then we assume it's a compressor - if (url.indexOf('gzip') != -1) { - lang = settings.language || "en"; - url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) + "&themes=" + escape(settings.theme) + "&plugins=" + escape(settings.plugins) + "&languages=" + lang; - - // Check if compressor script is already loaded otherwise setup a basic one - if (!win["tinyMCE_GZ"]) { - tinyMCE_GZ = { - start : function() { - tinymce.suffix = suffix; - - function load(url) { - tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(url)); - } - - // Add core languages - load("langs/" + lang + ".js"); - - // Add themes with languages - load("themes/" + settings.theme + "/editor_template" + suffix + ".js"); - load("themes/" + settings.theme + "/langs/" + lang + ".js"); - - // Add plugins with languages - $.each(settings.plugins.split(","), function(i, name) { - if (name) { - load("plugins/" + name + "/editor_plugin" + suffix + ".js"); - load("plugins/" + name + "/langs/" + lang + ".js"); - } - }); - }, - - end : function() { - } - } - } - } - - // Load the script cached and execute the inits once it's done - $.ajax({ - type : "GET", - url : url, - dataType : "script", - cache : true, - success : function() { - tinymce.dom.Event.domLoaded = 1; - lazyLoading = 2; - init(); - - $.each(delayedInits, function(i, init) { - init(); - }); - } - }); - } else { - // Delay the init call until tinymce is loaded - if (lazyLoading === 1) - delayedInits.push(init); - else - init(); - } - }; - - // Add :tinymce psuedo selector this will select elements that has been converted into editor instances - // it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements. - $.extend($.expr[":"], { - tinymce : function(e) { - return e.id && !!tinyMCE.get(e.id); - } - }); - - // This function patches internal jQuery functions so that if - // you for example remove an div element containing an editor it's - // automatically destroyed by the TinyMCE API - function applyPatch() { - // Removes any child editor instances by looking for editor wrapper elements - function removeEditors(name) { - // If the function is remove - if (name === "remove") { - this.each(function(i, node) { - var ed = tinyMCEInstance(node); - - if (ed) - ed.remove(); - }); - } - - this.find("span.mceEditor,div.mceEditor").each(function(i, node) { - var ed = tinyMCE.get(node.id.replace(/_parent$/, "")); - - if (ed) - ed.remove(); - }); - } - - // Loads or saves contents from/to textarea if the value - // argument is defined it will set the TinyMCE internal contents - function loadOrSave(value) { - var self = this, ed; - - // Handle set value - if (value !== undefined) { - removeEditors.call(self); - - // Saves the contents before get/set value of textarea/div - self.each(function(i, node) { - var ed; - - if (ed = tinyMCE.get(node.id)) - ed.setContent(value); - }); - } else if (self.length > 0) { - // Handle get value - if (ed = tinyMCE.get(self[0].id)) - return ed.getContent(); - } - } - - // Returns tinymce instance for the specified element or null if it wasn't found - function tinyMCEInstance(element) { - var ed = null; - - (element) && (element.id) && (win["tinymce"]) && (ed = tinyMCE.get(element.id)); - - return ed; - } - - // Checks if the specified set contains tinymce instances - function containsTinyMCE(matchedSet) { - return !!((matchedSet) && (matchedSet.length) && (win["tinymce"]) && (matchedSet.is(":tinymce"))); - } - - // Patch various jQuery functions - var jQueryFn = {}; - - // Patch some setter/getter functions these will - // now be able to set/get the contents of editor instances for - // example $('#editorid').html('Content'); will update the TinyMCE iframe instance - $.each(["text", "html", "val"], function(i, name) { - var origFn = jQueryFn[name] = $.fn[name], - textProc = (name === "text"); - - $.fn[name] = function(value) { - var self = this; - - if (!containsTinyMCE(self)) - return origFn.call(self, value); - - if (value !== undefined) { - loadOrSave.call(self.filter(":tinymce"), value); - origFn.call(self.not(":tinymce"), value); - - return self; // return original set for chaining - } else { - var ret = ""; - - (textProc ? self : self.eq(0)).each(function(i, node) { - var ed = tinyMCEInstance(node); - - ret += ed ? (textProc ? ed.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, "") : ed.getContent()) : origFn.call($(node), value); - }); - - return ret; - } - }; - }); - - // Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe - $.each(["append", "prepend"], function(i, name) { - var origFn = jQueryFn[name] = $.fn[name], - prepend = (name === "prepend"); - - $.fn[name] = function(value) { - var self = this; - - if (!containsTinyMCE(self)) - return origFn.call(self, value); - - if (value !== undefined) { - self.filter(":tinymce").each(function(i, node) { - var ed = tinyMCEInstance(node); - - ed && ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value); - }); - - origFn.call(self.not(":tinymce"), value); - - return self; // return original set for chaining - } - }; - }); - - // Makes sure that the editor instance gets properly destroyed when the parent element is removed - $.each(["remove", "replaceWith", "replaceAll", "empty"], function(i, name) { - var origFn = jQueryFn[name] = $.fn[name]; - - $.fn[name] = function() { - removeEditors.call(this, name); - - return origFn.apply(this, arguments); - }; - }); - - jQueryFn.attr = $.fn.attr; - - // Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents - $.fn.attr = function(name, value, type) { - var self = this; - - if ((!name) || (name !== "value") || (!containsTinyMCE(self))) - return jQueryFn.attr.call(self, name, value, type); - - if (value !== undefined) { - loadOrSave.call(self.filter(":tinymce"), value); - jQueryFn.attr.call(self.not(":tinymce"), name, value, type); - - return self; // return original set for chaining - } else { - var node = self[0], ed = tinyMCEInstance(node); - - return ed ? ed.getContent() : jQueryFn.attr.call($(node), name, value, type); - } - }; - } +/** + * jquery.tinymce.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function($) { + var undefined, + lazyLoading, + delayedInits = [], + win = window; + + $.fn.tinymce = function(settings) { + var self = this, url, ed, base, pos, lang, query = "", suffix = ""; + + // No match then just ignore the call + if (!self.length) + return self; + + // Get editor instance + if (!settings) + return tinyMCE.get(self[0].id); + + self.css('visibility', 'hidden'); // Hide textarea to avoid flicker + + function init() { + var editors = [], initCount = 0; + + // Apply patches to the jQuery object, only once + if (applyPatch) { + applyPatch(); + applyPatch = null; + } + + // Create an editor instance for each matched node + self.each(function(i, node) { + var ed, id = node.id, oninit = settings.oninit; + + // Generate unique id for target element if needed + if (!id) + node.id = id = tinymce.DOM.uniqueId(); + + // Create editor instance and render it + ed = new tinymce.Editor(id, settings); + editors.push(ed); + + ed.onInit.add(function() { + var scope, func = oninit; + + self.css('visibility', ''); + + // Run this if the oninit setting is defined + // this logic will fire the oninit callback ones each + // matched editor instance is initialized + if (oninit) { + // Fire the oninit event ones each editor instance is initialized + if (++initCount == editors.length) { + if (tinymce.is(func, "string")) { + scope = (func.indexOf(".") === -1) ? null : tinymce.resolve(func.replace(/\.\w+$/, "")); + func = tinymce.resolve(func); + } + + // Call the oninit function with the object + func.apply(scope || tinymce, editors); + } + } + }); + }); + + // Render the editor instances in a separate loop since we + // need to have the full editors array used in the onInit calls + $.each(editors, function(i, ed) { + ed.render(); + }); + } + + // Load TinyMCE on demand, if we need to + if (!win["tinymce"] && !lazyLoading && (url = settings.script_url)) { + lazyLoading = 1; + base = url.substring(0, url.lastIndexOf("/")); + + // Check if it's a dev/src version they want to load then + // make sure that all plugins, themes etc are loaded in source mode aswell + if (/_(src|dev)\.js/g.test(url)) + suffix = "_src"; + + // Parse out query part, this will be appended to all scripts, css etc to clear browser cache + pos = url.lastIndexOf("?"); + if (pos != -1) + query = url.substring(pos + 1); + + // Setup tinyMCEPreInit object this will later be used by the TinyMCE + // core script to locate other resources like CSS files, dialogs etc + // You can also predefined a tinyMCEPreInit object and then it will use that instead + win.tinyMCEPreInit = win.tinyMCEPreInit || { + base : base, + suffix : suffix, + query : query + }; + + // url contains gzip then we assume it's a compressor + if (url.indexOf('gzip') != -1) { + lang = settings.language || "en"; + url = url + (/\?/.test(url) ? '&' : '?') + "js=true&core=true&suffix=" + escape(suffix) + "&themes=" + escape(settings.theme) + "&plugins=" + escape(settings.plugins) + "&languages=" + lang; + + // Check if compressor script is already loaded otherwise setup a basic one + if (!win["tinyMCE_GZ"]) { + tinyMCE_GZ = { + start : function() { + tinymce.suffix = suffix; + + function load(url) { + tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(url)); + } + + // Add core languages + load("langs/" + lang + ".js"); + + // Add themes with languages + load("themes/" + settings.theme + "/editor_template" + suffix + ".js"); + load("themes/" + settings.theme + "/langs/" + lang + ".js"); + + // Add plugins with languages + $.each(settings.plugins.split(","), function(i, name) { + if (name) { + load("plugins/" + name + "/editor_plugin" + suffix + ".js"); + load("plugins/" + name + "/langs/" + lang + ".js"); + } + }); + }, + + end : function() { + } + } + } + } + + // Load the script cached and execute the inits once it's done + $.ajax({ + type : "GET", + url : url, + dataType : "script", + cache : true, + success : function() { + tinymce.dom.Event.domLoaded = 1; + lazyLoading = 2; + + // Execute callback after mainscript has been loaded and before the initialization occurs + if (settings.script_loaded) + settings.script_loaded(); + + init(); + + $.each(delayedInits, function(i, init) { + init(); + }); + } + }); + } else { + // Delay the init call until tinymce is loaded + if (lazyLoading === 1) + delayedInits.push(init); + else + init(); + } + + return self; + }; + + // Add :tinymce psuedo selector this will select elements that has been converted into editor instances + // it's now possible to use things like $('*:tinymce') to get all TinyMCE bound elements. + $.extend($.expr[":"], { + tinymce : function(e) { + return e.id && !!tinyMCE.get(e.id); + } + }); + + // This function patches internal jQuery functions so that if + // you for example remove an div element containing an editor it's + // automatically destroyed by the TinyMCE API + function applyPatch() { + // Removes any child editor instances by looking for editor wrapper elements + function removeEditors(name) { + // If the function is remove + if (name === "remove") { + this.each(function(i, node) { + var ed = tinyMCEInstance(node); + + if (ed) + ed.remove(); + }); + } + + this.find("span.mceEditor,div.mceEditor").each(function(i, node) { + var ed = tinyMCE.get(node.id.replace(/_parent$/, "")); + + if (ed) + ed.remove(); + }); + } + + // Loads or saves contents from/to textarea if the value + // argument is defined it will set the TinyMCE internal contents + function loadOrSave(value) { + var self = this, ed; + + // Handle set value + if (value !== undefined) { + removeEditors.call(self); + + // Saves the contents before get/set value of textarea/div + self.each(function(i, node) { + var ed; + + if (ed = tinyMCE.get(node.id)) + ed.setContent(value); + }); + } else if (self.length > 0) { + // Handle get value + if (ed = tinyMCE.get(self[0].id)) + return ed.getContent(); + } + } + + // Returns tinymce instance for the specified element or null if it wasn't found + function tinyMCEInstance(element) { + var ed = null; + + (element) && (element.id) && (win["tinymce"]) && (ed = tinyMCE.get(element.id)); + + return ed; + } + + // Checks if the specified set contains tinymce instances + function containsTinyMCE(matchedSet) { + return !!((matchedSet) && (matchedSet.length) && (win["tinymce"]) && (matchedSet.is(":tinymce"))); + } + + // Patch various jQuery functions + var jQueryFn = {}; + + // Patch some setter/getter functions these will + // now be able to set/get the contents of editor instances for + // example $('#editorid').html('Content'); will update the TinyMCE iframe instance + $.each(["text", "html", "val"], function(i, name) { + var origFn = jQueryFn[name] = $.fn[name], + textProc = (name === "text"); + + $.fn[name] = function(value) { + var self = this; + + if (!containsTinyMCE(self)) + return origFn.apply(self, arguments); + + if (value !== undefined) { + loadOrSave.call(self.filter(":tinymce"), value); + origFn.apply(self.not(":tinymce"), arguments); + + return self; // return original set for chaining + } else { + var ret = ""; + var args = arguments; + + (textProc ? self : self.eq(0)).each(function(i, node) { + var ed = tinyMCEInstance(node); + + ret += ed ? (textProc ? ed.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g, "") : ed.getContent()) : origFn.apply($(node), args); + }); + + return ret; + } + }; + }); + + // Makes it possible to use $('#id').append("content"); to append contents to the TinyMCE editor iframe + $.each(["append", "prepend"], function(i, name) { + var origFn = jQueryFn[name] = $.fn[name], + prepend = (name === "prepend"); + + $.fn[name] = function(value) { + var self = this; + + if (!containsTinyMCE(self)) + return origFn.apply(self, arguments); + + if (value !== undefined) { + self.filter(":tinymce").each(function(i, node) { + var ed = tinyMCEInstance(node); + + ed && ed.setContent(prepend ? value + ed.getContent() : ed.getContent() + value); + }); + + origFn.apply(self.not(":tinymce"), arguments); + + return self; // return original set for chaining + } + }; + }); + + // Makes sure that the editor instance gets properly destroyed when the parent element is removed + $.each(["remove", "replaceWith", "replaceAll", "empty"], function(i, name) { + var origFn = jQueryFn[name] = $.fn[name]; + + $.fn[name] = function() { + removeEditors.call(this, name); + + return origFn.apply(this, arguments); + }; + }); + + jQueryFn.attr = $.fn.attr; + + // Makes sure that $('#tinymce_id').attr('value') gets the editors current HTML contents + $.fn.attr = function(name, value, type) { + var self = this; + + if ((!name) || (name !== "value") || (!containsTinyMCE(self))) + return jQueryFn.attr.call(self, name, value, type); + + if (value !== undefined) { + loadOrSave.call(self.filter(":tinymce"), value); + jQueryFn.attr.call(self.not(":tinymce"), name, value, type); + + return self; // return original set for chaining + } else { + var node = self[0], ed = tinyMCEInstance(node); + + return ed ? ed.getContent() : jQueryFn.attr.call($(node), name, value, type); + } + }; + } })(jQuery); \ No newline at end of file diff --git a/js/tiny_mce/classes/dom/DOMUtils.js b/js/tiny_mce/classes/dom/DOMUtils.js index 36c9bbba..783dbea1 100644 --- a/js/tiny_mce/classes/dom/DOMUtils.js +++ b/js/tiny_mce/classes/dom/DOMUtils.js @@ -1,2064 +1,1876 @@ -/** - * DOMUtils.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Shorten names - var each = tinymce.each, - is = tinymce.is, - isWebKit = tinymce.isWebKit, - isIE = tinymce.isIE, - blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/, - boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'), - mceAttribs = makeMap('src,href,style,coords,shape'), - encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'}, - encodeCharsRe = /[<>&\"]/g, - simpleSelectorRe = /^([a-z0-9],?)+$/i, - tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g, - attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; - - function makeMap(str) { - var map = {}, i; - - str = str.split(','); - for (i = str.length; i >= 0; i--) - map[str[i]] = 1; - - return map; - }; - - /** - * Utility class for various DOM manipulation and retrival functions. - * @class tinymce.dom.DOMUtils - */ - tinymce.create('tinymce.dom.DOMUtils', { - doc : null, - root : null, - files : null, - pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, - props : { - "for" : "htmlFor", - "class" : "className", - className : "className", - checked : "checked", - disabled : "disabled", - maxlength : "maxLength", - readonly : "readOnly", - selected : "selected", - value : "value", - id : "id", - name : "name", - type : "type" - }, - - /** - * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class. - * - * @constructor - * @method DOMUtils - * @param {Document} d Document reference to bind the utility class to. - * @param {settings} s Optional settings collection. - */ - DOMUtils : function(d, s) { - var t = this, globalStyle; - - t.doc = d; - t.win = window; - t.files = {}; - t.cssFlicker = false; - t.counter = 0; - t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat"; - t.stdMode = d.documentMode === 8; - - t.settings = s = tinymce.extend({ - keep_values : false, - hex_colors : 1, - process_html : 1 - }, s); - - // Fix IE6SP2 flicker and check it failed for pre SP2 - if (tinymce.isIE6) { - try { - d.execCommand('BackgroundImageCache', false, true); - } catch (e) { - t.cssFlicker = true; - } - } - - // Build styles list - if (s.valid_styles) { - t._styles = {}; - - // Convert styles into a rule list - each(s.valid_styles, function(value, key) { - t._styles[key] = tinymce.explode(value); - }); - } - - tinymce.addUnload(t.destroy, t); - }, - - /** - * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not - * go above the point of this root node. - * - * @method getRoot - * @return {Element} Root element for the utility class. - */ - getRoot : function() { - var t = this, s = t.settings; - - return (s && t.get(s.root_element)) || t.doc.body; - }, - - /** - * Returns the viewport of the window. - * - * @method getViewPort - * @param {Window} w Optional window to get viewport of. - * @return {Object} Viewport object with fields x, y, w and h. - */ - getViewPort : function(w) { - var d, b; - - w = !w ? this.win : w; - d = w.document; - b = this.boxModel ? d.documentElement : d.body; - - // Returns viewport size excluding scrollbars - return { - x : w.pageXOffset || b.scrollLeft, - y : w.pageYOffset || b.scrollTop, - w : w.innerWidth || b.clientWidth, - h : w.innerHeight || b.clientHeight - }; - }, - - /** - * Returns the rectangle for a specific element. - * - * @method getRect - * @param {Element/String} e Element object or element ID to get rectange from. - * @return {object} Rectange for specified element object with x, y, w, h fields. - */ - getRect : function(e) { - var p, t = this, sr; - - e = t.get(e); - p = t.getPos(e); - sr = t.getSize(e); - - return { - x : p.x, - y : p.y, - w : sr.w, - h : sr.h - }; - }, - - /** - * Returns the size dimensions of the specified element. - * - * @method getSize - * @param {Element/String} e Element object or element ID to get rectange from. - * @return {object} Rectange for specified element object with w, h fields. - */ - getSize : function(e) { - var t = this, w, h; - - e = t.get(e); - w = t.getStyle(e, 'width'); - h = t.getStyle(e, 'height'); - - // Non pixel value, then force offset/clientWidth - if (w.indexOf('px') === -1) - w = 0; - - // Non pixel value, then force offset/clientWidth - if (h.indexOf('px') === -1) - h = 0; - - return { - w : parseInt(w) || e.offsetWidth || e.clientWidth, - h : parseInt(h) || e.offsetHeight || e.clientHeight - }; - }, - - /** - * Returns a node by the specified selector function. This function will - * loop through all parent nodes and call the specified function for each node. - * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end - * and the node it found will be returned. - * - * @method getParent - * @param {Node/String} n DOM node to search parents on or ID string. - * @param {function} f Selection function to execute on each node or CSS pattern. - * @param {Node} r Optional root element, never go below this point. - * @return {Node} DOM Node or null if it wasn't found. - */ - getParent : function(n, f, r) { - return this.getParents(n, f, r, false); - }, - - /** - * Returns a node list of all parents matching the specified selector function or pattern. - * If the function then returns true indicating that it has found what it was looking for and that node will be collected. - * - * @method getParents - * @param {Node/String} n DOM node to search parents on or ID string. - * @param {function} f Selection function to execute on each node or CSS pattern. - * @param {Node} r Optional root element, never go below this point. - * @return {Array} Array of nodes or null if it wasn't found. - */ - getParents : function(n, f, r, c) { - var t = this, na, se = t.settings, o = []; - - n = t.get(n); - c = c === undefined; - - if (se.strict_root) - r = r || t.getRoot(); - - // Wrap node name as func - if (is(f, 'string')) { - na = f; - - if (f === '*') { - f = function(n) {return n.nodeType == 1;}; - } else { - f = function(n) { - return t.is(n, na); - }; - } - } - - while (n) { - if (n == r || !n.nodeType || n.nodeType === 9) - break; - - if (!f || f(n)) { - if (c) - o.push(n); - else - return n; - } - - n = n.parentNode; - } - - return c ? o : null; - }, - - /** - * Returns the specified element by ID or the input element if it isn't a string. - * - * @method get - * @param {String/Element} n Element id to look for or element to just pass though. - * @return {Element} Element matching the specified id or null if it wasn't found. - */ - get : function(e) { - var n; - - if (e && this.doc && typeof(e) == 'string') { - n = e; - e = this.doc.getElementById(e); - - // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick - if (e && e.id !== n) - return this.doc.getElementsByName(n)[1]; - } - - return e; - }, - - /** - * Returns the next node that matches selector or function - * - * @param {Node} node Node to find siblings from. - * @param {String/function} selector Selector CSS expression or function. - * @return {Node} Next node item matching the selector or null if it wasn't found. - */ - getNext : function(node, selector) { - return this._findSib(node, selector, 'nextSibling'); - }, - - /** - * Returns the previous node that matches selector or function - * - * @param {Node} node Node to find siblings from. - * @param {String/function} selector Selector CSS expression or function. - * @return {Node} Previous node item matching the selector or null if it wasn't found. - */ - getPrev : function(node, selector) { - return this._findSib(node, selector, 'previousSibling'); - }, - - // #ifndef jquery - - /** - * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test". - * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough - * on more complex patterns. - * - * @method select - * @param {String} p CSS level 1 pattern to select/find elements by. - * @param {Object} s Optional root element/scope element to search in. - * @return {Array} Array with all matched elements. - */ - select : function(pa, s) { - var t = this; - - return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); - }, - - /** - * Returns true/false if the specified element matches the specified css pattern. - * - * @method is - * @param {Node/NodeList} n DOM node to match or an array of nodes to match. - * @param {String} selector CSS pattern to match the element agains. - */ - is : function(n, selector) { - var i; - - // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance - if (n.length === undefined) { - // Simple all selector - if (selector === '*') - return n.nodeType == 1; - - // Simple selector just elements - if (simpleSelectorRe.test(selector)) { - selector = selector.toLowerCase().split(/,/); - n = n.nodeName.toLowerCase(); - - for (i = selector.length - 1; i >= 0; i--) { - if (selector[i] == n) - return true; - } - - return false; - } - } - - return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; - }, - - // #endif - - /** - * Adds the specified element to another element or elements. - * - * @method add - * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to. - * @param {String/Element} n Name of new element to add or existing element to add. - * @param {Object} a Optional object collection with arguments to add to the new element(s). - * @param {String} h Optional inner HTML contents to add for each element. - * @param {Boolean} c Optional internal state to indicate if it should create or add. - * @return {Element/Array} Element that got created or array with elements if multiple elements where passed. - */ - add : function(p, n, a, h, c) { - var t = this; - - return this.run(p, function(p) { - var e, k; - - e = is(n, 'string') ? t.doc.createElement(n) : n; - t.setAttribs(e, a); - - if (h) { - if (h.nodeType) - e.appendChild(h); - else - t.setHTML(e, h); - } - - return !c ? p.appendChild(e) : e; - }); - }, - - /** - * Creates a new element. - * - * @method create - * @param {String} n Name of new element. - * @param {Object} a Optional object name/value collection with element attributes. - * @param {String} h Optional HTML string to set as inner HTML of the element. - * @return {Element} HTML DOM node element that got created. - */ - create : function(n, a, h) { - return this.add(this.doc.createElement(n), n, a, h, 1); - }, - - /** - * Create HTML string for element. The elemtn will be closed unless an empty inner HTML string is passed. - * - * @method createHTML - * @param {String} n Name of new element. - * @param {Object} a Optional object name/value collection with element attributes. - * @param {String} h Optional HTML string to set as inner HTML of the element. - * @return {String} String with new HTML element like for example: test. - */ - createHTML : function(n, a, h) { - var o = '', t = this, k; - - o += '<' + n; - - for (k in a) { - if (a.hasOwnProperty(k)) - o += ' ' + k + '="' + t.encode(a[k]) + '"'; - } - - if (tinymce.is(h)) - return o + '>' + h + ''; - - return o + ' />'; - }, - - /** - * Removes/deletes the specified element(s) from the DOM. - * - * @method remove - * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids. - * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be placed at the location of the removed element. - * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input. - */ - remove : function(node, keep_children) { - return this.run(node, function(node) { - var parent, child; - - parent = node.parentNode; - - if (!parent) - return null; - - if (keep_children) { - while (child = node.firstChild) { - // IE 8 will crash if you don't remove completely empty text nodes - if (child.nodeType !== 3 || child.nodeValue) - parent.insertBefore(child, node); - else - node.removeChild(child); - } - } - - return parent.removeChild(node); - }); - }, - - /** - * Sets the CSS style value on a HTML element. The name can be a camelcase string - * or the CSS style name like background-color. - * - * @method setStyle - * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on. - * @param {String} na Name of the style value to set. - * @param {String} v Value to set on the style. - */ - setStyle : function(n, na, v) { - var t = this; - - return t.run(n, function(e) { - var s, i; - - s = e.style; - - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); - - // Default px suffix on these - if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) - v += 'px'; - - switch (na) { - case 'opacity': - // IE specific opacity - if (isIE) { - s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; - - if (!n.currentStyle || !n.currentStyle.hasLayout) - s.display = 'inline-block'; - } - - // Fix for older browsers - s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; - break; - - case 'float': - isIE ? s.styleFloat = v : s.cssFloat = v; - break; - - default: - s[na] = v || ''; - } - - // Force update of the style data - if (t.settings.update_styles) - t.setAttrib(e, '_mce_style'); - }); - }, - - /** - * Returns the current style or runtime/computed value of a element. - * - * @method getStyle - * @param {String/Element} n HTML element or element id string to get style from. - * @param {String} na Style name to return. - * @param {String} c Computed style. - * @return {String} Current style or computed style value of a element. - */ - getStyle : function(n, na, c) { - n = this.get(n); - - if (!n) - return false; - - // Gecko - if (this.doc.defaultView && c) { - // Remove camelcase - na = na.replace(/[A-Z]/g, function(a){ - return '-' + a; - }); - - try { - return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); - } catch (ex) { - // Old safari might fail - return null; - } - } - - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); - - if (na == 'float') - na = isIE ? 'styleFloat' : 'cssFloat'; - - // IE & Opera - if (n.currentStyle && c) - return n.currentStyle[na]; - - return n.style[na]; - }, - - /** - * Sets multiple styles on the specified element(s). - * - * @method setStyles - * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on. - * @param {Object} o Name/Value collection of style items to add to the element(s). - */ - setStyles : function(e, o) { - var t = this, s = t.settings, ol; - - ol = s.update_styles; - s.update_styles = 0; - - each(o, function(v, n) { - t.setStyle(e, n, v); - }); - - // Update style info - s.update_styles = ol; - if (s.update_styles) - t.setAttrib(e, s.cssText); - }, - - /** - * Sets the specified attributes value of a element or elements. - * - * @method setAttrib - * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on. - * @param {String} n Name of attribute to set. - * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead. - */ - setAttrib : function(e, n, v) { - var t = this; - - // Whats the point - if (!e || !n) - return; - - // Strict XML mode - if (t.settings.strict) - n = n.toLowerCase(); - - return this.run(e, function(e) { - var s = t.settings; - - switch (n) { - case "style": - if (!is(v, 'string')) { - each(v, function(v, n) { - t.setStyle(e, n, v); - }); - - return; - } - - // No mce_style for elements with these since they might get resized by the user - if (s.keep_values) { - if (v && !t._isRes(v)) - e.setAttribute('_mce_style', v, 2); - else - e.removeAttribute('_mce_style', 2); - } - - e.style.cssText = v; - break; - - case "class": - e.className = v || ''; // Fix IE null bug - break; - - case "src": - case "href": - if (s.keep_values) { - if (s.url_converter) - v = s.url_converter.call(s.url_converter_scope || t, v, n, e); - - t.setAttrib(e, '_mce_' + n, v, 2); - } - - break; - - case "shape": - e.setAttribute('_mce_style', v); - break; - } - - if (is(v) && v !== null && v.length !== 0) - e.setAttribute(n, '' + v, 2); - else - e.removeAttribute(n, 2); - }); - }, - - /** - * Sets the specified attributes of a element or elements. - * - * @method setAttribs - * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on. - * @param {Object} o Name/Value collection of attribute items to add to the element(s). - */ - setAttribs : function(e, o) { - var t = this; - - return this.run(e, function(e) { - each(o, function(v, n) { - t.setAttrib(e, n, v); - }); - }); - }, - - /** - * Returns the specified attribute by name. - * - * @method getAttrib - * @param {String/Element} e Element string id or DOM element to get attribute from. - * @param {String} n Name of attribute to get. - * @param {String} dv Optional default value to return if the attribute didn't exist. - * @return {String} Attribute value string, default value or null if the attribute wasn't found. - */ - getAttrib : function(e, n, dv) { - var v, t = this; - - e = t.get(e); - - if (!e || e.nodeType !== 1) - return false; - - if (!is(dv)) - dv = ''; - - // Try the mce variant for these - if (/^(src|href|style|coords|shape)$/.test(n)) { - v = e.getAttribute("_mce_" + n); - - if (v) - return v; - } - - if (isIE && t.props[n]) { - v = e[t.props[n]]; - v = v && v.nodeValue ? v.nodeValue : v; - } - - if (!v) - v = e.getAttribute(n, 2); - - // Check boolean attribs - if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { - if (e[t.props[n]] === true && v === '') - return n; - - return v ? n : ''; - } - - // Inner input elements will override attributes on form elements - if (e.nodeName === "FORM" && e.getAttributeNode(n)) - return e.getAttributeNode(n).nodeValue; - - if (n === 'style') { - v = v || e.style.cssText; - - if (v) { - v = t.serializeStyle(t.parseStyle(v), e.nodeName); - - if (t.settings.keep_values && !t._isRes(v)) - e.setAttribute('_mce_style', v); - } - } - - // Remove Apple and WebKit stuff - if (isWebKit && n === "class" && v) - v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); - - // Handle IE issues - if (isIE) { - switch (n) { - case 'rowspan': - case 'colspan': - // IE returns 1 as default value - if (v === 1) - v = ''; - - break; - - case 'size': - // IE returns +0 as default value for size - if (v === '+0' || v === 20 || v === 0) - v = ''; - - break; - - case 'width': - case 'height': - case 'vspace': - case 'checked': - case 'disabled': - case 'readonly': - if (v === 0) - v = ''; - - break; - - case 'hspace': - // IE returns -1 as default value - if (v === -1) - v = ''; - - break; - - case 'maxlength': - case 'tabindex': - // IE returns default value - if (v === 32768 || v === 2147483647 || v === '32768') - v = ''; - - break; - - case 'multiple': - case 'compact': - case 'noshade': - case 'nowrap': - if (v === 65535) - return n; - - return dv; - - case 'shape': - v = v.toLowerCase(); - break; - - default: - // IE has odd anonymous function for event attributes - if (n.indexOf('on') === 0 && v) - v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1'); - } - } - - return (v !== undefined && v !== null && v !== '') ? '' + v : dv; - }, - - /** - * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields. - * - * @method getPos - * @param {Element/String} n HTML element or element id to get x, y position from. - * @param {Element} ro Optional root element to stop calculations at. - * @return {object} Absolute position of the specified element object with x, y fields. - */ - getPos : function(n, ro) { - var t = this, x = 0, y = 0, e, d = t.doc, r; - - n = t.get(n); - ro = ro || d.body; - - if (n) { - // Use getBoundingClientRect on IE, Opera has it but it's not perfect - if (isIE && !t.stdMode) { - n = n.getBoundingClientRect(); - e = t.boxModel ? d.documentElement : d.body; - x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border - x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x; - n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset - - return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x}; - } - - r = n; - while (r && r != ro && r.nodeType) { - x += r.offsetLeft || 0; - y += r.offsetTop || 0; - r = r.offsetParent; - } - - r = n.parentNode; - while (r && r != ro && r.nodeType) { - x -= r.scrollLeft || 0; - y -= r.scrollTop || 0; - r = r.parentNode; - } - } - - return {x : x, y : y}; - }, - - /** - * Parses the specified style value into an object collection. This parser will also - * merge and remove any redundant items that browsers might have added. It will also convert non hex - * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. - * - * @method parseStyle - * @param {String} st Style value to parse for example: border:1px solid red;. - * @return {Object} Object representation of that style like {border : '1px solid red'} - */ - parseStyle : function(st) { - var t = this, s = t.settings, o = {}; - - if (!st) - return o; - - function compress(p, s, ot) { - var t, r, b, l; - - // Get values and check it it needs compressing - t = o[p + '-top' + s]; - if (!t) - return; - - r = o[p + '-right' + s]; - if (t != r) - return; - - b = o[p + '-bottom' + s]; - if (r != b) - return; - - l = o[p + '-left' + s]; - if (b != l) - return; - - // Compress - o[ot] = l; - delete o[p + '-top' + s]; - delete o[p + '-right' + s]; - delete o[p + '-bottom' + s]; - delete o[p + '-left' + s]; - }; - - function compress2(ta, a, b, c) { - var t; - - t = o[a]; - if (!t) - return; - - t = o[b]; - if (!t) - return; - - t = o[c]; - if (!t) - return; - - // Compress - o[ta] = o[a] + ' ' + o[b] + ' ' + o[c]; - delete o[a]; - delete o[b]; - delete o[c]; - }; - - st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities - - each(st.split(';'), function(v) { - var sv, ur = []; - - if (v) { - v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities - v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';}); - v = v.split(':'); - sv = tinymce.trim(v[1]); - sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];}); - - sv = sv.replace(/rgb\([^\)]+\)/g, function(v) { - return t.toHex(v); - }); - - if (s.url_converter) { - sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) { - return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')'; - }); - } - - o[tinymce.trim(v[0]).toLowerCase()] = sv; - } - }); - - compress("border", "", "border"); - compress("border", "-width", "border-width"); - compress("border", "-color", "border-color"); - compress("border", "-style", "border-style"); - compress("padding", "", "padding"); - compress("margin", "", "margin"); - compress2('border', 'border-width', 'border-style', 'border-color'); - - if (isIE) { - // Remove pointless border - if (o.border == 'medium none') - o.border = ''; - } - - return o; - }, - - /** - * Serializes the specified style object into a string. - * - * @method serializeStyle - * @param {Object} o Object to serialize as string for example: {border : '1px solid red'} - * @param {String} name Optional element name. - * @return {String} String representation of the style object for example: border: 1px solid red. - */ - serializeStyle : function(o, name) { - var t = this, s = ''; - - function add(v, k) { - if (k && v) { - // Remove browser specific styles like -moz- or -webkit- - if (k.indexOf('-') === 0) - return; - - switch (k) { - case 'font-weight': - // Opera will output bold as 700 - if (v == 700) - v = 'bold'; - - break; - - case 'color': - case 'background-color': - v = v.toLowerCase(); - break; - } - - s += (s ? ' ' : '') + k + ': ' + v + ';'; - } - }; - - // Validate style output - if (name && t._styles) { - each(t._styles['*'], function(name) { - add(o[name], name); - }); - - each(t._styles[name.toLowerCase()], function(name) { - add(o[name], name); - }); - } else - each(o, add); - - return s; - }, - - /** - * Imports/loads the specified CSS file into the document bound to the class. - * - * @method loadCSS - * @param {String} u URL to CSS file to load. - */ - loadCSS : function(u) { - var t = this, d = t.doc, head; - - if (!u) - u = ''; - - head = t.select('head')[0]; - - each(u.split(','), function(u) { - var link; - - if (t.files[u]) - return; - - t.files[u] = true; - link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); - - // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug - // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading - // It's ugly but it seems to work fine. - if (isIE && d.documentMode) { - link.onload = function() { - d.recalc(); - link.onload = null; - }; - } - - head.appendChild(link); - }); - }, - - /** - * Adds a class to the specified element or elements. - * - * @method addClass - * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs. - * @param {String} c Class name to add to each element. - * @return {String/Array} String with new class value or array with new class values for all elements. - */ - addClass : function(e, c) { - return this.run(e, function(e) { - var o; - - if (!c) - return 0; - - if (this.hasClass(e, c)) - return e.className; - - o = this.removeClass(e, c); - - return e.className = (o != '' ? (o + ' ') : '') + c; - }); - }, - - /** - * Removes a class from the specified element or elements. - * - * @method removeClass - * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs. - * @param {String} c Class name to remove to each element. - * @return {String/Array} String with new class value or array with new class values for all elements. - */ - removeClass : function(e, c) { - var t = this, re; - - return t.run(e, function(e) { - var v; - - if (t.hasClass(e, c)) { - if (!re) - re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); - - v = e.className.replace(re, ' '); - v = tinymce.trim(v != ' ' ? v : ''); - - e.className = v; - - // Empty class attr - if (!v) { - e.removeAttribute('class'); - e.removeAttribute('className'); - } - - return v; - } - - return e.className; - }); - }, - - /** - * Returns true if the specified element has the specified class. - * - * @method hasClass - * @param {String/Element} n HTML element or element id string to check CSS class on. - * @param {String} c CSS class to check for. - * @return {Boolean} true/false if the specified element has the specified class. - */ - hasClass : function(n, c) { - n = this.get(n); - - if (!n || !c) - return false; - - return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; - }, - - /** - * Shows the specified element(s) by ID by setting the "display" style. - * - * @method show - * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show. - */ - show : function(e) { - return this.setStyle(e, 'display', 'block'); - }, - - /** - * Hides the specified element(s) by ID by setting the "display" style. - * - * @method hide - * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide. - */ - hide : function(e) { - return this.setStyle(e, 'display', 'none'); - }, - - /** - * Returns true/false if the element is hidden or not by checking the "display" style. - * - * @method isHidden - * @param {String/Element} e Id or element to check display state on. - * @return {Boolean} true/false if the element is hidden or not. - */ - isHidden : function(e) { - e = this.get(e); - - return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; - }, - - /** - * Returns a unique id. This can be useful when generating elements on the fly. - * This method will not check if the element allready exists. - * - * @method uniqueId - * @param {String} p Optional prefix to add infront of all ids defaults to "mce_". - * @return {String} Unique id. - */ - uniqueId : function(p) { - return (!p ? 'mce_' : p) + (this.counter++); - }, - - /** - * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means - * URLs will get converted, hex color values fixed etc. Check processHTML for details. - * - * @method setHTML - * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside. - * @param {String} h HTML content to set as inner HTML of the element. - */ - setHTML : function(e, h) { - var t = this; - - return this.run(e, function(e) { - var x, i, nl, n, p, x; - - h = t.processHTML(h); - - if (isIE) { - function set() { - // Remove all child nodes - while (e.firstChild) - e.firstChild.removeNode(); - - try { - // IE will remove comments from the beginning - // unless you padd the contents with something - e.innerHTML = '
    ' + h; - e.removeChild(e.firstChild); - } catch (ex) { - // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p - // This seems to fix this problem - - // Create new div with HTML contents and a BR infront to keep comments - x = t.create('div'); - x.innerHTML = '
    ' + h; - - // Add all children from div to target - each (x.childNodes, function(n, i) { - // Skip br element - if (i) - e.appendChild(n); - }); - } - }; - - // IE has a serious bug when it comes to paragraphs it can produce an invalid - // DOM tree if contents like this

    • Item 1

    is inserted - // It seems to be that IE doesn't like a root block element placed inside another root block element - if (t.settings.fix_ie_paragraphs) - h = h.replace(/

    <\/p>|]+)><\/p>|/gi, ' 

    '); - - set(); - - if (t.settings.fix_ie_paragraphs) { - // Check for odd paragraphs this is a sign of a broken DOM - nl = e.getElementsByTagName("p"); - for (i = nl.length - 1, x = 0; i >= 0; i--) { - n = nl[i]; - - if (!n.hasChildNodes()) { - if (!n._mce_keep) { - x = 1; // Is broken - break; - } - - n.removeAttribute('_mce_keep'); - } - } - } - - // Time to fix the madness IE left us - if (x) { - // So if we replace the p elements with divs and mark them and then replace them back to paragraphs - // after we use innerHTML we can fix the DOM tree - h = h.replace(/

    ]+)>|

    /ig, '

    '); - h = h.replace(/<\/p>/g, '
    '); - - // Set the new HTML with DIVs - set(); - - // Replace all DIV elements with the _mce_tmp attibute back to paragraphs - // This is needed since IE has a annoying bug see above for details - // This is a slow process but it has to be done. :( - if (t.settings.fix_ie_paragraphs) { - nl = e.getElementsByTagName("DIV"); - for (i = nl.length - 1; i >= 0; i--) { - n = nl[i]; - - // Is it a temp div - if (n._mce_tmp) { - // Create new paragraph - p = t.doc.createElement('p'); - - // Copy all attributes - n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) { - var v; - - if (b !== '_mce_tmp') { - v = n.getAttribute(b); - - if (!v && b === 'class') - v = n.className; - - p.setAttribute(b, v); - } - }); - - // Append all children to new paragraph - for (x = 0; x]+)\/>|/gi, ''); // Force open - - // Store away src and href in _mce_src and mce_href since browsers mess them up - if (s.keep_values) { - // Wrap scripts and styles in comments for serialization purposes - if (/)/g, '\n'); - s = s.replace(/^[\r\n]*|[\r\n]*$/g, ''); - s = s.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); - - return s; - }; - - // Wrap the script contents in CDATA and keep them from executing - h = h.replace(/]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) { - // Force type attribute - if (!attribs) - attribs = ' type="text/javascript"'; - - // Convert the src attribute of the scripts - attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) { - if (s.url_converter) - url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script')); - - return '_mce_src="' + url + '"'; - }); - - // Wrap text contents - if (tinymce.trim(text)) { - codeBlocks.push(trim(text)); - text = ''; - } - - return '' + text + ''; - }); - - // Wrap style elements - h = h.replace(/]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) { - // Wrap text contents - if (text) { - codeBlocks.push(trim(text)); - text = ''; - } - - return '' + text + ''; - }); - - // Wrap noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return ''; - }); - } - - h = h.replace(//g, ''); - - // This function processes the attributes in the HTML string to force boolean - // attributes to the attr="attr" format and convert style, src and href to _mce_ versions - function processTags(html) { - return html.replace(tagRegExp, function(match, elm_name, attrs, end) { - return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) { - var mceValue; - - name = name.toLowerCase(); - value = value || val2 || val3 || ""; - - // Treat boolean attributes - if (boolAttrs[name]) { - // false or 0 is treated as a missing attribute - if (value === 'false' || value === '0') - return; - - return name + '="' + name + '"'; - } - - // Is attribute one that needs special treatment - if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) { - mceValue = t.decode(value); - - // Convert URLs to relative/absolute ones - if (s.url_converter && (name == "src" || name == "href")) - mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name); - - // Process styles lowercases them and compresses them - if (name == 'style') - mceValue = t.serializeStyle(t.parseStyle(mceValue), name); - - return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"'; - } - - return match; - }) + end + '>'; - }); - }; - - h = processTags(h); - - // Restore script blocks - h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) { - return codeBlocks[idx]; - }); - } - - return h; - }, - - /** - * Returns the outer HTML of an element. - * - * @method getOuterHTML - * @param {String/Element} e Element ID or element object to get outer HTML from. - * @return {String} Outer HTML string. - */ - getOuterHTML : function(e) { - var d; - - e = this.get(e); - - if (!e) - return null; - - if (e.outerHTML !== undefined) - return e.outerHTML; - - d = (e.ownerDocument || this.doc).createElement("body"); - d.appendChild(e.cloneNode(true)); - - return d.innerHTML; - }, - - /** - * Sets the specified outer HTML on a element or elements. - * - * @method setOuterHTML - * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on. - * @param {Object} h HTML code to set as outer value for the element. - * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class. - */ - setOuterHTML : function(e, h, d) { - var t = this; - - function setHTML(e, h, d) { - var n, tp; - - tp = d.createElement("body"); - tp.innerHTML = h; - - n = tp.lastChild; - while (n) { - t.insertAfter(n.cloneNode(true), e); - n = n.previousSibling; - } - - t.remove(e); - }; - - return this.run(e, function(e) { - e = t.get(e); - - // Only set HTML on elements - if (e.nodeType == 1) { - d = d || e.ownerDocument || t.doc; - - if (isIE) { - try { - // Try outerHTML for IE it sometimes produces an unknown runtime error - if (isIE && e.nodeType == 1) - e.outerHTML = h; - else - setHTML(e, h, d); - } catch (ex) { - // Fix for unknown runtime error - setHTML(e, h, d); - } - } else - setHTML(e, h, d); - } - }); - }, - - /** - * Entity decode a string, resolves any HTML entities like å. - * - * @method decode - * @param {String} s String to decode entities on. - * @return {String} Entity decoded string. - */ - decode : function(s) { - var e, n, v; - - // Look for entities to decode - if (/&[\w#]+;/.test(s)) { - // Decode the entities using a div element not super efficient but less code - e = this.doc.createElement("div"); - e.innerHTML = s; - n = e.firstChild; - v = ''; - - if (n) { - do { - v += n.nodeValue; - } while (n = n.nextSibling); - } - - return v || s; - } - - return s; - }, - - /** - * Entity encodes a string, encodes the most common entities <>"& into entities. - * - * @method encode - * @param {String} s String to encode with entities. - * @return {String} Entity encoded string. - */ - encode : function(str) { - return ('' + str).replace(encodeCharsRe, function(chr) { - return encodedChars[chr]; - }); - }, - - /** - * Inserts a element after the reference element. - * - * @method insertAfter - * @param {Element} node Element to insert after the reference. - * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after. - * @return {Element/Array} Element that got added or an array with elements. - */ - insertAfter : function(node, reference_node) { - reference_node = this.get(reference_node); - - return this.run(node, function(node) { - var parent, nextSibling; - - parent = reference_node.parentNode; - nextSibling = reference_node.nextSibling; - - if (nextSibling) - parent.insertBefore(node, nextSibling); - else - parent.appendChild(node); - - return node; - }); - }, - - /** - * Returns true/false if the specified element is a block element or not. - * - * @method isBlock - * @param {Node} n Element/Node to check. - * @return {Boolean} True/False state if the node is a block element or not. - */ - isBlock : function(n) { - if (n.nodeType && n.nodeType !== 1) - return false; - - n = n.nodeName || n; - - return blockRe.test(n); - }, - - /** - * Replaces the specified element or elements with the specified element, the new element will - * be cloned if multiple inputs elements are passed. - * - * @method replace - * @param {Element} n New element to replace old ones with. - * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace. - * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones. - */ - replace : function(n, o, k) { - var t = this; - - if (is(o, 'array')) - n = n.cloneNode(true); - - return t.run(o, function(o) { - if (k) { - each(tinymce.grep(o.childNodes), function(c) { - n.appendChild(c); - }); - } - - return o.parentNode.replaceChild(n, o); - }); - }, - - /** - * Renames the specified element to a new name and keep it's attributes and children. - * - * @method rename - * @param {Element} elm Element to rename. - * @param {String} name Name of the new element. - * @return New element or the old element if it needed renaming. - */ - rename : function(elm, name) { - var t = this, newElm; - - if (elm.nodeName != name.toUpperCase()) { - // Rename block element - newElm = t.create(name); - - // Copy attribs to new block - each(t.getAttribs(elm), function(attr_node) { - t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); - }); - - // Replace block - t.replace(newElm, elm, 1); - } - - return newElm || elm; - }, - - /** - * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic. - * - * @method findCommonAncestor - * @param {Element} a Element to find common ancestor of. - * @param {Element} b Element to find common ancestor of. - * @return {Element} Common ancestor element of the two input elements. - */ - findCommonAncestor : function(a, b) { - var ps = a, pe; - - while (ps) { - pe = b; - - while (pe && ps != pe) - pe = pe.parentNode; - - if (ps == pe) - break; - - ps = ps.parentNode; - } - - if (!ps && a.ownerDocument) - return a.ownerDocument.documentElement; - - return ps; - }, - - /** - * Parses the specified RGB color value and returns a hex version of that color. - * - * @method toHex - * @param {String} s RGB string value like rgb(1,2,3) - * @return {String} Hex version of that RGB value like #FF00FF. - */ - toHex : function(s) { - var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); - - function hex(s) { - s = parseInt(s).toString(16); - - return s.length > 1 ? s : '0' + s; // 0 -> 00 - }; - - if (c) { - s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); - - return s; - } - - return s; - }, - - /** - * Returns a array of all single CSS classes in the document. A single CSS class is a simple - * rule like ".class" complex ones like "div td.class" will not be added to output. - * - * @method getClasses - * @return {Array} Array with class objects each object has a class field might be other fields in the future. - */ - getClasses : function() { - var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; - - if (t.classes) - return t.classes; - - function addClasses(s) { - // IE style imports - each(s.imports, function(r) { - addClasses(r); - }); - - each(s.cssRules || s.rules, function(r) { - // Real type or fake it on IE - switch (r.type || 1) { - // Rule - case 1: - if (r.selectorText) { - each(r.selectorText.split(','), function(v) { - v = v.replace(/^\s*|\s*$|^\s\./g, ""); - - // Is internal or it doesn't contain a class - if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) - return; - - // Remove everything but class name - ov = v; - v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1'); - - // Filter classes - if (f && !(v = f(v, ov))) - return; - - if (!lo[v]) { - cl.push({'class' : v}); - lo[v] = 1; - } - }); - } - break; - - // Import - case 3: - addClasses(r.styleSheet); - break; - } - }); - }; - - try { - each(t.doc.styleSheets, addClasses); - } catch (ex) { - // Ignore - } - - if (cl.length > 0) - t.classes = cl; - - return cl; - }, - - /** - * Executes the specified function on the element by id or dom element node or array of elements/id. - * - * @method run - * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements. - * @param {function} f Function to execute for each item. - * @param {Object} s Optional scope to execute the function in. - * @return {Object/Array} Single object or array with objects depending on multiple input or not. - */ - run : function(e, f, s) { - var t = this, o; - - if (t.doc && typeof(e) === 'string') - e = t.get(e); - - if (!e) - return false; - - s = s || this; - if (!e.nodeType && (e.length || e.length === 0)) { - o = []; - - each(e, function(e, i) { - if (e) { - if (typeof(e) == 'string') - e = t.doc.getElementById(e); - - o.push(f.call(s, e, i)); - } - }); - - return o; - } - - return f.call(s, e); - }, - - /** - * Returns an NodeList with attributes for the element. - * - * @method getAttribs - * @param {HTMLElement/string} n Element node or string id to get attributes from. - * @return {NodeList} NodeList with attributes. - */ - getAttribs : function(n) { - var o; - - n = this.get(n); - - if (!n) - return []; - - if (isIE) { - o = []; - - // Object will throw exception in IE - if (n.nodeName == 'OBJECT') - return n.attributes; - - // IE doesn't keep the selected attribute if you clone option elements - if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) - o.push({specified : 1, nodeName : 'selected'}); - - // It's crazy that this is faster in IE but it's because it returns all attributes all the time - n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { - o.push({specified : 1, nodeName : a}); - }); - - return o; - } - - return n.attributes; - }, - - /** - * Destroys all internal references to the DOM to solve IE leak issues. - * - * @method destroy - */ - destroy : function(s) { - var t = this; - - if (t.events) - t.events.destroy(); - - t.win = t.doc = t.root = t.events = null; - - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - }, - - /** - * Created a new DOM Range object. This will use the native DOM Range API if it's - * available if it's not it will fallback to the custom TinyMCE implementation. - * - * @method createRng - * @return {DOMRange} DOM Range object. - */ - createRng : function() { - var d = this.doc; - - return d.createRange ? d.createRange() : new tinymce.dom.Range(this); - }, - - /** - * Returns the index of the specified node within it's parent. - * - * @param {Node} node Node to look for. - * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization. - * @return {Number} Index of the specified node. - */ - nodeIndex : function(node, normalized) { - var idx = 0, lastNodeType, lastNode, nodeType; - - if (node) { - for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { - nodeType = node.nodeType; - - // Handle normalization of text nodes - if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length)) - idx++; - - lastNodeType = nodeType; - } - } - - return idx; - }, - - /** - * Splits an element into two new elements and places the specified split - * element or element between the new ones. For example splitting the paragraph at the bold element in - * this example

    abcabc123

    would produce

    abc

    abc

    123

    . - * - * @method split - * @param {Element} pe Parent element to split. - * @param {Element} e Element to split at. - * @param {Element} re Optional replacement element to replace the split element by. - * @return {Element} Returns the split element or the replacement element if that is specified. - */ - split : function(pe, e, re) { - var t = this, r = t.createRng(), bef, aft, pa; - - // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense - // but we don't want that in our code since it serves no purpose for the end user - // For example if this is chopped: - //

    text 1CHOPtext 2

    - // would produce: - //

    text 1

    CHOP

    text 2

    - // this function will then trim of empty edges and produce: - //

    text 1

    CHOP

    text 2

    - function trim(node) { - var i, children = node.childNodes; - - if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark') - return; - - for (i = children.length - 1; i >= 0; i--) - trim(children[i]); - - if (node.nodeType != 9) { - // Keep non whitespace text nodes - if (node.nodeType == 3 && node.nodeValue.length > 0) - return; - - if (node.nodeType == 1) { - // If the only child is a bookmark then move it up - children = node.childNodes; - if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark') - node.parentNode.insertBefore(children[0], node); - - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) - return; - } - - t.remove(node); - } - - return node; - }; - - if (pe && e) { - // Get before chunk - r.setStart(pe.parentNode, t.nodeIndex(pe)); - r.setEnd(e.parentNode, t.nodeIndex(e)); - bef = r.extractContents(); - - // Get after chunk - r = t.createRng(); - r.setStart(e.parentNode, t.nodeIndex(e) + 1); - r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); - aft = r.extractContents(); - - // Insert before chunk - pa = pe.parentNode; - pa.insertBefore(trim(bef), pe); - - // Insert middle chunk - if (re) - pa.replaceChild(re, e); - else - pa.insertBefore(e, pe); - - // Insert after chunk - pa.insertBefore(trim(aft), pe); - t.remove(pe); - - return re || e; - } - }, - - /** - * Adds an event handler to the specified object. - * - * @method bind - * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents. - * @param {String} n Name of event handler to add for example: click. - * @param {function} f Function to execute when the event occurs. - * @param {Object} s Optional scope to execute the function in. - * @return {function} Function callback handler the same as the one passed in. - */ - bind : function(target, name, func, scope) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); - - return t.events.add(target, name, func, scope || this); - }, - - /** - * Removes the specified event handler by name and function from a element or collection of elements. - * - * @method unbind - * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from. - * @param {String} n Event handler name like for example: "click" - * @param {function} f Function to remove. - * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. - */ - unbind : function(target, name, func) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); - - return t.events.remove(target, name, func); - }, - - // #ifdef debug - - dumpRng : function(r) { - return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset; - }, - - // #endif - - _findSib : function(node, selector, name) { - var t = this, f = selector; - - if (node) { - // If expression make a function of it using is - if (is(f, 'string')) { - f = function(node) { - return t.is(node, selector); - }; - } - - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (f(node)) - return node; - } - } - - return null; - }, - - _isRes : function(c) { - // Is live resizble element - return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); - } - - /* - walk : function(n, f, s) { - var d = this.doc, w; - - if (d.createTreeWalker) { - w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); - - while ((n = w.nextNode()) != null) - f.call(s || this, n); - } else - tinymce.walk(n, f, 'childNodes', s); - } - */ - - /* - toRGB : function(s) { - var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); - - if (c) { - // #FFF -> #FFFFFF - if (!is(c[3])) - c[3] = c[2] = c[1]; - - return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; - } - - return s; - } - */ - }); - - /** - * Instance of DOMUtils for the current document. - * - * @property DOM - * @member tinymce - * @type tinymce.dom.DOMUtils - */ - tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); -})(tinymce); +/** + * DOMUtils.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + blockElementsMap = tinymce.html.Schema.blockElementsMap, + whiteSpaceRegExp = /^[ \t\r\n]*$/; + + /** + * Utility class for various DOM manipulation and retrival functions. + * + * @class tinymce.dom.DOMUtils + * @example + * // Add a class to an element by id in the page + * tinymce.DOM.addClass('someid', 'someclass'); + * + * // Add a class to an element by id inside the editor + * tinyMCE.activeEditor.dom.addClass('someid', 'someclass'); + */ + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, + + /** + * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class. + * + * @constructor + * @method DOMUtils + * @param {Document} d Document reference to bind the utility class to. + * @param {settings} s Optional settings collection. + */ + DOMUtils : function(d, s) { + var t = this, globalStyle, name; + + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); + + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); + + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } + } + + if (isIE && s.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + d.createElement(name); + }); + + // Create all custom elements + for (name in s.schema.getCustomElements()) { + d.createElement(name); + } + } + + tinymce.addUnload(t.destroy, t); + }, + + /** + * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not + * go above the point of this root node. + * + * @method getRoot + * @return {Element} Root element for the utility class. + */ + getRoot : function() { + var t = this, s = t.settings; + + return (s && t.get(s.root_element)) || t.doc.body; + }, + + /** + * Returns the viewport of the window. + * + * @method getViewPort + * @param {Window} w Optional window to get viewport of. + * @return {Object} Viewport object with fields x, y, w and h. + */ + getViewPort : function(w) { + var d, b; + + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; + + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, + + /** + * Returns the rectangle for a specific element. + * + * @method getRect + * @param {Element/String} e Element object or element ID to get rectange from. + * @return {object} Rectange for specified element object with x, y, w, h fields. + */ + getRect : function(e) { + var p, t = this, sr; + + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); + + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, + + /** + * Returns the size dimensions of the specified element. + * + * @method getSize + * @param {Element/String} e Element object or element ID to get rectange from. + * @return {object} Rectange for specified element object with w, h fields. + */ + getSize : function(e) { + var t = this, w, h; + + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); + + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; + + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; + + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, + + /** + * Returns a node by the specified selector function. This function will + * loop through all parent nodes and call the specified function for each node. + * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end + * and the node it found will be returned. + * + * @method getParent + * @param {Node/String} n DOM node to search parents on or ID string. + * @param {function} f Selection function to execute on each node or CSS pattern. + * @param {Node} r Optional root element, never go below this point. + * @return {Node} DOM Node or null if it wasn't found. + */ + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, + + /** + * Returns a node list of all parents matching the specified selector function or pattern. + * If the function then returns true indicating that it has found what it was looking for and that node will be collected. + * + * @method getParents + * @param {Node/String} n DOM node to search parents on or ID string. + * @param {function} f Selection function to execute on each node or CSS pattern. + * @param {Node} r Optional root element, never go below this point. + * @return {Array} Array of nodes or null if it wasn't found. + */ + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; + + n = t.get(n); + c = c === undefined; + + if (se.strict_root) + r = r || t.getRoot(); + + // Wrap node name as func + if (is(f, 'string')) { + na = f; + + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } + + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; + + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } + + n = n.parentNode; + } + + return c ? o : null; + }, + + /** + * Returns the specified element by ID or the input element if it isn't a string. + * + * @method get + * @param {String/Element} n Element id to look for or element to just pass though. + * @return {Element} Element matching the specified id or null if it wasn't found. + */ + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; + } + + return e; + }, + + /** + * Returns the next node that matches selector or function + * + * @method getNext + * @param {Node} node Node to find siblings from. + * @param {String/function} selector Selector CSS expression or function. + * @return {Node} Next node item matching the selector or null if it wasn't found. + */ + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, + + /** + * Returns the previous node that matches selector or function + * + * @method getPrev + * @param {Node} node Node to find siblings from. + * @param {String/function} selector Selector CSS expression or function. + * @return {Node} Previous node item matching the selector or null if it wasn't found. + */ + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, + + // #ifndef jquery + + /** + * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test". + * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough + * on more complex patterns. + * + * @method select + * @param {String} p CSS level 1 pattern to select/find elements by. + * @param {Object} s Optional root element/scope element to search in. + * @return {Array} Array with all matched elements. + * @example + * // Adds a class to all paragraphs in the currently active editor + * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass'); + * + * // Adds a class to all spans that has the test class in the currently active editor + * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass') + */ + select : function(pa, s) { + var t = this; + + return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); + }, + + /** + * Returns true/false if the specified element matches the specified css pattern. + * + * @method is + * @param {Node/NodeList} n DOM node to match or an array of nodes to match. + * @param {String} selector CSS pattern to match the element agains. + */ + is : function(n, selector) { + var i; + + // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance + if (n.length === undefined) { + // Simple all selector + if (selector === '*') + return n.nodeType == 1; + + // Simple selector just elements + if (simpleSelectorRe.test(selector)) { + selector = selector.toLowerCase().split(/,/); + n = n.nodeName.toLowerCase(); + + for (i = selector.length - 1; i >= 0; i--) { + if (selector[i] == n) + return true; + } + + return false; + } + } + + return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + }, + + // #endif + + /** + * Adds the specified element to another element or elements. + * + * @method add + * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to. + * @param {String/Element} n Name of new element to add or existing element to add. + * @param {Object} a Optional object collection with arguments to add to the new element(s). + * @param {String} h Optional inner HTML contents to add for each element. + * @param {Boolean} c Optional internal state to indicate if it should create or add. + * @return {Element/Array} Element that got created or array with elements if multiple elements where passed. + * @example + * // Adds a new paragraph to the end of the active editor + * tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content'); + */ + add : function(p, n, a, h, c) { + var t = this; + + return this.run(p, function(p) { + var e, k; + + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); + + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } + + return !c ? p.appendChild(e) : e; + }); + }, + + /** + * Creates a new element. + * + * @method create + * @param {String} n Name of new element. + * @param {Object} a Optional object name/value collection with element attributes. + * @param {String} h Optional HTML string to set as inner HTML of the element. + * @return {Element} HTML DOM node element that got created. + * @example + * // Adds an element where the caret/selection is in the active editor + * var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content'); + * tinyMCE.activeEditor.selection.setNode(el); + */ + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, + + /** + * Create HTML string for element. The element will be closed unless an empty inner HTML string is passed. + * + * @method createHTML + * @param {String} n Name of new element. + * @param {Object} a Optional object name/value collection with element attributes. + * @param {String} h Optional HTML string to set as inner HTML of the element. + * @return {String} String with new HTML element like for example: test. + * @example + * // Creates a html chunk and inserts it at the current selection/caret location + * tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line')); + */ + createHTML : function(n, a, h) { + var o = '', t = this, k; + + o += '<' + n; + + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } + + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; + + return o + ' />'; + }, + + /** + * Removes/deletes the specified element(s) from the DOM. + * + * @method remove + * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids. + * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be placed at the location of the removed element. + * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input. + * @example + * // Removes all paragraphs in the active editor + * tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p')); + * + * // Removes a element by id in the document + * tinyMCE.DOM.remove('mydiv'); + */ + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } + } + + return parent.removeChild(node); + }); + }, + + /** + * Sets the CSS style value on a HTML element. The name can be a camelcase string + * or the CSS style name like background-color. + * + * @method setStyle + * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on. + * @param {String} na Name of the style value to set. + * @param {String} v Value to set on the style. + * @example + * // Sets a style value on all paragraphs in the currently active editor + * tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red'); + * + * // Sets a style value to an element by id in the current document + * tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red'); + */ + setStyle : function(n, na, v) { + var t = this; + + return t.run(n, function(e) { + var s, i; + + s = e.style; + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; + + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } + + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; + + case 'float': + isIE ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } + + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, + + /** + * Returns the current style or runtime/computed value of a element. + * + * @method getStyle + * @param {String/Element} n HTML element or element id string to get style from. + * @param {String} na Style name to return. + * @param {Boolean} c Computed style. + * @return {String} Current style or computed style value of a element. + */ + getStyle : function(n, na, c) { + n = this.get(n); + + if (!n) + return; + + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } + } + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; + + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; + + return n.style ? n.style[na] : undefined; + }, + + /** + * Sets multiple styles on the specified element(s). + * + * @method setStyles + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on. + * @param {Object} o Name/Value collection of style items to add to the element(s). + * @example + * // Sets styles on all paragraphs in the currently active editor + * tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'}); + * + * // Sets styles to an element by id in the current document + * tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'}); + */ + setStyles : function(e, o) { + var t = this, s = t.settings, ol; + + ol = s.update_styles; + s.update_styles = 0; + + each(o, function(v, n) { + t.setStyle(e, n, v); + }); + + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, + + /** + * Removes all attributes from an element or elements. + * + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from. + */ + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, + + /** + * Sets the specified attributes value of a element or elements. + * + * @method setAttrib + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on. + * @param {String} n Name of attribute to set. + * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead. + * @example + * // Sets an attribute to all paragraphs in the active editor + * tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass'); + * + * // Sets an attribute to a specific element in the current page + * tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass'); + */ + setAttrib : function(e, n, v) { + var t = this; + + // Whats the point + if (!e || !n) + return; + + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); + + return this.run(e, function(e) { + var s = t.settings; + if (v !== null) { + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); + + return; + } + + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } + + e.style.cssText = v; + break; + + case "class": + e.className = v || ''; // Fix IE null bug + break; + + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + + t.setAttrib(e, 'data-mce-' + n, v, 2); + } + + break; + + case "shape": + e.setAttribute('data-mce-style', v); + break; + } + } + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + }); + }, + + /** + * Sets the specified attributes of a element or elements. + * + * @method setAttribs + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on. + * @param {Object} o Name/Value collection of attribute items to add to the element(s). + * @example + * // Sets some attributes to all paragraphs in the active editor + * tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'}); + * + * // Sets some attributes to a specific element in the current page + * tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'}); + */ + setAttribs : function(e, o) { + var t = this; + + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, + + /** + * Returns the specified attribute by name. + * + * @method getAttrib + * @param {String/Element} e Element string id or DOM element to get attribute from. + * @param {String} n Name of attribute to get. + * @param {String} dv Optional default value to return if the attribute didn't exist. + * @return {String} Attribute value string, default value or null if the attribute wasn't found. + */ + getAttrib : function(e, n, dv) { + var v, t = this, undef; + + e = t.get(e); + + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; + + if (!is(dv)) + dv = ''; + + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); + + if (v) + return v; + } + + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } + + if (!v) + v = e.getAttribute(n, 2); + + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; + + return v ? n : ''; + } + + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); + + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); + } + } + + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; + + break; + + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; + + break; + + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; + + break; + + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; + + break; + + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; + + break; + + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; + + return dv; + + case 'shape': + v = v.toLowerCase(); + break; + + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); + } + } + + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + /** + * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields. + * + * @method getPos + * @param {Element/String} n HTML element or element id to get x, y position from. + * @param {Element} ro Optional root element to stop calculations at. + * @return {object} Absolute position of the specified element object with x, y fields. + */ + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; + } + + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } + + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } + + return {x : x, y : y}; + }, + + /** + * Parses the specified style value into an object collection. This parser will also + * merge and remove any redundant items that browsers might have added. It will also convert non hex + * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. + * + * @method parseStyle + * @param {String} st Style value to parse for example: border:1px solid red;. + * @return {Object} Object representation of that style like {border : '1px solid red'} + */ + parseStyle : function(st) { + return this.styles.parse(st); + }, + + /** + * Serializes the specified style object into a string. + * + * @method serializeStyle + * @param {Object} o Object to serialize as string for example: {border : '1px solid red'} + * @param {String} name Optional element name. + * @return {String} String representation of the style object for example: border: 1px solid red. + */ + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, + + /** + * Imports/loads the specified CSS file into the document bound to the class. + * + * @method loadCSS + * @param {String} u URL to CSS file to load. + * @example + * // Loads a CSS file dynamically into the current document + * tinymce.DOM.loadCSS('somepath/some.css'); + * + * // Loads a CSS file into the currently active editor instance + * tinyMCE.activeEditor.dom.loadCSS('somepath/some.css'); + * + * // Loads a CSS file into an editor instance by id + * tinyMCE.get('someid').dom.loadCSS('somepath/some.css'); + * + * // Loads multiple CSS files into the current document + * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css'); + */ + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = t.select('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; + } + + head.appendChild(link); + }); + }, + + /** + * Adds a class to the specified element or elements. + * + * @method addClass + * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs. + * @param {String} c Class name to add to each element. + * @return {String/Array} String with new class value or array with new class values for all elements. + * @example + * // Adds a class to all paragraphs in the active editor + * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass'); + * + * // Adds a class to a specific element in the current page + * tinyMCE.DOM.addClass('mydiv', 'myclass'); + */ + addClass : function(e, c) { + return this.run(e, function(e) { + var o; + + if (!c) + return 0; + + if (this.hasClass(e, c)) + return e.className; + + o = this.removeClass(e, c); + + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, + + /** + * Removes a class from the specified element or elements. + * + * @method removeClass + * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs. + * @param {String} c Class name to remove to each element. + * @return {String/Array} String with new class value or array with new class values for all elements. + * @example + * // Removes a class from all paragraphs in the active editor + * tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass'); + * + * // Removes a class from a specific element in the current page + * tinyMCE.DOM.removeClass('mydiv', 'myclass'); + */ + removeClass : function(e, c) { + var t = this, re; + + return t.run(e, function(e) { + var v; + + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); + + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); + } + + return v; + } + + return e.className; + }); + }, + + /** + * Returns true if the specified element has the specified class. + * + * @method hasClass + * @param {String/Element} n HTML element or element id string to check CSS class on. + * @param {String} c CSS class to check for. + * @return {Boolean} true/false if the specified element has the specified class. + */ + hasClass : function(n, c) { + n = this.get(n); + + if (!n || !c) + return false; + + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, + + /** + * Shows the specified element(s) by ID by setting the "display" style. + * + * @method show + * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show. + */ + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, + + /** + * Hides the specified element(s) by ID by setting the "display" style. + * + * @method hide + * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide. + * @example + * // Hides a element by id in the document + * tinymce.DOM.hide('myid'); + */ + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, + + /** + * Returns true/false if the element is hidden or not by checking the "display" style. + * + * @method isHidden + * @param {String/Element} e Id or element to check display state on. + * @return {Boolean} true/false if the element is hidden or not. + */ + isHidden : function(e) { + e = this.get(e); + + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, + + /** + * Returns a unique id. This can be useful when generating elements on the fly. + * This method will not check if the element allready exists. + * + * @method uniqueId + * @param {String} p Optional prefix to add infront of all ids defaults to "mce_". + * @return {String} Unique id. + */ + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, + + /** + * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means + * URLs will get converted, hex color values fixed etc. Check processHTML for details. + * + * @method setHTML + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside. + * @param {String} h HTML content to set as inner HTML of the element. + * @example + * // Sets the inner HTML of all paragraphs in the active editor + * tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html'); + * + * // Sets the inner HTML of a element by id in the document + * tinyMCE.DOM.setHTML('mydiv', 'some inner html'); + */ + setHTML : function(element, html) { + var self = this; + + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + element = self.create('div'); + element.innerHTML = '
    ' + html; + + // Add all children from div to target + each (element.childNodes, function(node, i) { + // Skip br element + if (i) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; + + return html; + }); + }, + + /** + * Returns the outer HTML of an element. + * + * @method getOuterHTML + * @param {String/Element} elm Element ID or element object to get outer HTML from. + * @return {String} Outer HTML string. + * @example + * tinymce.DOM.getOuterHTML(editorElement); + * tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody()); + */ + getOuterHTML : function(elm) { + var doc, self = this; + + elm = self.get(elm); + + if (!elm) + return null; + + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; + + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); + + return doc.innerHTML; + }, + + /** + * Sets the specified outer HTML on a element or elements. + * + * @method setOuterHTML + * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on. + * @param {Object} h HTML code to set as outer value for the element. + * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class. + * @example + * // Sets the outer HTML of all paragraphs in the active editor + * tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '
    some html
    '); + * + * // Sets the outer HTML of a element by id in the document + * tinyMCE.DOM.setOuterHTML('mydiv', '
    some html
    '); + */ + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + }; + + return this.run(e, function(e) { + e = t.get(e); + + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; + + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); + } + }); + }, + + /** + * Entity decode a string, resolves any HTML entities like å. + * + * @method decode + * @param {String} s String to decode entities on. + * @return {String} Entity decoded string. + */ + decode : Entities.decode, + + /** + * Entity encodes a string, encodes the most common entities <>"& into entities. + * + * @method encode + * @param {String} text String to encode with entities. + * @return {String} Entity encoded string. + */ + encode : Entities.encodeAllRaw, + + /** + * Inserts a element after the reference element. + * + * @method insertAfter + * @param {Element} node Element to insert after the reference. + * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after. + * @return {Element/Array} Element that got added or an array with elements. + */ + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); + + return this.run(node, function(node) { + var parent, nextSibling; + + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); + + return node; + }); + }, + + /** + * Returns true/false if the specified element is a block element or not. + * + * @method isBlock + * @param {Node/String} node Element/Node to check. + * @return {Boolean} True/False state if the node is a block element or not. + */ + isBlock : function(node) { + var type = node.nodeType; + + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); + + return !!blockElementsMap[node]; + }, + + /** + * Replaces the specified element or elements with the specified element, the new element will + * be cloned if multiple inputs elements are passed. + * + * @method replace + * @param {Element} n New element to replace old ones with. + * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace. + * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones. + */ + replace : function(n, o, k) { + var t = this; + + if (is(o, 'array')) + n = n.cloneNode(true); + + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + /** + * Renames the specified element to a new name and keep it's attributes and children. + * + * @method rename + * @param {Element} elm Element to rename. + * @param {String} name Name of the new element. + * @return New element or the old element if it needed renaming. + */ + rename : function(elm, name) { + var t = this, newElm; + + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); + + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); + + // Replace block + t.replace(newElm, elm, 1); + } + + return newElm || elm; + }, + + /** + * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic. + * + * @method findCommonAncestor + * @param {Element} a Element to find common ancestor of. + * @param {Element} b Element to find common ancestor of. + * @return {Element} Common ancestor element of the two input elements. + */ + findCommonAncestor : function(a, b) { + var ps = a, pe; + + while (ps) { + pe = b; + + while (pe && ps != pe) + pe = pe.parentNode; + + if (ps == pe) + break; + + ps = ps.parentNode; + } + + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; + + return ps; + }, + + /** + * Parses the specified RGB color value and returns a hex version of that color. + * + * @method toHex + * @param {String} s RGB string value like rgb(1,2,3) + * @return {String} Hex version of that RGB value like #FF00FF. + */ + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + + function hex(s) { + s = parseInt(s).toString(16); + + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; + + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + + return s; + } + + return s; + }, + + /** + * Returns a array of all single CSS classes in the document. A single CSS class is a simple + * rule like ".class" complex ones like "div td.class" will not be added to output. + * + * @method getClasses + * @return {Array} Array with class objects each object has a class field might be other fields in the future. + */ + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + + if (t.classes) + return t.classes; + + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); + + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); + + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; + + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + + // Filter classes + if (f && !(v = f(v, ov))) + return; + + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; + + // Import + case 3: + addClasses(r.styleSheet); + break; + } + }); + }; + + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } + + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + /** + * Executes the specified function on the element by id or dom element node or array of elements/id. + * + * @method run + * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements. + * @param {function} f Function to execute for each item. + * @param {Object} s Optional scope to execute the function in. + * @return {Object/Array} Single object or array with objects depending on multiple input or not. + */ + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); + } + }); + + return o; + } + + return f.call(s, e); + }, + + /** + * Returns an NodeList with attributes for the element. + * + * @method getAttribs + * @param {HTMLElement/string} n Element node or string id to get attributes from. + * @return {NodeList} NodeList with attributes. + */ + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; + } + + return n.attributes; + }, + + /** + * Returns true/false if the specified node is to be considered empty or not. + * + * @example + * tinymce.DOM.isEmpty(node, {img : true}); + * @method isEmpty + * @param {Object} elements Optional name/value object with elements that are automatically treated as non empty elements. + * @return {Boolean} true/false if the node is empty or not. + */ + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, parentNode; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    + parentNode = node.parentNode; + if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) { + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } + } + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); + } + + return true; + }, + + /** + * Destroys all internal references to the DOM to solve IE leak issues. + * + * @method destroy + */ + destroy : function(s) { + var t = this; + + if (t.events) + t.events.destroy(); + + t.win = t.doc = t.root = t.events = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + /** + * Created a new DOM Range object. This will use the native DOM Range API if it's + * available if it's not it will fallback to the custom TinyMCE implementation. + * + * @method createRng + * @return {DOMRange} DOM Range object. + * @example + * var rng = tinymce.DOM.createRng(); + * alert(rng.startContainer + "," + rng.startOffset); + */ + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); + }, + + /** + * Returns the index of the specified node within it's parent. + * + * @param {Node} node Node to look for. + * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization. + * @return {Number} Index of the specified node. + */ + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; + + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + idx++; + lastNodeType = nodeType; + } + } + + return idx; + }, + + /** + * Splits an element into two new elements and places the specified split + * element or element between the new ones. For example splitting the paragraph at the bold element in + * this example

    abcabc123

    would produce

    abc

    abc

    123

    . + * + * @method split + * @param {Element} pe Parent element to split. + * @param {Element} e Element to split at. + * @param {Element} re Optional replacement element to replace the split element by. + * @return {Element} Returns the split element or the replacement element if that is specified. + */ + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; + } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; + } + }, + + /** + * Adds an event handler to the specified object. + * + * @method bind + * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents. + * @param {String} n Name of event handler to add for example: click. + * @param {function} f Function to execute when the event occurs. + * @param {Object} s Optional scope to execute the function in. + * @return {function} Function callback handler the same as the one passed in. + */ + bind : function(target, name, func, scope) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.add(target, name, func, scope || this); + }, + + /** + * Removes the specified event handler by name and function from a element or collection of elements. + * + * @method unbind + * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from. + * @param {String} n Event handler name like for example: "click" + * @param {function} f Function to remove. + * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. + */ + unbind : function(target, name, func) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.remove(target, name, func); + }, + + // #ifdef debug + + dumpRng : function(r) { + return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset; + }, + + // #endif + + _findSib : function(node, selector, name) { + var t = this, f = selector; + + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _isRes : function(c) { + // Is live resizble element + return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); + } + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); + } + */ + + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; + + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } + + return s; + } + */ + }); + + /** + * Instance of DOMUtils for the current document. + * + * @property DOM + * @member tinymce + * @type tinymce.dom.DOMUtils + * @example + * // Example of how to add a class to some element by id + * tinymce.DOM.addClass('someid', 'someclass'); + */ + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); diff --git a/js/tiny_mce/classes/dom/Element.js b/js/tiny_mce/classes/dom/Element.js index 1e6d6753..bf71a1dc 100644 --- a/js/tiny_mce/classes/dom/Element.js +++ b/js/tiny_mce/classes/dom/Element.js @@ -1,189 +1,195 @@ -/** - * Element.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - /** - * Element class, this enables element blocking in IE. Element blocking is a method to block out select blockes that - * gets visible though DIVs on IE 6 it uses a iframe for this blocking. This class also shortens the length of some DOM API calls - * since it's bound to an element. - * - * @class tinymce.dom.Element - */ - - /** - * Constructs a new Element instance. Consult the Wiki for more details on this class. - * - * @constructor - * @method Element - * @param {String} id Element ID to bind/execute methods on. - * @param {Object} settings Optional settings name/value collection. - */ - tinymce.dom.Element = function(id, settings) { - var t = this, dom, el; - - t.settings = settings = settings || {}; - t.id = id; - t.dom = dom = settings.dom || tinymce.DOM; - - // Only IE leaks DOM references, this is a lot faster - if (!tinymce.isIE) - el = dom.get(t.id); - - tinymce.each( - ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + - 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + - 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + - 'isHidden,setHTML,get').split(/,/) - , function(k) { - t[k] = function() { - var a = [id], i; - - for (i = 0; i < arguments.length; i++) - a.push(arguments[i]); - - a = dom[k].apply(dom, a); - t.update(k); - - return a; - }; - }); - - tinymce.extend(t, { - /** - * Adds a event handler to the element. - * - * @method on - * @param {String} n Event name like for example "click". - * @param {function} f Function to execute on the specified event. - * @param {Object} s Optional scope to execute function on. - * @return {function} Event handler function the same as the input function. - */ - on : function(n, f, s) { - return tinymce.dom.Event.add(t.id, n, f, s); - }, - - /** - * Returns the absolute X, Y cordinate of the element. - * - * @method getXY - * @return {Object} Objext with x, y cordinate fields. - */ - getXY : function() { - return { - x : parseInt(t.getStyle('left')), - y : parseInt(t.getStyle('top')) - }; - }, - - /** - * Returns the size of the element by a object with w and h fields. - * - * @method getSize - * @return {Object} Object with element size with a w and h field. - */ - getSize : function() { - var n = dom.get(t.id); - - return { - w : parseInt(t.getStyle('width') || n.clientWidth), - h : parseInt(t.getStyle('height') || n.clientHeight) - }; - }, - - /** - * Moves the element to a specific absolute position. - * - * @method moveTo - * @param {Number} x X cordinate of element position. - * @param {Number} y Y cordinate of element position. - */ - moveTo : function(x, y) { - t.setStyles({left : x, top : y}); - }, - - /** - * Moves the element relative to the current position. - * - * @method moveBy - * @param {Number} x Relative X cordinate of element position. - * @param {Number} y Relative Y cordinate of element position. - */ - moveBy : function(x, y) { - var p = t.getXY(); - - t.moveTo(p.x + x, p.y + y); - }, - - /** - * Resizes the element to a specific size. - * - * @method resizeTo - * @param {Number} w New width of element. - * @param {Numner} h New height of element. - */ - resizeTo : function(w, h) { - t.setStyles({width : w, height : h}); - }, - - /** - * Resizes the element relative to the current sizeto a specific size. - * - * @method resizeBy - * @param {Number} w Relative width of element. - * @param {Numner} h Relative height of element. - */ - resizeBy : function(w, h) { - var s = t.getSize(); - - t.resizeTo(s.w + w, s.h + h); - }, - - /** - * Updates the element blocker in IE6 based on the style information of the element. - * - * @method update - * @param {String} k Optional function key. Used internally. - */ - update : function(k) { - var b; - - if (tinymce.isIE6 && settings.blocker) { - k = k || ''; - - // Ignore getters - if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) - return; - - // Remove blocker on remove - if (k == 'remove') { - dom.remove(t.blocker); - return; - } - - if (!t.blocker) { - t.blocker = dom.uniqueId(); - b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); - dom.setStyle(b, 'opacity', 0); - } else - b = dom.get(t.blocker); - - dom.setStyles(b, { - left : t.getStyle('left', 1), - top : t.getStyle('top', 1), - width : t.getStyle('width', 1), - height : t.getStyle('height', 1), - display : t.getStyle('display', 1), - zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 - }); - } - } - }); - }; -})(tinymce); +/** + * Element.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * Element class, this enables element blocking in IE. Element blocking is a method to block out select blockes that + * gets visible though DIVs on IE 6 it uses a iframe for this blocking. This class also shortens the length of some DOM API calls + * since it's bound to an element. + * + * @class tinymce.dom.Element + * @example + * // Creates an basic element for an existing element + * var elm = new tinymce.dom.Element('someid'); + * + * elm.setStyle('background-color', 'red'); + * elm.moveTo(10, 10); + */ + + /** + * Constructs a new Element instance. Consult the Wiki for more details on this class. + * + * @constructor + * @method Element + * @param {String} id Element ID to bind/execute methods on. + * @param {Object} settings Optional settings name/value collection. + */ + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; + + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); + + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/) + , function(k) { + t[k] = function() { + var a = [id], i; + + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); + + a = dom[k].apply(dom, a); + t.update(k); + + return a; + }; + }); + + tinymce.extend(t, { + /** + * Adds a event handler to the element. + * + * @method on + * @param {String} n Event name like for example "click". + * @param {function} f Function to execute on the specified event. + * @param {Object} s Optional scope to execute function on. + * @return {function} Event handler function the same as the input function. + */ + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, + + /** + * Returns the absolute X, Y cordinate of the element. + * + * @method getXY + * @return {Object} Objext with x, y cordinate fields. + */ + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, + + /** + * Returns the size of the element by a object with w and h fields. + * + * @method getSize + * @return {Object} Object with element size with a w and h field. + */ + getSize : function() { + var n = dom.get(t.id); + + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + /** + * Moves the element to a specific absolute position. + * + * @method moveTo + * @param {Number} x X cordinate of element position. + * @param {Number} y Y cordinate of element position. + */ + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, + + /** + * Moves the element relative to the current position. + * + * @method moveBy + * @param {Number} x Relative X cordinate of element position. + * @param {Number} y Relative Y cordinate of element position. + */ + moveBy : function(x, y) { + var p = t.getXY(); + + t.moveTo(p.x + x, p.y + y); + }, + + /** + * Resizes the element to a specific size. + * + * @method resizeTo + * @param {Number} w New width of element. + * @param {Numner} h New height of element. + */ + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, + + /** + * Resizes the element relative to the current sizeto a specific size. + * + * @method resizeBy + * @param {Number} w Relative width of element. + * @param {Numner} h Relative height of element. + */ + resizeBy : function(w, h) { + var s = t.getSize(); + + t.resizeTo(s.w + w, s.h + h); + }, + + /** + * Updates the element blocker in IE6 based on the style information of the element. + * + * @method update + * @param {String} k Optional function key. Used internally. + */ + update : function(k) { + var b; + + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; + + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; + + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } + + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); + + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); + } + } + }); + }; +})(tinymce); diff --git a/js/tiny_mce/classes/dom/EventUtils.js b/js/tiny_mce/classes/dom/EventUtils.js index 06746e7c..f1a183c9 100644 --- a/js/tiny_mce/classes/dom/EventUtils.js +++ b/js/tiny_mce/classes/dom/EventUtils.js @@ -1,363 +1,381 @@ -/** - * EventUtils.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Shorten names - var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; - - /** - * This class handles DOM events in a cross platform fasion it also keeps track of element - * and handler references to be able to clean elements to reduce IE memory leaks. - * - * @class tinymce.dom.EventUtils - */ - tinymce.create('tinymce.dom.EventUtils', { - /** - * Constructs a new EventUtils instance. - * - * @constructor - * @method EventUtils - */ - EventUtils : function() { - this.inits = []; - this.events = []; - }, - - /** - * Adds an event handler to the specified object. - * - * @method add - * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents. - * @param {String/Array} n Name of event handler to add for example: click. - * @param {function} f Function to execute when the event occurs. - * @param {Object} s Optional scope to execute the function in. - * @return {function} Function callback handler the same as the one passed in. - */ - add : function(o, n, f, s) { - var cb, t = this, el = t.events, r; - - if (n instanceof Array) { - r = []; - - each(n, function(n) { - r.push(t.add(o, n, f, s)); - }); - - return r; - } - - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; - - each(o, function(o) { - o = DOM.get(o); - r.push(t.add(o, n, f, s)); - }); - - return r; - } - - o = DOM.get(o); - - if (!o) - return; - - // Setup event callback - cb = function(e) { - // Is all events disabled - if (t.disabled) - return; - - e = e || window.event; - - // Patch in target, preventDefault and stopPropagation in IE it's W3C valid - if (e && isIE) { - if (!e.target) - e.target = e.srcElement; - - // Patch in preventDefault, stopPropagation methods for W3C compatibility - tinymce.extend(e, t._stoppers); - } - - if (!s) - return f(e); - - return f.call(s, e); - }; - - if (n == 'unload') { - tinymce.unloads.unshift({func : cb}); - return cb; - } - - if (n == 'init') { - if (t.domLoaded) - cb(); - else - t.inits.push(cb); - - return cb; - } - - // Store away listener reference - el.push({ - obj : o, - name : n, - func : f, - cfunc : cb, - scope : s - }); - - t._add(o, n, cb); - - return f; - }, - - /** - * Removes the specified event handler by name and function from a element or collection of elements. - * - * @method remove - * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from. - * @param {String} n Event handler name like for example: "click" - * @param {function} f Function to remove. - * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. - */ - remove : function(o, n, f) { - var t = this, a = t.events, s = false, r; - - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; - - each(o, function(o) { - o = DOM.get(o); - r.push(t.remove(o, n, f)); - }); - - return r; - } - - o = DOM.get(o); - - each(a, function(e, i) { - if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { - a.splice(i, 1); - t._remove(o, n, e.cfunc); - s = true; - return false; - } - }); - - return s; - }, - - /** - * Clears all events of a specific object. - * - * @method clear - * @param {Object} o DOM element or object to remove all events from. - */ - clear : function(o) { - var t = this, a = t.events, i, e; - - if (o) { - o = DOM.get(o); - - for (i = a.length - 1; i >= 0; i--) { - e = a[i]; - - if (e.obj === o) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - a.splice(i, 1); - } - } - } - }, - - /** - * Cancels an event for both bubbeling and the default browser behavior. - * - * @method cancel - * @param {Event} e Event object to cancel. - * @return {Boolean} Always false. - */ - cancel : function(e) { - if (!e) - return false; - - this.stop(e); - - return this.prevent(e); - }, - - /** - * Stops propogation/bubbeling of an event. - * - * @method stop - * @param {Event} e Event to cancel bubbeling on. - * @return {Boolean} Always false. - */ - stop : function(e) { - if (e.stopPropagation) - e.stopPropagation(); - else - e.cancelBubble = true; - - return false; - }, - - /** - * Prevent default browser behvaior of an event. - * - * @method prevent - * @param {Event} e Event to prevent default browser behvaior of an event. - * @return {Boolean} Always false. - */ - prevent : function(e) { - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - return false; - }, - - /** - * Destroys the instance. - * - * @method destroy - */ - destroy : function() { - var t = this; - - each(t.events, function(e, i) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - }); - - t.events = []; - t = null; - }, - - _add : function(o, n, f) { - if (o.attachEvent) - o.attachEvent('on' + n, f); - else if (o.addEventListener) - o.addEventListener(n, f, false); - else - o['on' + n] = f; - }, - - _remove : function(o, n, f) { - if (o) { - try { - if (o.detachEvent) - o.detachEvent('on' + n, f); - else if (o.removeEventListener) - o.removeEventListener(n, f, false); - else - o['on' + n] = null; - } catch (ex) { - // Might fail with permission denined on IE so we just ignore that - } - } - }, - - _pageInit : function(win) { - var t = this; - - // Keep it from running more than once - if (t.domLoaded) - return; - - t.domLoaded = true; - - each(t.inits, function(c) { - c(); - }); - - t.inits = []; - }, - - _wait : function(win) { - var t = this, doc = win.document; - - // No need since the document is already loaded - if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { - t.domLoaded = 1; - return; - } - - // Use IE method - if (doc.attachEvent) { - doc.attachEvent("onreadystatechange", function() { - if (doc.readyState === "complete") { - doc.detachEvent("onreadystatechange", arguments.callee); - t._pageInit(win); - } - }); - - if (doc.documentElement.doScroll && win == win.top) { - (function() { - if (t.domLoaded) - return; - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - doc.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(arguments.callee, 0); - return; - } - - t._pageInit(win); - })(); - } - } else if (doc.addEventListener) { - t._add(win, 'DOMContentLoaded', function() { - t._pageInit(win); - }); - } - - t._add(win, 'load', function() { - t._pageInit(win); - }); - }, - - _stoppers : { - preventDefault : function() { - this.returnValue = false; - }, - - stopPropagation : function() { - this.cancelBubble = true; - } - } - }); - - /** - * Instance of EventUtils for the current document. - * - * @property Event - * @member tinymce.dom - * @type tinymce.dom.EventUtils - */ - Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); - - // Dispatch DOM content loaded event for IE and Safari - Event._wait(window); - - tinymce.addUnload(function() { - Event.destroy(); - }); -})(tinymce); +/** + * EventUtils.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + + /** + * This class handles DOM events in a cross platform fasion it also keeps track of element + * and handler references to be able to clean elements to reduce IE memory leaks. + * + * @class tinymce.dom.EventUtils + */ + tinymce.create('tinymce.dom.EventUtils', { + /** + * Constructs a new EventUtils instance. + * + * @constructor + * @method EventUtils + */ + EventUtils : function() { + this.inits = []; + this.events = []; + }, + + /** + * Adds an event handler to the specified object. + * + * @method add + * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents. + * @param {String/Array} n Name of event handler to add for example: click. + * @param {function} f Function to execute when the event occurs. + * @param {Object} s Optional scope to execute the function in. + * @return {function} Function callback handler the same as the one passed in. + * @example + * // Adds a click handler to the current document + * tinymce.dom.Event.add(document, 'click', function(e) { + * console.debug(e.target); + * }); + */ + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; + + if (n instanceof Array) { + r = []; + + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + o = DOM.get(o); + + if (!o) + return; + + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; + + e = e || window.event; + + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; + + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } + + if (!s) + return f(e); + + return f.call(s, e); + }; + + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; + } + + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); + + return cb; + } + + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); + + t._add(o, n, cb); + + return f; + }, + + /** + * Removes the specified event handler by name and function from a element or collection of elements. + * + * @method remove + * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from. + * @param {String} n Event handler name like for example: "click" + * @param {function} f Function to remove. + * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in. + * @example + * // Adds a click handler to the current document + * var func = tinymce.dom.Event.add(document, 'click', function(e) { + * console.debug(e.target); + * }); + * + * // Removes the click handler from the document + * tinymce.dom.Event.remove(document, 'click', func); + */ + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); + + return r; + } + + o = DOM.get(o); + + each(a, function(e, i) { + if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { + a.splice(i, 1); + t._remove(o, n, e.cfunc); + s = true; + return false; + } + }); + + return s; + }, + + /** + * Clears all events of a specific object. + * + * @method clear + * @param {Object} o DOM element or object to remove all events from. + * @example + * // Cancels all mousedown events in the active editor + * tinyMCE.activeEditor.onMouseDown.add(function(ed, e) { + * return tinymce.dom.Event.cancel(e); + * }); + */ + clear : function(o) { + var t = this, a = t.events, i, e; + + if (o) { + o = DOM.get(o); + + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; + + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } + }, + + /** + * Cancels an event for both bubbeling and the default browser behavior. + * + * @method cancel + * @param {Event} e Event object to cancel. + * @return {Boolean} Always false. + */ + cancel : function(e) { + if (!e) + return false; + + this.stop(e); + + return this.prevent(e); + }, + + /** + * Stops propogation/bubbeling of an event. + * + * @method stop + * @param {Event} e Event to cancel bubbeling on. + * @return {Boolean} Always false. + */ + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; + + return false; + }, + + /** + * Prevent default browser behvaior of an event. + * + * @method prevent + * @param {Event} e Event to prevent default browser behvaior of an event. + * @return {Boolean} Always false. + */ + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + + return false; + }, + + /** + * Destroys the instance. + * + * @method destroy + */ + destroy : function() { + var t = this; + + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); + + t.events = []; + t = null; + }, + + _add : function(o, n, f) { + if (o.attachEvent) + o.attachEvent('on' + n, f); + else if (o.addEventListener) + o.addEventListener(n, f, false); + else + o['on' + n] = f; + }, + + _remove : function(o, n, f) { + if (o) { + try { + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; + } catch (ex) { + // Might fail with permission denined on IE so we just ignore that + } + } + }, + + _pageInit : function(win) { + var t = this; + + // Keep it from running more than once + if (t.domLoaded) + return; + + t.domLoaded = true; + + each(t.inits, function(c) { + c(); + }); + + t.inits = []; + }, + + _wait : function(win) { + var t = this, doc = win.document; + + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } + + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); + + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; + + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + t._pageInit(win); + })(); + } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } + + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, + + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, + + stopPropagation : function() { + this.cancelBubble = true; + } + } + }); + + /** + * Instance of EventUtils for the current document. + * + * @property Event + * @member tinymce.dom + * @type tinymce.dom.EventUtils + */ + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); + + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); diff --git a/js/tiny_mce/classes/dom/Range.js b/js/tiny_mce/classes/dom/Range.js index 634459d5..baf43bba 100644 --- a/js/tiny_mce/classes/dom/Range.js +++ b/js/tiny_mce/classes/dom/Range.js @@ -1,686 +1,687 @@ -/** - * Range.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(ns) { - // Range constructor - function Range(dom) { - var t = this, - doc = dom.doc, - EXTRACT = 0, - CLONE = 1, - DELETE = 2, - TRUE = true, - FALSE = false, - START_OFFSET = 'startOffset', - START_CONTAINER = 'startContainer', - END_CONTAINER = 'endContainer', - END_OFFSET = 'endOffset', - extend = tinymce.extend, - nodeIndex = dom.nodeIndex; - - extend(t, { - // Inital states - startContainer : doc, - startOffset : 0, - endContainer : doc, - endOffset : 0, - collapsed : TRUE, - commonAncestorContainer : doc, - - // Range constants - START_TO_START : 0, - START_TO_END : 1, - END_TO_END : 2, - END_TO_START : 3, - - // Public methods - setStart : setStart, - setEnd : setEnd, - setStartBefore : setStartBefore, - setStartAfter : setStartAfter, - setEndBefore : setEndBefore, - setEndAfter : setEndAfter, - collapse : collapse, - selectNode : selectNode, - selectNodeContents : selectNodeContents, - compareBoundaryPoints : compareBoundaryPoints, - deleteContents : deleteContents, - extractContents : extractContents, - cloneContents : cloneContents, - insertNode : insertNode, - surroundContents : surroundContents, - cloneRange : cloneRange - }); - - function setStart(n, o) { - _setEndPoint(TRUE, n, o); - }; - - function setEnd(n, o) { - _setEndPoint(FALSE, n, o); - }; - - function setStartBefore(n) { - setStart(n.parentNode, nodeIndex(n)); - }; - - function setStartAfter(n) { - setStart(n.parentNode, nodeIndex(n) + 1); - }; - - function setEndBefore(n) { - setEnd(n.parentNode, nodeIndex(n)); - }; - - function setEndAfter(n) { - setEnd(n.parentNode, nodeIndex(n) + 1); - }; - - function collapse(ts) { - if (ts) { - t[END_CONTAINER] = t[START_CONTAINER]; - t[END_OFFSET] = t[START_OFFSET]; - } else { - t[START_CONTAINER] = t[END_CONTAINER]; - t[START_OFFSET] = t[END_OFFSET]; - } - - t.collapsed = TRUE; - }; - - function selectNode(n) { - setStartBefore(n); - setEndAfter(n); - }; - - function selectNodeContents(n) { - setStart(n, 0); - setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); - }; - - function compareBoundaryPoints(h, r) { - var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET]; - - // Check START_TO_START - if (h === 0) - return _compareBoundaryPoints(sc, so, sc, so); - - // Check START_TO_END - if (h === 1) - return _compareBoundaryPoints(sc, so, ec, eo); - - // Check END_TO_END - if (h === 2) - return _compareBoundaryPoints(ec, eo, ec, eo); - - // Check END_TO_START - if (h === 3) - return _compareBoundaryPoints(ec, eo, sc, so); - }; - - function deleteContents() { - _traverse(DELETE); - }; - - function extractContents() { - return _traverse(EXTRACT); - }; - - function cloneContents() { - return _traverse(CLONE); - }; - - function insertNode(n) { - var startContainer = this[START_CONTAINER], - startOffset = this[START_OFFSET], nn, o; - - // Node is TEXT_NODE or CDATA - if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { - if (!startOffset) { - // At the start of text - startContainer.parentNode.insertBefore(n, startContainer); - } else if (startOffset >= startContainer.nodeValue.length) { - // At the end of text - dom.insertAfter(n, startContainer); - } else { - // Middle, need to split - nn = startContainer.splitText(startOffset); - startContainer.parentNode.insertBefore(n, nn); - } - } else { - // Insert element node - if (startContainer.childNodes.length > 0) - o = startContainer.childNodes[startOffset]; - - if (o) - startContainer.insertBefore(n, o); - else - startContainer.appendChild(n); - } - }; - - function surroundContents(n) { - var f = t.extractContents(); - - t.insertNode(n); - n.appendChild(f); - t.selectNode(n); - }; - - function cloneRange() { - return extend(new Range(dom), { - startContainer : t[START_CONTAINER], - startOffset : t[START_OFFSET], - endContainer : t[END_CONTAINER], - endOffset : t[END_OFFSET], - collapsed : t.collapsed, - commonAncestorContainer : t.commonAncestorContainer - }); - }; - - // Private methods - - function _getSelectedNode(container, offset) { - var child; - - if (container.nodeType == 3 /* TEXT_NODE */) - return container; - - if (offset < 0) - return container; - - child = container.firstChild; - while (child && offset > 0) { - --offset; - child = child.nextSibling; - } - - if (child) - return child; - - return container; - }; - - function _isCollapsed() { - return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); - }; - - function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { - var c, offsetC, n, cmnRoot, childA, childB; - - // In the first case the boundary-points have the same container. A is before B - // if its offset is less than the offset of B, A is equal to B if its offset is - // equal to the offset of B, and A is after B if its offset is greater than the - // offset of B. - if (containerA == containerB) { - if (offsetA == offsetB) - return 0; // equal - - if (offsetA < offsetB) - return -1; // before - - return 1; // after - } - - // In the second case a child node C of the container of A is an ancestor - // container of B. In this case, A is before B if the offset of A is less than or - // equal to the index of the child node C and A is after B otherwise. - c = containerB; - while (c && c.parentNode != containerA) - c = c.parentNode; - - if (c) { - offsetC = 0; - n = containerA.firstChild; - - while (n != c && offsetC < offsetA) { - offsetC++; - n = n.nextSibling; - } - - if (offsetA <= offsetC) - return -1; // before - - return 1; // after - } - - // In the third case a child node C of the container of B is an ancestor container - // of A. In this case, A is before B if the index of the child node C is less than - // the offset of B and A is after B otherwise. - c = containerA; - while (c && c.parentNode != containerB) { - c = c.parentNode; - } - - if (c) { - offsetC = 0; - n = containerB.firstChild; - - while (n != c && offsetC < offsetB) { - offsetC++; - n = n.nextSibling; - } - - if (offsetC < offsetB) - return -1; // before - - return 1; // after - } - - // In the fourth case, none of three other cases hold: the containers of A and B - // are siblings or descendants of sibling nodes. In this case, A is before B if - // the container of A is before the container of B in a pre-order traversal of the - // Ranges' context tree and A is after B otherwise. - cmnRoot = dom.findCommonAncestor(containerA, containerB); - childA = containerA; - - while (childA && childA.parentNode != cmnRoot) - childA = childA.parentNode; - - if (!childA) - childA = cmnRoot; - - childB = containerB; - while (childB && childB.parentNode != cmnRoot) - childB = childB.parentNode; - - if (!childB) - childB = cmnRoot; - - if (childA == childB) - return 0; // equal - - n = cmnRoot.firstChild; - while (n) { - if (n == childA) - return -1; // before - - if (n == childB) - return 1; // after - - n = n.nextSibling; - } - }; - - function _setEndPoint(st, n, o) { - var ec, sc; - - if (st) { - t[START_CONTAINER] = n; - t[START_OFFSET] = o; - } else { - t[END_CONTAINER] = n; - t[END_OFFSET] = o; - } - - // If one boundary-point of a Range is set to have a root container - // other than the current one for the Range, the Range is collapsed to - // the new position. This enforces the restriction that both boundary- - // points of a Range must have the same root container. - ec = t[END_CONTAINER]; - while (ec.parentNode) - ec = ec.parentNode; - - sc = t[START_CONTAINER]; - while (sc.parentNode) - sc = sc.parentNode; - - if (sc == ec) { - // The start position of a Range is guaranteed to never be after the - // end position. To enforce this restriction, if the start is set to - // be at a position after the end, the Range is collapsed to that - // position. - if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) - t.collapse(st); - } else - t.collapse(st); - - t.collapsed = _isCollapsed(); - t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); - }; - - function _traverse(how) { - var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; - - if (t[START_CONTAINER] == t[END_CONTAINER]) - return _traverseSameContainer(how); - - for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[START_CONTAINER]) - return _traverseCommonStartContainer(c, how); - - ++endContainerDepth; - } - - for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[END_CONTAINER]) - return _traverseCommonEndContainer(c, how); - - ++startContainerDepth; - } - - depthDiff = startContainerDepth - endContainerDepth; - - startNode = t[START_CONTAINER]; - while (depthDiff > 0) { - startNode = startNode.parentNode; - depthDiff--; - } - - endNode = t[END_CONTAINER]; - while (depthDiff < 0) { - endNode = endNode.parentNode; - depthDiff++; - } - - // ascend the ancestor hierarchy until we have a common parent. - for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { - startNode = sp; - endNode = ep; - } - - return _traverseCommonAncestors(startNode, endNode, how); - }; - - function _traverseSameContainer(how) { - var frag, s, sub, n, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - // If selection is empty, just return the fragment - if (t[START_OFFSET] == t[END_OFFSET]) - return frag; - - // Text node needs special case handling - if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { - // get the substring - s = t[START_CONTAINER].nodeValue; - sub = s.substring(t[START_OFFSET], t[END_OFFSET]); - - // set the original text node to its new value - if (how != CLONE) { - t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); - - // Nothing is partially selected, so collapse to start point - t.collapse(TRUE); - } - - if (how == DELETE) - return; - - frag.appendChild(doc.createTextNode(sub)); - return frag; - } - - // Copy nodes between the start/end offsets. - n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); - cnt = t[END_OFFSET] - t[START_OFFSET]; - - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.appendChild( xferNode ); - - --cnt; - n = sibling; - } - - // Nothing is partially selected, so collapse to start point - if (how != CLONE) - t.collapse(TRUE); - - return frag; - }; - - function _traverseCommonStartContainer(endAncestor, how) { - var frag, n, endIdx, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseRightBoundary(endAncestor, how); - - if (frag) - frag.appendChild(n); - - endIdx = nodeIndex(endAncestor); - cnt = endIdx - t[START_OFFSET]; - - if (cnt <= 0) { - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); - } - - return frag; - } - - n = endAncestor.previousSibling; - while (cnt > 0) { - sibling = n.previousSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.insertBefore(xferNode, frag.firstChild); - - --cnt; - n = sibling; - } - - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); - } - - return frag; - }; - - function _traverseCommonEndContainer(startAncestor, how) { - var frag, startIdx, n, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); - - startIdx = nodeIndex(startAncestor); - ++startIdx; // Because we already traversed it.... - - cnt = t[END_OFFSET] - startIdx; - n = startAncestor.nextSibling; - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.appendChild(xferNode); - - --cnt; - n = sibling; - } - - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); - } - - return frag; - }; - - function _traverseCommonAncestors(startAncestor, endAncestor, how) { - var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); - - commonParent = startAncestor.parentNode; - startOffset = nodeIndex(startAncestor); - endOffset = nodeIndex(endAncestor); - ++startOffset; - - cnt = endOffset - startOffset; - sibling = startAncestor.nextSibling; - - while (cnt > 0) { - nextSibling = sibling.nextSibling; - n = _traverseFullySelected(sibling, how); - - if (frag) - frag.appendChild(n); - - sibling = nextSibling; - --cnt; - } - - n = _traverseRightBoundary(endAncestor, how); - - if (frag) - frag.appendChild(n); - - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); - } - - return frag; - }; - - function _traverseRightBoundary(root, how) { - var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; - - if (next == root) - return _traverseNode(next, isFullySelected, FALSE, how); - - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, FALSE, how); - - while (parent) { - while (next) { - prevSibling = next.previousSibling; - clonedChild = _traverseNode(next, isFullySelected, FALSE, how); - - if (how != DELETE) - clonedParent.insertBefore(clonedChild, clonedParent.firstChild); - - isFullySelected = TRUE; - next = prevSibling; - } - - if (parent == root) - return clonedParent; - - next = parent.previousSibling; - parent = parent.parentNode; - - clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); - - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); - - clonedParent = clonedGrandParent; - } - }; - - function _traverseLeftBoundary(root, how) { - var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; - - if (next == root) - return _traverseNode(next, isFullySelected, TRUE, how); - - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, TRUE, how); - - while (parent) { - while (next) { - nextSibling = next.nextSibling; - clonedChild = _traverseNode(next, isFullySelected, TRUE, how); - - if (how != DELETE) - clonedParent.appendChild(clonedChild); - - isFullySelected = TRUE; - next = nextSibling; - } - - if (parent == root) - return clonedParent; - - next = parent.nextSibling; - parent = parent.parentNode; - - clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); - - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); - - clonedParent = clonedGrandParent; - } - }; - - function _traverseNode(n, isFullySelected, isLeft, how) { - var txtValue, newNodeValue, oldNodeValue, offset, newNode; - - if (isFullySelected) - return _traverseFullySelected(n, how); - - if (n.nodeType == 3 /* TEXT_NODE */) { - txtValue = n.nodeValue; - - if (isLeft) { - offset = t[START_OFFSET]; - newNodeValue = txtValue.substring(offset); - oldNodeValue = txtValue.substring(0, offset); - } else { - offset = t[END_OFFSET]; - newNodeValue = txtValue.substring(0, offset); - oldNodeValue = txtValue.substring(offset); - } - - if (how != CLONE) - n.nodeValue = oldNodeValue; - - if (how == DELETE) - return; - - newNode = n.cloneNode(FALSE); - newNode.nodeValue = newNodeValue; - - return newNode; - } - - if (how == DELETE) - return; - - return n.cloneNode(FALSE); - }; - - function _traverseFullySelected(n, how) { - if (how != DELETE) - return how == CLONE ? n.cloneNode(TRUE) : n; - - n.parentNode.removeChild(n); - }; - }; - - ns.Range = Range; -})(tinymce.dom); +/** + * Range.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; + + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, + + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange + }); + + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; + + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; + + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; + + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; + + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; + + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; + } + + t.collapsed = TRUE; + }; + + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; + + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; + + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; + + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; + + function deleteContents() { + _traverse(DELETE); + }; + + function extractContents() { + return _traverse(EXTRACT); + }; + + function cloneContents() { + return _traverse(CLONE); + }; + + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; + + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; + + function surroundContents(n) { + var f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; + + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; + + // Private methods + + function _getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child) + return child; + + return container; + }; + + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; + + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal + + if (offsetA < offsetB) + return -1; // before + + return 1; // after + } + + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; + + if (c) { + offsetC = 0; + n = containerA.firstChild; + + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + + if (offsetA <= offsetC) + return -1; // before + + return 1; // after + } + + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } + + if (c) { + offsetC = 0; + n = containerB.firstChild; + + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } + + if (offsetC < offsetB) + return -1; // before + + return 1; // after + } + + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; + + if (!childA) + childA = cmnRoot; + + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; + + if (!childB) + childB = cmnRoot; + + if (childA == childB) + return 0; // equal + + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before + + if (n == childB) + return 1; // after + + n = n.nextSibling; + } + }; + + function _setEndPoint(st, n, o) { + var ec, sc; + + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; + } + + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); + + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; + + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); + + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } + + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } + + return _traverseCommonAncestors(startNode, endNode, how); + }; + + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; + + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } + + if (how == DELETE) + return; + + frag.appendChild(doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + } + + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; + } + + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + }; + + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = n.cloneNode(FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return n.cloneNode(FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? n.cloneNode(TRUE) : n; + + n.parentNode.removeChild(n); + }; + }; + + ns.Range = Range; +})(tinymce.dom); diff --git a/js/tiny_mce/classes/dom/RangeUtils.js b/js/tiny_mce/classes/dom/RangeUtils.js index ea6623df..6d48b8cd 100644 --- a/js/tiny_mce/classes/dom/RangeUtils.js +++ b/js/tiny_mce/classes/dom/RangeUtils.js @@ -1,200 +1,251 @@ -/** - * Range.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - tinymce.dom.RangeUtils = function(dom) { - var INVISIBLE_CHAR = '\uFEFF'; - - /** - * Walks the specified range like object and executes the callback for each sibling collection it finds. - * - * @param {Object} rng Range like object. - * @param {function} callback Callback function to execute for each sibling collection. - */ - this.walk = function(rng, callback) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, - ancestor, startPoint, - endPoint, node, parent, siblings, nodes; - - // Handle table cell selection the table plugin enables - // you to fake select table cells and perform formatting actions on them - nodes = dom.select('td.mceSelected,th.mceSelected'); - if (nodes.length > 0) { - tinymce.each(nodes, function(node) { - callback([node]); - }); - - return; - } - - /** - * Collects siblings - * - * @private - * @param {Node} node Node to collect siblings from. - * @param {String} name Name of the sibling to check for. - * @return {Array} Array of collected siblings. - */ - function collectSiblings(node, name, end_node) { - var siblings = []; - - for (; node && node != end_node; node = node[name]) - siblings.push(node); - - return siblings; - }; - - /** - * Find an end point this is the node just before the common ancestor root. - * - * @private - * @param {Node} node Node to start at. - * @param {Node} root Root/ancestor element to stop just before. - * @return {Node} Node just before the root element. - */ - function findEndPoint(node, root) { - do { - if (node.parentNode == root) - return node; - - node = node.parentNode; - } while(node); - }; - - function walkBoundary(start_node, end_node, next) { - var siblingName = next ? 'nextSibling' : 'previousSibling'; - - for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { - parent = node.parentNode; - siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); - - if (siblings.length) { - if (!next) - siblings.reverse(); - - callback(siblings); - } - } - }; - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) - startContainer = startContainer.childNodes[startOffset]; - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) - endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)]; - - // Find common ancestor and end points - ancestor = dom.findCommonAncestor(startContainer, endContainer); - - // Same container - if (startContainer == endContainer) - return callback([startContainer]); - - // Process left side - for (node = startContainer; node; node = node.parentNode) { - if (node == endContainer) - return walkBoundary(startContainer, ancestor, true); - - if (node == ancestor) - break; - } - - // Process right side - for (node = endContainer; node; node = node.parentNode) { - if (node == startContainer) - return walkBoundary(endContainer, ancestor); - - if (node == ancestor) - break; - } - - // Find start/end point - startPoint = findEndPoint(startContainer, ancestor) || startContainer; - endPoint = findEndPoint(endContainer, ancestor) || endContainer; - - // Walk left leaf - walkBoundary(startContainer, startPoint, true); - - // Walk the middle from start to end point - siblings = collectSiblings( - startPoint == startContainer ? startPoint : startPoint.nextSibling, - 'nextSibling', - endPoint == endContainer ? endPoint.nextSibling : endPoint - ); - - if (siblings.length) - callback(siblings); - - // Walk right leaf - walkBoundary(endContainer, endPoint); - }; - - /** - * Splits the specified range at it's start/end points. - * - * @param {Range/RangeObject} rng Range to split. - * @return {Object} Range position object. - */ -/* this.split = function(rng) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset; - - function splitText(node, offset) { - if (offset == node.nodeValue.length) - node.appendData(INVISIBLE_CHAR); - - node = node.splitText(offset); - - if (node.nodeValue === INVISIBLE_CHAR) - node.nodeValue = ''; - - return node; - }; - - // Handle single text node - if (startContainer == endContainer) { - if (startContainer.nodeType == 3) { - if (startOffset != 0) - startContainer = endContainer = splitText(startContainer, startOffset); - - if (endOffset - startOffset != startContainer.nodeValue.length) - splitText(startContainer, endOffset - startOffset); - } - } else { - // Split startContainer text node if needed - if (startContainer.nodeType == 3 && startOffset != 0) { - startContainer = splitText(startContainer, startOffset); - startOffset = 0; - } - - // Split endContainer text node if needed - if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) { - endContainer = splitText(endContainer, endOffset).previousSibling; - endOffset = endContainer.nodeValue.length; - } - } - - return { - startContainer : startContainer, - startOffset : startOffset, - endContainer : endContainer, - endOffset : endOffset - }; - }; -*/ - }; -})(tinymce); +/** + * Range.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + tinymce.dom.RangeUtils = function(dom) { + var INVISIBLE_CHAR = '\uFEFF'; + + /** + * Walks the specified range like object and executes the callback for each sibling collection it finds. + * + * @param {Object} rng Range like object. + * @param {function} callback Callback function to execute for each sibling collection. + */ + this.walk = function(rng, callback) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, + ancestor, startPoint, + endPoint, node, parent, siblings, nodes; + + // Handle table cell selection the table plugin enables + // you to fake select table cells and perform formatting actions on them + nodes = dom.select('td.mceSelected,th.mceSelected'); + if (nodes.length > 0) { + tinymce.each(nodes, function(node) { + callback([node]); + }); + + return; + } + + /** + * Excludes start/end text node if they are out side the range + * + * @private + * @param {Array} nodes Nodes to exclude items from. + * @return {Array} Array with nodes excluding the start/end container if needed. + */ + function exclude(nodes) { + var node; + + // First node is excluded + node = nodes[0]; + if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { + nodes.splice(0, 1); + } + + // Last node is excluded + node = nodes[nodes.length - 1]; + if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { + nodes.splice(nodes.length - 1, 1); + } + + return nodes; + }; + + /** + * Collects siblings + * + * @private + * @param {Node} node Node to collect siblings from. + * @param {String} name Name of the sibling to check for. + * @return {Array} Array of collected siblings. + */ + function collectSiblings(node, name, end_node) { + var siblings = []; + + for (; node && node != end_node; node = node[name]) + siblings.push(node); + + return siblings; + }; + + /** + * Find an end point this is the node just before the common ancestor root. + * + * @private + * @param {Node} node Node to start at. + * @param {Node} root Root/ancestor element to stop just before. + * @return {Node} Node just before the root element. + */ + function findEndPoint(node, root) { + do { + if (node.parentNode == root) + return node; + + node = node.parentNode; + } while(node); + }; + + function walkBoundary(start_node, end_node, next) { + var siblingName = next ? 'nextSibling' : 'previousSibling'; + + for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { + parent = node.parentNode; + siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); + + if (siblings.length) { + if (!next) + siblings.reverse(); + + callback(exclude(siblings)); + } + } + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) + startContainer = startContainer.childNodes[startOffset]; + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; + + // Same container + if (startContainer == endContainer) + return callback(exclude([startContainer])); + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(startContainer, endContainer); + + // Process left side + for (node = startContainer; node; node = node.parentNode) { + if (node === endContainer) + return walkBoundary(startContainer, ancestor, true); + + if (node === ancestor) + break; + } + + // Process right side + for (node = endContainer; node; node = node.parentNode) { + if (node === startContainer) + return walkBoundary(endContainer, ancestor); + + if (node === ancestor) + break; + } + + // Find start/end point + startPoint = findEndPoint(startContainer, ancestor) || startContainer; + endPoint = findEndPoint(endContainer, ancestor) || endContainer; + + // Walk left leaf + walkBoundary(startContainer, startPoint, true); + + // Walk the middle from start to end point + siblings = collectSiblings( + startPoint == startContainer ? startPoint : startPoint.nextSibling, + 'nextSibling', + endPoint == endContainer ? endPoint.nextSibling : endPoint + ); + + if (siblings.length) + callback(exclude(siblings)); + + // Walk right leaf + walkBoundary(endContainer, endPoint); + }; + + /** + * Splits the specified range at it's start/end points. + * + * @param {Range/RangeObject} rng Range to split. + * @return {Object} Range position object. + */ + this.split = function(rng) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + function splitText(node, offset) { + return node.splitText(offset); + }; + + // Handle single text node + if (startContainer == endContainer && startContainer.nodeType == 3) { + if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { + endContainer = splitText(startContainer, startOffset); + startContainer = endContainer.previousSibling; + + if (endOffset > startOffset) { + endOffset = endOffset - startOffset; + startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + startOffset = 0; + } else { + endOffset = 0; + } + } + } else { + // Split startContainer text node if needed + if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { + startContainer = splitText(startContainer, startOffset); + startOffset = 0; + } + + // Split endContainer text node if needed + if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { + endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + } + } + + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + }; + + }; + + /** + * Compares two ranges and checks if they are equal. + * + * @static + * @param {DOMRange} rng1 First range to compare. + * @param {DOMRange} rng2 First range to compare. + * @return {Boolean} true/false if the ranges are equal. + */ + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; +})(tinymce); diff --git a/js/tiny_mce/classes/dom/ScriptLoader.js b/js/tiny_mce/classes/dom/ScriptLoader.js index 70bef4a5..a322759e 100644 --- a/js/tiny_mce/classes/dom/ScriptLoader.js +++ b/js/tiny_mce/classes/dom/ScriptLoader.js @@ -1,239 +1,285 @@ -/** - * ScriptLoader.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - tinymce.dom.ScriptLoader = function(settings) { - var QUEUED = 0, - LOADING = 1, - LOADED = 2, - states = {}, - queue = [], - scriptLoadedCallbacks = {}, - queueLoadedCallbacks = [], - loading = 0, - undefined; - - /** - * Loads a specific script directly without adding it to the load queue. - * - * @method load - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - function loadScript(url, callback) { - var t = this, dom = tinymce.DOM, elm, uri, loc, id; - - // Execute callback when script is loaded - function done() { - dom.remove(id); - - if (elm) - elm.onreadystatechange = elm.onload = elm = null; - - callback(); - }; - - id = dom.uniqueId(); - - if (tinymce.isIE6) { - uri = new tinymce.util.URI(url); - loc = location; - - // If script is from same domain and we - // use IE 6 then use XHR since it's more reliable - if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) { - tinymce.util.XHR.send({ - url : tinymce._addVer(uri.getURI()), - success : function(content) { - // Create new temp script element - var script = dom.create('script', { - type : 'text/javascript' - }); - - // Evaluate script in global scope - script.text = content; - document.getElementsByTagName('head')[0].appendChild(script); - dom.remove(script); - - done(); - } - }); - - return; - } - } - - // Create new script element - elm = dom.create('script', { - id : id, - type : 'text/javascript', - src : tinymce._addVer(url) - }); - - // Add onload and readystate listeners - elm.onload = done; - elm.onreadystatechange = function() { - var state = elm.readyState; - - // Loaded state is passed on IE 6 however there - // are known issues with this method but we can't use - // XHR in a cross domain loading - if (state == 'complete' || state == 'loaded') - done(); - }; - - // Most browsers support this feature so we report errors - // for those at least to help users track their missing plugins etc - // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option - /*elm.onerror = function() { - alert('Failed to load: ' + url); - };*/ - - // Add script to document - (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); - }; - - /** - * Returns true/false if a script has been loaded or not. - * - * @method isDone - * @param {String} url URL to check for. - * @return [Boolean} true/false if the URL is loaded. - */ - this.isDone = function(url) { - return states[url] == LOADED; - }; - - /** - * Marks a specific script to be loaded. This can be useful if a script got loaded outside - * the script loader or to skip it from loading some script. - * - * @method markDone - * @param {string} u Absolute URL to the script to mark as loaded. - */ - this.markDone = function(url) { - states[url] = LOADED; - }; - - /** - * Adds a specific script to the load queue of the script loader. - * - * @method add - * @param {String} url Absolute URL to script to add. - * @param {function} callback Optional callback function to execute ones this script gets loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.add = this.load = function(url, callback, scope) { - var item, state = states[url]; - - // Add url to load queue - if (state == undefined) { - queue.push(url); - states[url] = QUEUED; - } - - if (callback) { - // Store away callback for later execution - if (!scriptLoadedCallbacks[url]) - scriptLoadedCallbacks[url] = []; - - scriptLoadedCallbacks[url].push({ - func : callback, - scope : scope || this - }); - } - }; - - /** - * Starts the loading of the queue. - * - * @method loadQueue - * @param {function} callback Optional callback to execute when all queued items are loaded. - * @param {Object} scope Optional scope to execute the callback in. - */ - this.loadQueue = function(callback, scope) { - this.loadScripts(queue, callback, scope); - }; - - /** - * Loads the specified queue of files and executes the callback ones they are loaded. - * This method is generally not used outside this class but it might be useful in some scenarios. - * - * @method loadScripts - * @param {Array} scripts Array of queue items to load. - * @param {function} callback Optional callback to execute ones all items are loaded. - * @param {Object} scope Optional scope to execute callback in. - */ - this.loadScripts = function(scripts, callback, scope) { - var loadScripts; - - function execScriptLoadedCallbacks(url) { - // Execute URL callback functions - tinymce.each(scriptLoadedCallbacks[url], function(callback) { - callback.func.call(callback.scope); - }); - - scriptLoadedCallbacks[url] = undefined; - }; - - queueLoadedCallbacks.push({ - func : callback, - scope : scope || this - }); - - loadScripts = function() { - var loadingScripts = tinymce.grep(scripts); - - // Current scripts has been handled - scripts.length = 0; - - // Load scripts that needs to be loaded - tinymce.each(loadingScripts, function(url) { - // Script is already loaded then execute script callbacks directly - if (states[url] == LOADED) { - execScriptLoadedCallbacks(url); - return; - } - - // Is script not loading then start loading it - if (states[url] != LOADING) { - states[url] = LOADING; - loading++; - - loadScript(url, function() { - states[url] = LOADED; - loading--; - - execScriptLoadedCallbacks(url); - - // Load more scripts if they where added by the recently loaded script - loadScripts(); - }); - } - }); - - // No scripts are currently loading then execute all pending queue loaded callbacks - if (!loading) { - tinymce.each(queueLoadedCallbacks, function(callback) { - callback.func.call(callback.scope); - }); - - queueLoadedCallbacks.length = 0; - } - }; - - loadScripts(); - }; - }; - - // Global script loader - tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); -})(tinymce); +/** + * ScriptLoader.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks when various items gets loaded. This class is useful to load external JavaScript files. + * + * @class tinymce.dom.ScriptLoader + * @example + * // Load a script from a specific URL using the global script loader + * tinymce.ScriptLoader.load('somescript.js'); + * + * // Load a script using a unique instance of the script loader + * var scriptLoader = new tinymce.dom.ScriptLoader(); + * + * scriptLoader.load('somescript.js'); + * + * // Load multiple scripts + * var scriptLoader = new tinymce.dom.ScriptLoader(); + * + * scriptLoader.add('somescript1.js'); + * scriptLoader.add('somescript2.js'); + * scriptLoader.add('somescript3.js'); + * + * scriptLoader.loadQueue(function() { + * alert('All scripts are now loaded.'); + * }); + */ + tinymce.dom.ScriptLoader = function(settings) { + var QUEUED = 0, + LOADING = 1, + LOADED = 2, + states = {}, + queue = [], + scriptLoadedCallbacks = {}, + queueLoadedCallbacks = [], + loading = 0, + undefined; + + /** + * Loads a specific script directly without adding it to the load queue. + * + * @method load + * @param {String} url Absolute URL to script to add. + * @param {function} callback Optional callback function to execute ones this script gets loaded. + * @param {Object} scope Optional scope to execute callback in. + */ + function loadScript(url, callback) { + var t = this, dom = tinymce.DOM, elm, uri, loc, id; + + // Execute callback when script is loaded + function done() { + dom.remove(id); + + if (elm) + elm.onreadystatechange = elm.onload = elm = null; + + callback(); + }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; + + id = dom.uniqueId(); + + if (tinymce.isIE6) { + uri = new tinymce.util.URI(url); + loc = location; + + // If script is from same domain and we + // use IE 6 then use XHR since it's more reliable + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { + tinymce.util.XHR.send({ + url : tinymce._addVer(uri.getURI()), + success : function(content) { + // Create new temp script element + var script = dom.create('script', { + type : 'text/javascript' + }); + + // Evaluate script in global scope + script.text = content; + document.getElementsByTagName('head')[0].appendChild(script); + dom.remove(script); + + done(); + }, + + error : error + }); + + return; + } + } + + // Create new script element + elm = dom.create('script', { + id : id, + type : 'text/javascript', + src : tinymce._addVer(url) + }); + + // Add onload listener for non IE browsers since IE9 + // fires onload event before the script is parsed and executed + if (!tinymce.isIE) + elm.onload = done; + + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; + + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } + + // Most browsers support this feature so we report errors + // for those at least to help users track their missing plugins etc + // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option + /*elm.onerror = function() { + alert('Failed to load: ' + url); + };*/ + + // Add script to document + (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); + }; + + /** + * Returns true/false if a script has been loaded or not. + * + * @method isDone + * @param {String} url URL to check for. + * @return [Boolean} true/false if the URL is loaded. + */ + this.isDone = function(url) { + return states[url] == LOADED; + }; + + /** + * Marks a specific script to be loaded. This can be useful if a script got loaded outside + * the script loader or to skip it from loading some script. + * + * @method markDone + * @param {string} u Absolute URL to the script to mark as loaded. + */ + this.markDone = function(url) { + states[url] = LOADED; + }; + + /** + * Adds a specific script to the load queue of the script loader. + * + * @method add + * @param {String} url Absolute URL to script to add. + * @param {function} callback Optional callback function to execute ones this script gets loaded. + * @param {Object} scope Optional scope to execute callback in. + */ + this.add = this.load = function(url, callback, scope) { + var item, state = states[url]; + + // Add url to load queue + if (state == undefined) { + queue.push(url); + states[url] = QUEUED; + } + + if (callback) { + // Store away callback for later execution + if (!scriptLoadedCallbacks[url]) + scriptLoadedCallbacks[url] = []; + + scriptLoadedCallbacks[url].push({ + func : callback, + scope : scope || this + }); + } + }; + + /** + * Starts the loading of the queue. + * + * @method loadQueue + * @param {function} callback Optional callback to execute when all queued items are loaded. + * @param {Object} scope Optional scope to execute the callback in. + */ + this.loadQueue = function(callback, scope) { + this.loadScripts(queue, callback, scope); + }; + + /** + * Loads the specified queue of files and executes the callback ones they are loaded. + * This method is generally not used outside this class but it might be useful in some scenarios. + * + * @method loadScripts + * @param {Array} scripts Array of queue items to load. + * @param {function} callback Optional callback to execute ones all items are loaded. + * @param {Object} scope Optional scope to execute callback in. + */ + this.loadScripts = function(scripts, callback, scope) { + var loadScripts; + + function execScriptLoadedCallbacks(url) { + // Execute URL callback functions + tinymce.each(scriptLoadedCallbacks[url], function(callback) { + callback.func.call(callback.scope); + }); + + scriptLoadedCallbacks[url] = undefined; + }; + + queueLoadedCallbacks.push({ + func : callback, + scope : scope || this + }); + + loadScripts = function() { + var loadingScripts = tinymce.grep(scripts); + + // Current scripts has been handled + scripts.length = 0; + + // Load scripts that needs to be loaded + tinymce.each(loadingScripts, function(url) { + // Script is already loaded then execute script callbacks directly + if (states[url] == LOADED) { + execScriptLoadedCallbacks(url); + return; + } + + // Is script not loading then start loading it + if (states[url] != LOADING) { + states[url] = LOADING; + loading++; + + loadScript(url, function() { + states[url] = LOADED; + loading--; + + execScriptLoadedCallbacks(url); + + // Load more scripts if they where added by the recently loaded script + loadScripts(); + }); + } + }); + + // No scripts are currently loading then execute all pending queue loaded callbacks + if (!loading) { + tinymce.each(queueLoadedCallbacks, function(callback) { + callback.func.call(callback.scope); + }); + + queueLoadedCallbacks.length = 0; + } + }; + + loadScripts(); + }; + }; + + // Global script loader + tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); +})(tinymce); diff --git a/js/tiny_mce/classes/dom/Selection.js b/js/tiny_mce/classes/dom/Selection.js index ee3bb70f..3041e667 100644 --- a/js/tiny_mce/classes/dom/Selection.js +++ b/js/tiny_mce/classes/dom/Selection.js @@ -1,720 +1,1103 @@ -/** - * Selection.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - function trimNl(s) { - return s.replace(/[\n\r]+/g, ''); - }; - - // Shorten names - var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; - - /** - * This class handles text and control selection it's an crossbrowser utility class. - * Consult the TinyMCE Wiki API for more details and examples on how to use this class. - * @class tinymce.dom.Selection - */ - tinymce.create('tinymce.dom.Selection', { - /** - * Constructs a new selection instance. - * - * @constructor - * @method Selection - * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. - * @param {Window} win Window to bind the selection object to. - * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. - */ - Selection : function(dom, win, serializer) { - var t = this; - - t.dom = dom; - t.win = win; - t.serializer = serializer; - - // Add events - each([ - 'onBeforeSetContent', - 'onBeforeGetContent', - 'onSetContent', - 'onGetContent' - ], function(e) { - t[e] = new tinymce.util.Dispatcher(t); - }); - - // No W3C Range support - if (!t.win.getSelection) - t.tridentSel = new tinymce.dom.TridentSelection(t); - - // Prevent leaks - tinymce.addUnload(t.destroy, t); - }, - - /** - * Returns the selected contents using the DOM serializer passed in to this class. - * - * @method getContent - * @param {Object} s Optional settings class with for example output format text or html. - * @return {String} Selected contents in for example HTML format. - */ - getContent : function(s) { - var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; - - s = s || {}; - wb = wa = ''; - s.get = true; - s.format = s.format || 'html'; - t.onBeforeGetContent.dispatch(t, s); - - if (s.format == 'text') - return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); - - if (r.cloneContents) { - n = r.cloneContents(); - - if (n) - e.appendChild(n); - } else if (is(r.item) || is(r.htmlText)) - e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText; - else - e.innerHTML = r.toString(); - - // Keep whitespace before and after - if (/^\s/.test(e.innerHTML)) - wb = ' '; - - if (/\s+$/.test(e.innerHTML)) - wa = ' '; - - s.getInner = true; - - s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; - t.onGetContent.dispatch(t, s); - - return s.content; - }, - - /** - * Sets the current selection to the specified content. If any contents is selected it will be replaced - * with the contents passed in to this function. If there is no selection the contents will be inserted - * where the caret is placed in the editor/page. - * - * @method setContent - * @param {String} h HTML contents to set could also be other formats depending on settings. - * @param {Object} s Optional settings object with for example data format. - */ - setContent : function(h, s) { - var t = this, r = t.getRng(), c, d = t.win.document; - - s = s || {format : 'html'}; - s.set = true; - h = s.content = t.dom.processHTML(h); - - // Dispatch before set content event - t.onBeforeSetContent.dispatch(t, s); - h = s.content; - - if (r.insertNode) { - // Make caret marker since insertNode places the caret in the beginning of text after insert - h += '_'; - - // Delete and insert new node - if (r.startContainer == d && r.endContainer == d) { - // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents - d.body.innerHTML = h; - } else { - r.deleteContents(); - r.insertNode(t.getRng().createContextualFragment(h)); - } - - // Move to caret marker - c = t.dom.get('__caret'); - - // Make sure we wrap it compleatly, Opera fails with a simple select call - r = d.createRange(); - r.setStartBefore(c); - r.setEndBefore(c); - t.setRng(r); - - // Remove the caret position - t.dom.remove('__caret'); - } else { - if (r.item) { - // Delete content and get caret text selection - d.execCommand('Delete', false, null); - r = t.getRng(); - } - - r.pasteHTML(h); - } - - // Dispatch set content event - t.onSetContent.dispatch(t, s); - }, - - /** - * Returns the start element of a selection range. If the start is in a text - * node the parent element will be returned. - * - * @method getStart - * @return {Element} Start element of selection range. - */ - getStart : function() { - var t = this, r = t.getRng(), e; - - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); - - r = r.duplicate(); - r.collapse(1); - e = r.parentElement(); - - if (e && e.nodeName == 'BODY') - return e.firstChild || e; - - return e; - } else { - e = r.startContainer; - - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)]; - - if (e && e.nodeType == 3) - return e.parentNode; - - return e; - } - }, - - /** - * Returns the end element of a selection range. If the end is in a text - * node the parent element will be returned. - * - * @method getEnd - * @return {Element} End element of selection range. - */ - getEnd : function() { - var t = this, r = t.getRng(), e, eo; - - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); - - r = r.duplicate(); - r.collapse(0); - e = r.parentElement(); - - if (e && e.nodeName == 'BODY') - return e.lastChild || e; - - return e; - } else { - e = r.endContainer; - eo = r.endOffset; - - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[eo > 0 ? eo - 1 : eo]; - - if (e && e.nodeType == 3) - return e.parentNode; - - return e; - } - }, - - /** - * Returns a bookmark location for the current selection. This bookmark object - * can then be used to restore the selection after some content modification to the document. - * - * @method getBookmark - * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. - * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. - * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. - */ - getBookmark : function(type, normalized) { - var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; - - function findIndex(name, element) { - var index = 0; - - each(dom.select(name), function(node, i) { - if (node == element) - index = i; - }); - - return index; - }; - - if (type == 2) { - function getLocation() { - var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; - - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; - - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) - offset += node.nodeValue.length; - } - - point.push(offset); - } else { - childNodes = container.childNodes; - - if (offset >= childNodes.length) { - after = 1; - offset = childNodes.length - 1; - } - - point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); - } - - for (; container && container != root; container = container.parentNode) - point.push(t.dom.nodeIndex(container, normalized)); - - return point; - }; - - bookmark.start = getPoint(rng, true); - - if (!t.isCollapsed()) - bookmark.end = getPoint(rng); - - return bookmark; - }; - - return getLocation(); - } - - // Handle simple range - if (type) - return {rng : t.getRng()}; - - rng = t.getRng(); - id = dom.uniqueId(); - collapsed = tinyMCE.activeEditor.selection.isCollapsed(); - styles = 'overflow:hidden;line-height:0px'; - - // Explorer method - if (rng.duplicate || rng.item) { - // Text selection - if (!rng.item) { - rng2 = rng.duplicate(); - - // Insert start marker - rng.collapse(); - rng.pasteHTML('' + chr + ''); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.pasteHTML('' + chr + ''); - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name : name, index : findIndex(name, element)}; - } - } else { - element = t.getNode(); - name = element.nodeName; - if (name == 'IMG') - return {name : name, index : findIndex(name, element)}; - - // W3C method - rng2 = rng.cloneRange(); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr)); - } - - rng.collapse(true); - rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr)); - } - - t.moveToBookmark({id : id, keep : 1}); - - return {id : id}; - }, - - /** - * Restores the selection to the specified bookmark. - * - * @method moveToBookmark - * @param {Object} bookmark Bookmark to restore selection from. - * @return {Boolean} true/false if it was successful or not. - */ - moveToBookmark : function(bookmark) { - var t = this, dom = t.dom, marker1, marker2, rng, root; - - // Clear selection cache - if (t.tridentSel) - t.tridentSel.destroy(); - - if (bookmark) { - if (bookmark.start) { - rng = dom.createRng(); - root = dom.getRoot(); - - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset; - - if (point) { - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) - node = node.childNodes[point[i]]; - - // Set offset within container node - if (start) - rng.setStart(node, point[0]); - else - rng.setEnd(node, point[0]); - } - }; - - setEndPoint(true); - setEndPoint(); - - t.setRng(rng); - } else if (bookmark.id) { - rng = dom.createRng(); - - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - - if (marker) { - node = marker.parentNode; - - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } - - rng.setStart(node, idx); - rng.setEnd(node, idx); - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } - - rng.setEnd(node, idx); - } - - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; - - // Remove all marker text nodes - each(tinymce.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - }); - - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature - while (marker = dom.get(bookmark.id + '_' + suffix)) - dom.remove(marker, 1); - - // If siblings are text nodes then merge them - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); - - if (suffix == 'start') { - rng.setStart(prev, idx); - rng.setEnd(prev, idx); - } else - rng.setEnd(prev, idx); - } - } - } - }; - - // Restore start/end points - restoreEndPoint('start'); - restoreEndPoint('end'); - - t.setRng(rng); - } else if (bookmark.name) { - t.select(dom.select(bookmark.name)[bookmark.index]); - } else if (bookmark.rng) - t.setRng(bookmark.rng); - } - }, - - /** - * Selects the specified element. This will place the start and end of the selection range around the element. - * - * @method select - * @param {Element} node HMTL DOM element to select. - * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser. - * @return {Element} Selected element the same element as the one that got passed in. - */ - select : function(node, content) { - var t = this, dom = t.dom, rng = dom.createRng(), idx; - - idx = dom.nodeIndex(node); - rng.setStart(node.parentNode, idx); - rng.setEnd(node.parentNode, idx + 1); - - // Find first/last text node or BR element - if (content) { - function setPoint(node, start) { - var walker = new tinymce.dom.TreeWalker(node, node); - - do { - // Text node - if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { - if (start) - rng.setStart(node, 0); - else - rng.setEnd(node, node.nodeValue.length); - - return; - } - - // BR element - if (node.nodeName == 'BR') { - if (start) - rng.setStartBefore(node); - else - rng.setEndBefore(node); - - return; - } - } while (node = (start ? walker.next() : walker.prev())); - }; - - setPoint(node, 1); - setPoint(node); - } - - t.setRng(rng); - - return node; - }, - - /** - * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. - * - * @method isCollapsed - * @return {Boolean} true/false state if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. - */ - isCollapsed : function() { - var t = this, r = t.getRng(), s = t.getSel(); - - if (!r || r.item) - return false; - - if (r.compareEndPoints) - return r.compareEndPoints('StartToEnd', r) === 0; - - return !s || r.collapsed; - }, - - /** - * Collapse the selection to start or end of range. - * - * @method collapse - * @param {Boolean} b Optional boolean state if to collapse to end or not. Defaults to start. - */ - collapse : function(b) { - var t = this, r = t.getRng(), n; - - // Control range on IE - if (r.item) { - n = r.item(0); - r = this.win.document.body.createTextRange(); - r.moveToElementText(n); - } - - r.collapse(!!b); - t.setRng(r); - }, - - /** - * Returns the browsers internal selection object. - * - * @method getSel - * @return {Selection} Internal browser selection object. - */ - getSel : function() { - var t = this, w = this.win; - - return w.getSelection ? w.getSelection() : w.document.selection; - }, - - /** - * Returns the browsers internal range object. - * - * @method getRng - * @param {Boolean} w3c Forces a compatible W3C range on IE. - * @return {Range} Internal browser range object. - */ - getRng : function(w3c) { - var t = this, s, r; - - // Found tridentSel object then we need to use that one - if (w3c && t.tridentSel) - return t.tridentSel.getRangeAt(0); - - try { - if (s = t.getSel()) - r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange()); - } catch (ex) { - // IE throws unspecified error here if TinyMCE is placed in a frame/iframe - } - - // No range found then create an empty one - // This can occur when the editor is placed in a hidden container element on Gecko - // Or on IE when there was an exception - if (!r) - r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange(); - - return r; - }, - - /** - * Changes the selection to the specified DOM range. - * - * @method setRng - * @param {Range} r Range to select. - */ - setRng : function(r) { - var s, t = this; - - if (!t.tridentSel) { - s = t.getSel(); - - if (s) { - s.removeAllRanges(); - s.addRange(r); - } - } else { - // Is W3C Range - if (r.cloneRange) { - t.tridentSel.addRange(r); - return; - } - - // Is IE specific range - try { - r.select(); - } catch (ex) { - // Needed for some odd IE bug #1843306 - } - } - }, - - /** - * Sets the current selection to the specified DOM element. - * - * @method setNode - * @param {Element} n Element to set as the contents of the selection. - * @return {Element} Returns the element that got passed in. - */ - setNode : function(n) { - var t = this; - - t.setContent(t.dom.getOuterHTML(n)); - - return n; - }, - - /** - * Returns the currently selected element or the common ancestor element for both start and end of the selection. - * - * @method getNode - * @return {Element} Currently selected element or common ancestor element. - */ - getNode : function() { - var t = this, rng = t.getRng(), sel = t.getSel(), elm; - - if (rng.setStart) { - // Range maybe lost after the editor is made visible again - if (!rng) - return t.dom.getRoot(); - - elm = rng.commonAncestorContainer; - - // Handle selection a image or other control like element such as anchors - if (!rng.collapsed) { - if (rng.startContainer == rng.endContainer) { - if (rng.startOffset - rng.endOffset < 2) { - if (rng.startContainer.hasChildNodes()) - elm = rng.startContainer.childNodes[rng.startOffset]; - } - } - - // If the anchor node is a element instead of a text node then return this element - if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) - return sel.anchorNode.childNodes[sel.anchorOffset]; - } - - if (elm && elm.nodeType == 3) - return elm.parentNode; - - return elm; - } - - return rng.item ? rng.item(0) : rng.parentElement(); - }, - - getSelectedBlocks : function(st, en) { - var t = this, dom = t.dom, sb, eb, n, bl = []; - - sb = dom.getParent(st || t.getStart(), dom.isBlock); - eb = dom.getParent(en || t.getEnd(), dom.isBlock); - - if (sb) - bl.push(sb); - - if (sb && eb && sb != eb) { - n = sb; - - while ((n = n.nextSibling) && n != eb) { - if (dom.isBlock(n)) - bl.push(n); - } - } - - if (eb && sb != eb) - bl.push(eb); - - return bl; - }, - - destroy : function(s) { - var t = this; - - t.win = null; - - if (t.tridentSel) - t.tridentSel.destroy(); - - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - } - }); -})(tinymce); +/** + * Selection.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + /** + * This class handles text and control selection it's an crossbrowser utility class. + * Consult the TinyMCE Wiki API for more details and examples on how to use this class. + * + * @class tinymce.dom.Selection + * @example + * // Getting the currently selected node for the active editor + * alert(tinymce.activeEditor.selection.getNode().nodeName); + */ + tinymce.create('tinymce.dom.Selection', { + /** + * Constructs a new selection instance. + * + * @constructor + * @method Selection + * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. + * @param {Window} win Window to bind the selection object to. + * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. + */ + Selection : function(dom, win, serializer) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + + // Add events + each([ + /** + * This event gets executed before contents is extracted from the selection. + * + * @event onBeforeSetContent + * @param {tinymce.dom.Selection} selection Selection object that fired the event. + * @param {Object} args Contains things like the contents that will be returned. + */ + 'onBeforeSetContent', + + /** + * This event gets executed before contents is inserted into selection. + * + * @event onBeforeGetContent + * @param {tinymce.dom.Selection} selection Selection object that fired the event. + * @param {Object} args Contains things like the contents that will be inserted. + */ + 'onBeforeGetContent', + + /** + * This event gets executed when contents is inserted into selection. + * + * @event onSetContent + * @param {tinymce.dom.Selection} selection Selection object that fired the event. + * @param {Object} args Contains things like the contents that will be inserted. + */ + 'onSetContent', + + /** + * This event gets executed when contents is extracted from the selection. + * + * @event onGetContent + * @param {tinymce.dom.Selection} selection Selection object that fired the event. + * @param {Object} args Contains things like the contents that will be returned. + */ + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); + + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); + + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); + + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, + + /** + * Move the selection cursor range to the specified node and offset. + * @param node Node to put the cursor in. + * @param offset Offset from the start of the node to put the cursor at. + */ + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); + }, + /** + * Returns the selected contents using the DOM serializer passed in to this class. + * + * @method getContent + * @param {Object} s Optional settings class with for example output format text or html. + * @return {String} Selected contents in for example HTML format. + * @example + * // Alerts the currently selected contents + * alert(tinyMCE.activeEditor.selection.getContent()); + * + * // Alerts the currently selected contents as plain text + * alert(tinyMCE.activeEditor.selection.getContent({format : 'text'})); + */ + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); + + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + + if (r.cloneContents) { + n = r.cloneContents(); + + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); + + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; + + if (/\s+$/.test(e.innerHTML)) + wa = ' '; + + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; + }, + + /** + * Sets the current selection to the specified content. If any contents is selected it will be replaced + * with the contents passed in to this function. If there is no selection the contents will be inserted + * where the caret is placed in the editor/page. + * + * @method setContent + * @param {String} content HTML contents to set could also be other formats depending on settings. + * @param {Object} args Optional settings object with for example data format. + * @example + * // Inserts some HTML contents at the current selection + * tinyMCE.activeEditor.selection.setContent('Some contents'); + */ + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; + + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; + + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; + + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); + + if (doc.body.childNodes.length == 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); + + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } + } + + // Move to caret marker + caretNode = self.dom.get('__caret'); + + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); + + // Remove the caret position + self.dom.remove('__caret'); + + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } + + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); + } else + rng.pasteHTML(content); + } + + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, + + /** + * Returns the start element of a selection range. If the start is in a text + * node the parent element will be returned. + * + * @method getStart + * @return {Element} Start element of selection range. + */ + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; + + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); + + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); + + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; + } + } + + return startElement; + } else { + startElement = rng.startContainer; + + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; + + return startElement; + } + }, + + /** + * Returns the end element of a selection range. If the end is in a text + * node the parent element will be returned. + * + * @method getEnd + * @return {Element} End element of selection range. + */ + getEnd : function() { + var t = this, r = t.getRng(), e, eo; + + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild || e; + + return e; + } else { + e = r.endContainer; + eo = r.endOffset; + + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; + + if (e && e.nodeType == 3) + return e.parentNode; + + return e; + } + }, + + /** + * Returns a bookmark location for the current selection. This bookmark object + * can then be used to restore the selection after some content modification to the document. + * + * @method getBookmark + * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. + * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. + * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. + * @example + * // Stores a bookmark of the current selection + * var bm = tinyMCE.activeEditor.selection.getBookmark(); + * + * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content'); + * + * // Restore the selection bookmark + * tinyMCE.activeEditor.selection.moveToBookmark(bm); + */ + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + + function findIndex(name, element) { + var index = 0; + + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); + + return index; + }; + + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } + + point.push(offset); + } else { + childNodes = container.childNodes; + + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } + + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } + + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); + + return point; + }; + + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (t.tridentSel) + return t.tridentSel.getBookmark(type); + + return getLocation(); + } + + // Handle simple range + if (type) + return {rng : t.getRng()}; + + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; + + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); + + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) == 0) + rng2.move('character', -1); + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; + + // W3C method + rng2 = rng.cloneRange(); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } + + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); + + return {id : id}; + }, + + /** + * Restores the selection to the specified bookmark. + * + * @method moveToBookmark + * @param {Object} bookmark Bookmark to restore selection from. + * @return {Boolean} true/false if it was successful or not. + * @example + * // Stores a bookmark of the current selection + * var bm = tinyMCE.activeEditor.selection.getBookmark(); + * + * tinyMCE.activeEditor.setContent(tinyMCE.activeEditor.getContent() + 'Some new content'); + * + * // Restore the selection bookmark + * tinyMCE.activeEditor.selection.moveToBookmark(bm); + */ + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; + + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) + return; + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); + + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } + + return true; + }; + + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); + + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; + + function addBogus(node) { + // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly + if (dom.isBlock(node) && !node.innerHTML) + node.innerHTML = !isIE ? '
    ' : ' '; + + return node; + }; + + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); + + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); + } + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) + t.setRng(bookmark.rng); + } + }, + + /** + * Selects the specified element. This will place the start and end of the selection range around the element. + * + * @method select + * @param {Element} node HMTL DOM element to select. + * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser. + * @return {Element} Selected element the same element as the one that got passed in. + * @example + * // Select the first paragraph in the active editor + * tinyMCE.activeEditor.selection.select(tinyMCE.activeEditor.dom.select('p')[0]); + */ + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; + + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + setPoint(node, 1); + setPoint(node); + } + + t.setRng(rng); + } + + return node; + }, + + /** + * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. + * + * @method isCollapsed + * @return {Boolean} true/false state if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. + */ + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; + + return !s || r.collapsed; + }, + + /** + * Collapse the selection to start or end of range. + * + * @method collapse + * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start. + */ + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; + + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } + + rng.collapse(!!to_start); + self.setRng(rng); + }, + + /** + * Returns the browsers internal selection object. + * + * @method getSel + * @return {Selection} Internal browser selection object. + */ + getSel : function() { + var t = this, w = this.win; + + return w.getSelection ? w.getSelection() : w.document.selection; + }, + + /** + * Returns the browsers internal range object. + * + * @method getRng + * @param {Boolean} w3c Forces a compatible W3C range on IE. + * @return {Range} Internal browser range object. + * @see http://www.quirksmode.org/dom/range_intro.html + * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/ + */ + getRng : function(w3c) { + var t = this, s, r, elm, doc = t.win.document; + + // Found tridentSel object then we need to use that one + if (w3c && t.tridentSel) + return t.tridentSel.getRangeAt(0); + + try { + if (s = t.getSel()) + r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } + + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + r = doc.createRange(); + r.setStartBefore(elm); + r.setEndAfter(elm); + } + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!r) + r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; + } + } + + return r; + }, + + /** + * Changes the selection to the specified DOM range. + * + * @method setRng + * @param {Range} r Range to select. + */ + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); + + if (s) { + t.explicitRange = r; + + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } + + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } + + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } + }, + + /** + * Sets the current selection to the specified DOM element. + * + * @method setNode + * @param {Element} n Element to set as the contents of the selection. + * @return {Element} Returns the element that got passed in. + * @example + * // Inserts a DOM node at current selection/caret location + * tinyMCE.activeEditor.selection.setNode(tinyMCE.activeEditor.dom.create('img', {src : 'some.gif', title : 'some title'})); + */ + setNode : function(n) { + var t = this; + + t.setContent(t.dom.getOuterHTML(n)); + + return n; + }, + + /** + * Returns the currently selected element or the common ancestor element for both start and end of the selection. + * + * @method getNode + * @return {Element} Currently selected element or common ancestor element. + * @example + * // Alerts the currently selected elements node name + * alert(tinyMCE.activeEditor.selection.getNode().nodeName); + */ + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; + + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); + + if (rng.setStart) { + elm = rng.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } + + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + } + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } + + if (start && start === end) + return start; + } + } + + if (elm && elm.nodeType == 3) + return elm.parentNode; + + return elm; + } + + return rng.item ? rng.item(0) : rng.parentElement(); + }, + + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; + + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); + + if (sb) + bl.push(sb); + + if (sb && eb && sb != eb) { + n = sb; + + var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot()); + while ((n = walker.next()) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } + + if (eb && sb != eb) + bl.push(eb); + + return bl; + }, + + normalize : function() { + var self = this, rng, normalized; + + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; + + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node; + + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; + + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = container.body; + offset = 0; + } + + // If the container is body try move it into the closest text node or position + // TODO: Add more logic here to handle element selection cases + if (container === body) { + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes()) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new tinymce.dom.TreeWalker(container, body); + do { + // Found a text node use that position + if (node.nodeType === 3) { + offset = start ? 0 : node.nodeValue.length - 1; + container = node; + break; + } + + // Found a BR element that we can place the caret before + if (node.nodeName === 'BR') { + offset = dom.nodeIndex(node); + container = node.parentNode; + break; + } + } while (node = (start ? walker.next() : walker.prev())); + + normalized = true; + } + } + } + + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; + + rng = self.getRng(); + + // Normalize the end points + normalizeEndPoint(true); + + if (rng.collapsed) + normalizeEndPoint(); + + // Set the selection if it was normalized + if (normalized) { + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng); + } + }, + + destroy : function(s) { + var t = this; + + t.win = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + }; + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); + + pointRng.select(); + } + } else + endSelection(); + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + }; + + // Detect when user selects outside BODY + dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { + if (e.target.nodeName === 'HTML') { + if (started) + endSelection(); + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/dom/Serializer.js b/js/tiny_mce/classes/dom/Serializer.js index 71c0b0bc..f3f087af 100644 --- a/js/tiny_mce/classes/dom/Serializer.js +++ b/js/tiny_mce/classes/dom/Serializer.js @@ -1,945 +1,379 @@ -/** - * Serializer.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Shorten names - var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko; - - function wildcardToRE(s) { - return s.replace(/([?+*])/g, '.$1'); - }; - - /** - * This class is used to serialize DOM trees into a string. - * Consult the TinyMCE Wiki API for more details and examples on how to use this class. - * @class tinymce.dom.Serializer - */ - tinymce.create('tinymce.dom.Serializer', { - /** - * Constucts a new DOM serializer class. - * - * @constructor - * @method Serializer - * @param {Object} s Optional name/Value collection of settings for the serializer. - */ - Serializer : function(s) { - var t = this; - - t.key = 0; - t.onPreProcess = new Dispatcher(t); - t.onPostProcess = new Dispatcher(t); - - try { - t.writer = new tinymce.dom.XMLWriter(); - } catch (ex) { - // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter - t.writer = new tinymce.dom.StringWriter(); - } - - // Default settings - t.settings = s = extend({ - dom : tinymce.DOM, - valid_nodes : 0, - node_filter : 0, - attr_filter : 0, - invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/, - closed : /^(br|hr|input|meta|img|link|param|area)$/, - entity_encoding : 'named', - entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro', - valid_elements : '*[*]', - extended_valid_elements : 0, - invalid_elements : 0, - fix_table_elements : 1, - fix_list_elements : true, - fix_content_duplication : true, - convert_fonts_to_spans : false, - font_size_classes : 0, - apply_source_formatting : 0, - indent_mode : 'simple', - indent_char : '\t', - indent_levels : 1, - remove_linebreaks : 1, - remove_redundant_brs : 1, - element_format : 'xhtml' - }, s); - - t.dom = s.dom; - t.schema = s.schema; - - // Use raw entities if no entities are defined - if (s.entity_encoding == 'named' && !s.entities) - s.entity_encoding = 'raw'; - - if (s.remove_redundant_brs) { - t.onPostProcess.add(function(se, o) { - // Remove single BR at end of block elements since they get rendered - o.content = o.content.replace(/(
    \s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) { - // Check if it's a single element - if (/^
    \s*<\//.test(a)) - return ''; - - return a; - }); - }); - } - - // Remove XHTML element endings i.e. produce crap :) XHTML is better - if (s.element_format == 'html') { - t.onPostProcess.add(function(se, o) { - o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>'); - }); - } - - if (s.fix_list_elements) { - t.onPreProcess.add(function(se, o) { - var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np; - - function prevNode(e, n) { - var a = n.split(','), i; - - while ((e = e.previousSibling) != null) { - for (i=0; i= 1767) { - each(t.dom.select('p table', o.node).reverse(), function(n) { - var parent = t.dom.getParent(n.parentNode, 'table,p'); - - if (parent.nodeName != 'TABLE') { - try { - t.dom.split(parent, n); - } catch (ex) { - // IE can sometimes fire an unknown runtime error so we just ignore it - } - } - }); - } - }); - } - }, - - /** - * Sets a list of entities to use for the named entity encoded. - * - * @method setEntities - * @param {String} s List of entities in the following format: number,name,.... - */ - setEntities : function(s) { - var t = this, a, i, l = {}, v; - - // No need to setup more than once - if (t.entityLookup) - return; - - // Build regex and lookup array - a = s.split(','); - for (i = 0; i < a.length; i += 2) { - v = a[i]; - - // Don't add default & " etc. - if (v == 34 || v == 38 || v == 60 || v == 62) - continue; - - l[String.fromCharCode(a[i])] = a[i + 1]; - - v = parseInt(a[i]).toString(16); - } - - t.entityLookup = l; - }, - - /** - * Sets the valid elements rules of the serializer this enables you to specify things like what elements should be - * outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method setRules - * @param {String} s Valid elements rules string. - */ - setRules : function(s) { - var t = this; - - t._setup(); - t.rules = {}; - t.wildRules = []; - t.validElements = {}; - - return t.addRules(s); - }, - - /** - * Adds valid elements rules to the serializer this enables you to specify things like what elements should be - * outputted and what attributes specific elements might have. - * Consult the Wiki for more details on this format. - * - * @method addRules - * @param {String} s Valid elements rules string to add. - */ - addRules : function(s) { - var t = this, dr; - - if (!s) - return; - - t._setup(); - - each(s.split(','), function(s) { - var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = []; - - // Extend with default rules - if (dr) - at = tinymce.extend([], dr.attribs); - - // Parse attributes - if (p.length > 1) { - each(p[1].split('|'), function(s) { - var ar = {}, i; - - at = at || []; - - // Parse attribute rule - s = s.replace(/::/g, '~'); - s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s); - s[2] = s[2].replace(/~/g, ':'); - - // Add required attributes - if (s[1] == '!') { - ra = ra || []; - ra.push(s[2]); - } - - // Remove inherited attributes - if (s[1] == '-') { - for (i = 0; i = 1767)) { - // Create an empty HTML document - doc = impl.createHTMLDocument(""); - - // Add the element or it's children if it's a body element to the new document - each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) { - doc.body.appendChild(doc.importNode(node, true)); - }); - - // Grab first child or body element for serialization - if (n.nodeName != 'BODY') - n = doc.body.firstChild; - else - n = doc.body; - - // set the new document in DOMUtils so createElement etc works - oldDoc = t.dom.doc; - t.dom.doc = doc; - } - - t.key = '' + (parseInt(t.key) + 1); - - // Pre process - if (!o.no_events) { - o.node = n; - t.onPreProcess.dispatch(t, o); - } - - // Serialize HTML DOM into a string - t.writer.reset(); - t._info = o; - t._serializeNode(n, o.getInner); - - // Post process - o.content = t.writer.getContent(); - - // Restore the old document if it was changed - if (oldDoc) - t.dom.doc = oldDoc; - - if (!o.no_events) - t.onPostProcess.dispatch(t, o); - - t._postProcess(o); - o.node = null; - - return tinymce.trim(o.content); - }, - - // Internal functions - - /** - * Indents the specified content object. - * - * @param {Object} o Content object to indent. - */ - _postProcess : function(o) { - var t = this, s = t.settings, h = o.content, sc = [], p; - - if (o.format == 'html') { - // Protect some elements - p = t._protect({ - content : h, - patterns : [ - {pattern : /(]*>)(.*?)(<\/script>)/g}, - {pattern : /(]*>)(.*?)(<\/noscript>)/g}, - {pattern : /(]*>)(.*?)(<\/style>)/g}, - {pattern : /(]*>)(.*?)(<\/pre>)/g, encode : 1}, - {pattern : /()/g} - ] - }); - - h = p.content; - - // Entity encode - if (s.entity_encoding !== 'raw') - h = t._encode(h); - - // Use BR instead of   padded P elements inside editor and use

     

    outside editor -/* if (o.set) - h = h.replace(/

    \s+( | |\u00a0|
    )\s+<\/p>/g, '


    '); - else - h = h.replace(/

    \s+( | |\u00a0|
    )\s+<\/p>/g, '

    $1

    ');*/ - - // Since Gecko and Safari keeps whitespace in the DOM we need to - // remove it inorder to match other browsers. But I think Gecko and Safari is right. - // This process is only done when getting contents out from the editor. - if (!o.set) { - // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char - h = h.replace(/

    \s+<\/p>|]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? ' 

    ' : ' 

    '); - - if (s.remove_linebreaks) { - h = h.replace(/\r?\n|\r/g, ' '); - h = h.replace(/(<[^>]+>)\s+/g, '$1 '); - h = h.replace(/\s+(<\/[^>]+>)/g, ' $1'); - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start - h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, ''); // Trim block end - } - - // Simple indentation - if (s.apply_source_formatting && s.indent_mode == 'simple') { - // Add line breaks before and after block elements - h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n'); - h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>'); - h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '\n'); - h = h.replace(/\n\n/g, '\n'); - } - } - - h = t._unprotect(h, p); - - // Restore CDATA sections - h = h.replace(//g, ''); - - // Restore the \u00a0 character if raw mode is enabled - if (s.entity_encoding == 'raw') - h = h.replace(/

     <\/p>|]+)> <\/p>/g, '\u00a0

    '); - - // Restore noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return '' + t.dom.decode(text.replace(//g, '')) + ''; - }); - } - - o.content = h; - }, - - _serializeNode : function(n, inner) { - var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type; - - if (!s.node_filter || s.node_filter(n)) { - switch (n.nodeType) { - case 1: // Element - if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus')) - return; - - iv = keep = false; - hc = n.hasChildNodes(); - nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase(); - - // Get internal type - type = n.getAttribute('_mce_type'); - if (type) { - if (!t._info.cleanup) { - iv = true; - return; - } else - keep = 1; - } - - // Add correct prefix on IE - if (isIE) { - if (n.scopeName !== 'HTML' && n.scopeName !== 'html') - nn = n.scopeName + ':' + nn; - } - - // Remove mce prefix on IE needed for the abbr element - if (nn.indexOf('mce:') === 0) - nn = nn.substring(4); - - // Check if valid - if (!keep) { - if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) { - iv = true; - break; - } - } - - if (isIE) { - // Fix IE content duplication (DOM can have multiple copies of the same node) - if (s.fix_content_duplication) { - if (n._mce_serialized == t.key) - return; - - n._mce_serialized = t.key; - } - - // IE sometimes adds a / infront of the node name - if (nn.charAt(0) == '/') - nn = nn.substring(1); - } else if (isGecko) { - // Ignore br elements - if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz') - return; - } - - // Check if valid child - if (s.validate_children) { - if (t.elementName && !t.schema.isValid(t.elementName, nn)) { - iv = true; - break; - } - - t.elementName = nn; - } - - ru = t.findRule(nn); - - // No valid rule for this element could be found then skip it - if (!ru) { - iv = true; - break; - } - - nn = ru.name || nn; - closed = s.closed.test(nn); - - // Skip empty nodes or empty node name in IE - if ((!hc && ru.noEmpty) || (isIE && !nn)) { - iv = true; - break; - } - - // Check required - if (ru.requiredAttribs) { - a = ru.requiredAttribs; - - for (i = a.length - 1; i >= 0; i--) { - if (this.dom.getAttrib(n, a[i]) !== '') - break; - } - - // None of the required was there - if (i == -1) { - iv = true; - break; - } - } - - w.writeStartElement(nn); - - // Add ordered attributes - if (ru.attribs) { - for (i=0, at = ru.attribs, l = at.length; i-1; i--) { - no = at[i]; - - if (no.specified) { - a = no.nodeName.toLowerCase(); - - if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a)) - continue; - - ar = t.findAttribRule(ru, a); - v = t._getAttrib(n, ar, a); - - if (v !== null) - w.writeAttribute(a, v); - } - } - } - - // Keep type attribute - if (type && keep) - w.writeAttribute('_mce_type', type); - - // Write text from script - if (nn === 'script' && tinymce.trim(n.innerHTML)) { - w.writeText('// '); // Padd it with a comment so it will parse on older browsers - w.writeCDATA(n.innerHTML.replace(/|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures - hc = false; - break; - } - - // Padd empty nodes with a   - if (ru.padd) { - // If it has only one bogus child, padd it anyway workaround for
    bug - if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) { - if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus')) - w.writeText('\u00a0'); - } else if (!hc) - w.writeText('\u00a0'); // No children then padd it - } - - break; - - case 3: // Text - // Check if valid child - if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text')) - return; - - return w.writeText(n.nodeValue); - - case 4: // CDATA - return w.writeCDATA(n.nodeValue); - - case 8: // Comment - return w.writeComment(n.nodeValue); - } - } else if (n.nodeType == 1) - hc = n.hasChildNodes(); - - if (hc && !closed) { - cn = n.firstChild; - - while (cn) { - t._serializeNode(cn); - t.elementName = nn; - cn = cn.nextSibling; - } - } - - // Write element end - if (!iv) { - if (!closed) - w.writeFullEndElement(); - else - w.writeEndElement(); - } - }, - - _protect : function(o) { - var t = this; - - o.items = o.items || []; - - function enc(s) { - return s.replace(/[\r\n\\]/g, function(c) { - if (c === '\n') - return '\\n'; - else if (c === '\\') - return '\\\\'; - - return '\\r'; - }); - }; - - function dec(s) { - return s.replace(/\\[\\rn]/g, function(c) { - if (c === '\\n') - return '\n'; - else if (c === '\\\\') - return '\\'; - - return '\r'; - }); - }; - - each(o.patterns, function(p) { - o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) { - b = dec(b); - - if (p.encode) - b = t._encode(b); - - o.items.push(b); - return a + '' + c; - })); - }); - - return o; - }, - - _unprotect : function(h, o) { - h = h.replace(/\)/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); + }; + + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; + + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); + + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); + + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); + + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); + + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; + + while (i--) { + node = nodes[i]; + parentNode = node.parent; + + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } + } + }); + } + + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + // Return public methods + return { + /** + * Schema instance that was used to when the Serializer was constructed. + * + * @field {tinymce.html.Schema} schema + */ + schema : schema, + + /** + * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name + * and then execute the callback ones it has finished parsing the document. + * + * @example + * parser.addNodeFilter('p,h1', function(nodes, name) { + * for (var i = 0; i < nodes.length; i++) { + * console.log(nodes[i].name); + * } + * }); + * @method addNodeFilter + * @method {String} name Comma separated list of nodes to collect. + * @param {function} callback Callback function to execute once it has collected nodes. + */ + addNodeFilter : htmlParser.addNodeFilter, + + /** + * Adds a attribute filter function to the parser used by the serializer, the parser will collect nodes that has the specified attributes + * and then execute the callback ones it has finished parsing the document. + * + * @example + * parser.addAttributeFilter('src,href', function(nodes, name) { + * for (var i = 0; i < nodes.length; i++) { + * console.log(nodes[i].name); + * } + * }); + * @method addAttributeFilter + * @method {String} name Comma separated list of nodes to collect. + * @param {function} callback Callback function to execute once it has collected nodes. + */ + addAttributeFilter : htmlParser.addAttributeFilter, + + /** + * Fires when the Serializer does a preProcess on the contents. + * + * @event onPreProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + * @option {Node} node DOM node for the item being serialized. + * @option {String} format The specified output format normally "html". + * @option {Boolean} get Is true if the process is on a getContent operation. + * @option {Boolean} set Is true if the process is on a setContent operation. + * @option {Boolean} cleanup Is true if the process is on a cleanup operation. + */ + onPreProcess : onPreProcess, + + /** + * Fires when the Serializer does a postProcess on the contents. + * + * @event onPostProcess + * @param {tinymce.Editor} sender Editor instance. + * @param {Object} obj PreProcess object. + */ + onPostProcess : onPostProcess, + + /** + * Serializes the specified browser DOM node into a HTML string. + * + * @method serialize + * @param {DOMNode} node DOM node to serialize. + * @param {Object} args Arguments option that gets passed to event handlers. + */ + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; + + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); + + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; + + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } + + args = args || {}; + args.format = args.format || 'html'; + + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } + + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); + + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args) + ); + + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF|\u200B/g, ''); + + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); + + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; + + args.node = null; + + return args.content; + }, + + /** + * Adds valid elements rules to the serializers schema instance this enables you to specify things + * like what elements should be outputted and what attributes specific elements might have. + * Consult the Wiki for more details on this format. + * + * @method addRules + * @param {String} rules Valid elements rules string to add to schema. + */ + addRules : function(rules) { + schema.addValidElements(rules); + }, + + /** + * Sets the valid elements rules to the serializers schema instance this enables you to specify things + * like what elements should be outputted and what attributes specific elements might have. + * Consult the Wiki for more details on this format. + * + * @method setRules + * @param {String} rules Valid elements rules string. + */ + setRules : function(rules) { + schema.setValidElements(rules); + } + }; + }; +})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/dom/Sizzle.js b/js/tiny_mce/classes/dom/Sizzle.js index 0f16081f..5312fa70 100644 --- a/js/tiny_mce/classes/dom/Sizzle.js +++ b/js/tiny_mce/classes/dom/Sizzle.js @@ -8,14 +8,26 @@ */ (function(){ -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g, +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, - hasDuplicate = false; + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); var Sizzle = function(selector, context, results, seed) { results = results || []; - var origContext = context = context || document; + context = context || document; + + var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; @@ -25,19 +37,25 @@ var Sizzle = function(selector, context, results, seed) { return results; } - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context); + var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), + soFar = selector, ret, cur, pop, i; // Reset the position of the chunker regexp (start from head) - chunker.lastIndex = 0; - - while ( (m = chunker.exec(selector)) !== null ) { - parts.push( m[1] ); + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); - if ( m[2] ) { - extra = RegExp.rightContext; - break; + if ( m[2] ) { + extra = m[3]; + break; + } } - } + } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { @@ -50,9 +68,10 @@ var Sizzle = function(selector, context, results, seed) { while ( parts.length ) { selector = parts.shift(); - if ( Expr.relative[ selector ] ) + if ( Expr.relative[ selector ] ) { selector += parts.shift(); - + } + set = posProcess( selector, set ); } } @@ -61,12 +80,12 @@ var Sizzle = function(selector, context, results, seed) { // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); + ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { - var ret = seed ? + ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; @@ -78,7 +97,8 @@ var Sizzle = function(selector, context, results, seed) { } while ( parts.length ) { - var cur = parts.pop(), pop = cur; + cur = parts.pop(); + pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; @@ -102,20 +122,20 @@ var Sizzle = function(selector, context, results, seed) { } if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); + Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { - for ( var i = 0; checkSet[i] != null; i++ ) { + for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } @@ -135,7 +155,7 @@ var Sizzle = function(selector, context, results, seed) { Sizzle.uniqueSort = function(results){ if ( sortOrder ) { - hasDuplicate = false; + hasDuplicate = baseHasDuplicate; results.sort(sortOrder); if ( hasDuplicate ) { @@ -146,6 +166,8 @@ Sizzle.uniqueSort = function(results){ } } } + + return results; }; Sizzle.matches = function(expr, set){ @@ -153,7 +175,7 @@ Sizzle.matches = function(expr, set){ }; Sizzle.find = function(expr, context, isXML){ - var set, match; + var set; if ( !expr ) { return []; @@ -162,8 +184,9 @@ Sizzle.find = function(expr, context, isXML){ for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match; - if ( (match = Expr.match[ type ].exec( expr )) ) { - var left = RegExp.leftContext; + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); @@ -185,15 +208,21 @@ Sizzle.find = function(expr, context, isXML){ Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; anyFound = false; - if ( curLoop == result ) { + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { result = []; } @@ -244,9 +273,9 @@ Sizzle.filter = function(expr, set, inplace, not){ } // Improper expression - if ( expr == old ) { + if ( expr === old ) { if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; + Sizzle.error( expr ); } else { break; } @@ -258,18 +287,23 @@ Sizzle.filter = function(expr, set, inplace, not){ return curLoop; }; +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { - ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, + leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" @@ -280,20 +314,20 @@ var Expr = Sizzle.selectors = { } }, relative: { - "+": function(checkSet, part, isXML){ + "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !/\W/.test(part), isPartStrNotTag = isPartStr && !isTag; - if ( isTag && !isXML ) { - part = part.toUpperCase(); + if ( isTag ) { + part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } @@ -303,22 +337,23 @@ var Expr = Sizzle.selectors = { Sizzle.filter( part, checkSet, true ); } }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); + part = part.toLowerCase(); - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; + for ( ; i < l; i++ ) { + elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; + for ( ; i < l; i++ ) { + elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : @@ -332,20 +367,22 @@ var Expr = Sizzle.selectors = { } }, "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; + var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; checkFn = dirNodeCheck; } checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); }, "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; + var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( typeof part === "string" && !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; checkFn = dirNodeCheck; } @@ -359,7 +396,7 @@ var Expr = Sizzle.selectors = { return m ? [m] : []; } }, - NAME: function(match, context, isXML){ + NAME: function(match, context){ if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName(match[1]); @@ -386,9 +423,10 @@ var Expr = Sizzle.selectors = { for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { result.push( elem ); + } } else if ( inplace ) { curLoop[i] = false; } @@ -401,14 +439,13 @@ var Expr = Sizzle.selectors = { return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + return match[1].toLowerCase(); }, CHILD: function(match){ - if ( match[1] == "nth" ) { + if ( match[1] === "nth" ) { // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative @@ -437,7 +474,7 @@ var Expr = Sizzle.selectors = { PSEUDO: function(match, curLoop, inplace, result, not){ if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one - if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); @@ -483,7 +520,7 @@ var Expr = Sizzle.selectors = { return !!Sizzle( match[3], elem ).length; }, header: function(elem){ - return /h\d/i.test( elem.nodeName ); + return (/h\d/i).test( elem.nodeName ); }, text: function(elem){ return "text" === elem.type; @@ -510,10 +547,10 @@ var Expr = Sizzle.selectors = { return "reset" === elem.type; }, button: function(elem){ - return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; }, input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); + return (/input|select|textarea|button/i).test(elem.nodeName); } }, setFilters: { @@ -536,10 +573,10 @@ var Expr = Sizzle.selectors = { return i > match[3] - 0; }, nth: function(elem, i, match){ - return match[3] - 0 == i; + return match[3] - 0 === i; }, eq: function(elem, i, match){ - return match[3] - 0 == i; + return match[3] - 0 === i; } }, filter: { @@ -549,17 +586,19 @@ var Expr = Sizzle.selectors = { if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { return false; } } return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); } }, CHILD: function(elem, match){ @@ -567,20 +606,26 @@ var Expr = Sizzle.selectors = { switch (type) { case 'only': case 'first': - while (node = node.previousSibling) { - if ( node.nodeType === 1 ) return false; + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; } - if ( type == 'first') return true; node = elem; case 'last': - while (node = node.nextSibling) { - if ( node.nodeType === 1 ) return false; + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } } return true; case 'nth': var first = match[2], last = match[3]; - if ( first == 1 && last == 0 ) { + if ( first === 1 && last === 0 ) { return true; } @@ -598,10 +643,10 @@ var Expr = Sizzle.selectors = { } var diff = elem.nodeIndex - last; - if ( first == 0 ) { - return diff == 0; + if ( first === 0 ) { + return diff === 0; } else { - return ( diff % first == 0 && diff / first >= 0 ); + return ( diff % first === 0 && diff / first >= 0 ); } } }, @@ -609,7 +654,7 @@ var Expr = Sizzle.selectors = { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, CLASS: function(elem, match){ return (" " + (elem.className || elem.getAttribute("class")) + " ") @@ -637,7 +682,7 @@ var Expr = Sizzle.selectors = { !check ? value && result !== false : type === "!=" ? - value != check : + value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? @@ -656,14 +701,18 @@ var Expr = Sizzle.selectors = { } }; -var origPOS = Expr.match.POS; +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } var makeArray = function(array, results) { - array = Array.prototype.slice.call( array ); + array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); @@ -675,23 +724,25 @@ var makeArray = function(array, results) { // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) try { - Array.prototype.slice.call( document.documentElement.childNodes ); + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch(e){ makeArray = function(array, results) { - var ret = results || []; + var ret = results || [], i = 0; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { + for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { - for ( var i = 0; array[i]; i++ ) { + for ( ; array[i]; i++ ) { ret.push( array[i] ); } } @@ -705,6 +756,13 @@ var sortOrder; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; if ( ret === 0 ) { hasDuplicate = true; @@ -713,6 +771,13 @@ if ( document.documentElement.compareDocumentPosition ) { }; } else if ( "sourceIndex" in document.documentElement ) { sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + var ret = a.sourceIndex - b.sourceIndex; if ( ret === 0 ) { hasDuplicate = true; @@ -721,6 +786,13 @@ if ( document.documentElement.compareDocumentPosition ) { }; } else if ( document.createRange ) { sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); aRange.setStart(a, 0); aRange.setEnd(a, 0); @@ -734,12 +806,32 @@ if ( document.documentElement.compareDocumentPosition ) { }; } +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), - id = "script" + (new Date).getTime(); + id = "script" + (new Date()).getTime(); form.innerHTML = ""; // Inject it into the root element, check its status, and remove it quickly @@ -748,7 +840,7 @@ if ( document.documentElement.compareDocumentPosition ) { // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) - if ( !!document.getElementById( id ) ) { + if ( document.getElementById( id ) ) { Expr.find.ID = function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); @@ -763,6 +855,7 @@ if ( document.documentElement.compareDocumentPosition ) { } root.removeChild( form ); + root = form = null; // release memory in IE })(); (function(){ @@ -803,68 +896,75 @@ if ( document.documentElement.compareDocumentPosition ) { return elem.getAttribute("href", 2); }; } + + div = null; // release memory in IE })(); -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } - return oldSizzle(query, context, extra, seed); - }; + return oldSizzle(query, context, extra, seed); + }; - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } -})(); + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + div = null; // release memory in IE + })(); +} + +(function(){ var div = document.createElement("div"); + div.innerHTML = "
    "; // Opera can't find a second classname (in 9.6) - if ( div.getElementsByClassName("e").length === 0 ) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; + } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; - if ( div.getElementsByClassName("e").length === 1 ) + if ( div.getElementsByClassName("e").length === 1 ) { return; - + } + Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context, isXML) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; + + div = null; // release memory in IE })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } elem = elem[dir]; var match = false; @@ -879,7 +979,7 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem.sizset = i; } - if ( elem.nodeName === cur ) { + if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } @@ -893,14 +993,9 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } elem = elem[dir]; var match = false; @@ -935,15 +1030,17 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } } -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); } : function(a, b){ return a !== b && (a.contains ? a.contains(b) : true); }; -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +Sizzle.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function(selector, context){ diff --git a/js/tiny_mce/classes/dom/TreeWalker.js b/js/tiny_mce/classes/dom/TreeWalker.js index c7fc0604..ca92df4a 100644 --- a/js/tiny_mce/classes/dom/TreeWalker.js +++ b/js/tiny_mce/classes/dom/TreeWalker.js @@ -1,64 +1,64 @@ -/** - * TreeWalker.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -tinymce.dom.TreeWalker = function(start_node, root_node) { - var node = start_node; - - function findSibling(node, start_name, sibling_name, shallow) { - var sibling, parent; - - if (node) { - // Walk into nodes if it has a start - if (!shallow && node[start_name]) - return node[start_name]; - - // Return the sibling if it has one - if (node != root_node) { - sibling = node[sibling_name]; - if (sibling) - return sibling; - - // Walk up the parents to look for siblings - for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { - sibling = parent[sibling_name]; - if (sibling) - return sibling; - } - } - } - }; - - /** - * Returns the current node. - * - * @return {Node} Current node where the walker is. - */ - this.current = function() { - return node; - }; - - /** - * Walks to the next node in tree. - * - * @return {Node} Current node where the walker is after moving to the next node. - */ - this.next = function(shallow) { - return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); - }; - - /** - * Walks to the previous node in tree. - * - * @return {Node} Current node where the walker is after moving to the previous node. - */ - this.prev = function(shallow) { - return (node = findSibling(node, 'lastChild', 'lastSibling', shallow)); - }; -}; +/** + * TreeWalker.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinymce.dom.TreeWalker = function(start_node, root_node) { + var node = start_node; + + function findSibling(node, start_name, sibling_name, shallow) { + var sibling, parent; + + if (node) { + // Walk into nodes if it has a start + if (!shallow && node[start_name]) + return node[start_name]; + + // Return the sibling if it has one + if (node != root_node) { + sibling = node[sibling_name]; + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { + sibling = parent[sibling_name]; + if (sibling) + return sibling; + } + } + } + }; + + /** + * Returns the current node. + * + * @return {Node} Current node where the walker is. + */ + this.current = function() { + return node; + }; + + /** + * Walks to the next node in tree. + * + * @return {Node} Current node where the walker is after moving to the next node. + */ + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; + + /** + * Walks to the previous node in tree. + * + * @return {Node} Current node where the walker is after moving to the previous node. + */ + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); + }; +}; diff --git a/js/tiny_mce/classes/dom/TridentSelection.js b/js/tiny_mce/classes/dom/TridentSelection.js index a866d692..6fa231dc 100644 --- a/js/tiny_mce/classes/dom/TridentSelection.js +++ b/js/tiny_mce/classes/dom/TridentSelection.js @@ -1,370 +1,445 @@ -/** - * TridentSelection.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - function Selection(selection) { - var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; - - // Compares two IE specific ranges to see if they are different - // this method is useful when invalidating the cached selection range - function compareRanges(rng1, rng2) { - if (rng1 && rng2) { - // Both are control ranges and the selected element matches - if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) - return TRUE; - - // Both are text ranges and the range matches - if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { - // IE will say that the range is equal then produce an invalid argument exception - // if you perform specific operations in a keyup event. For example Ctrl+Del. - // This hack will invalidate the range cache if the exception occurs - try { - // Try accessing nextSibling will producer an invalid argument some times - range.startContainer.nextSibling; - return TRUE; - } catch (ex) { - // Ignore - } - } - } - - return FALSE; - }; - - // Returns a W3C DOM compatible range object by using the IE Range API - function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged; - - // If selection is outside the current document just return an empty range - element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); - if (element.ownerDocument != dom.doc) - return domRange; - - // Handle control selection or text selection of a image - if (ieRange.item || !element.hasChildNodes()) { - domRange.setStart(element.parentNode, dom.nodeIndex(element)); - domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); - - return domRange; - } - - // Duplicare IE selection range and check if the range is collapsed - ieRange2 = ieRange.duplicate(); - collapsed = selection.isCollapsed(); - - // Insert invisible start marker - ieRange.collapse(); - ieRange.pasteHTML(''); - - // Insert invisible end marker - if (!collapsed) { - ieRange2.collapse(FALSE); - ieRange2.pasteHTML(''); - } - - // Sets the end point of the range by looking for the marker - // This method also merges the text nodes it splits so that - // the DOM doesn't get fragmented. - function setEndPoint(start) { - var container, offset, marker, sibling; - - // Look for endpoint marker - marker = dom.get('_mce_' + (start ? 'start' : 'end')); - sibling = marker.previousSibling; - - // Is marker after a text node - if (sibling && sibling.nodeType == 3) { - // Get container node and calc offset - container = sibling; - offset = container.nodeValue.length; - dom.remove(marker); - - // Merge text nodes to reduce DOM fragmentation - sibling = container.nextSibling; - if (sibling && sibling.nodeType == 3) { - isMerged = TRUE; - container.appendData(sibling.nodeValue); - dom.remove(sibling); - } - } else { - sibling = marker.nextSibling; - - // Is marker before a text node - if (sibling && sibling.nodeType == 3) { - container = sibling; - offset = 0; - } else { - // Is marker before an element - if (sibling) - offset = dom.nodeIndex(sibling) - 1; - else - offset = dom.nodeIndex(marker); - - container = marker.parentNode; - } - - dom.remove(marker); - } - - // Set start of range - if (start) - domRange.setStart(container, offset); - - // Set end of range or automatically if it's collapsed to increase performance - if (!start || collapsed) - domRange.setEnd(container, offset); - }; - - // Set start of range - setEndPoint(TRUE); - - // Set end of range if needed - if (!collapsed) - setEndPoint(FALSE); - - // Restore selection if the range contents was merged - // since the selection was then moved since the text nodes got changed - if (isMerged) - t.addRange(domRange); - - return domRange; - }; - - this.addRange = function(rng) { - var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd; - - this.destroy(); - - // Setup some shorter versions - sc = rng.startContainer; - so = rng.startOffset; - ec = rng.endContainer; - eo = rng.endOffset; - ieRng = body.createTextRange(); - - // If document selection move caret to first node in document - if (sc == doc || ec == doc) { - ieRng = body.createTextRange(); - ieRng.collapse(); - ieRng.select(); - return; - } - - // If child index resolve it - if (sc.nodeType == 1 && sc.hasChildNodes()) { - lastIndex = sc.childNodes.length - 1; - - // Index is higher that the child count then we need to jump over the start container - if (so > lastIndex) { - skipStart = 1; - sc = sc.childNodes[lastIndex]; - } else - sc = sc.childNodes[so]; - - // Child was text node then move offset to start of it - if (sc.nodeType == 3) - so = 0; - } - - // If child index resolve it - if (ec.nodeType == 1 && ec.hasChildNodes()) { - lastIndex = ec.childNodes.length - 1; - - if (eo == 0) { - skipEnd = 1; - ec = ec.childNodes[0]; - } else { - ec = ec.childNodes[Math.min(lastIndex, eo - 1)]; - - // Child was text node then move offset to end of text node - if (ec.nodeType == 3) - eo = ec.nodeValue.length; - } - } - - // Single element selection - if (sc == ec && sc.nodeType == 1) { - // Make control selection for some elements - if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) { - ieRng = body.createControlRange(); - ieRng.addElement(sc); - } else { - ieRng = body.createTextRange(); - - // Padd empty elements with invisible character - if (!sc.hasChildNodes() && sc.canHaveHTML) - sc.innerHTML = invisibleChar; - - // Select element contents - ieRng.moveToElementText(sc); - - // If it's only containing a padding remove it so the caret remains - if (sc.innerHTML == invisibleChar) { - ieRng.collapse(TRUE); - sc.removeChild(sc.firstChild); - } - } - - if (so == eo) - ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1); - - ieRng.select(); - ieRng.scrollIntoView(); - return; - } - - // Create range and marker - ieRng = body.createTextRange(); - marker = doc.createElement('span'); - marker.innerHTML = ' '; - - // Set start of range to startContainer/startOffset - if (sc.nodeType == 3) { - // Insert marker after/before startContainer - if (skipStart) - dom.insertAfter(marker, sc); - else - sc.parentNode.insertBefore(marker, sc); - - // Select marker the caret to offset position - ieRng.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng.move('character', so); - } else { - ieRng.moveToElementText(sc); - - if (skipStart) - ieRng.collapse(FALSE); - } - - // If same text container then we can do a more simple move - if (sc == ec && sc.nodeType == 3) { - ieRng.moveEnd('character', eo - so); - ieRng.select(); - ieRng.scrollIntoView(); - return; - } - - // Set end of range to endContainer/endOffset - ieRng2 = body.createTextRange(); - if (ec.nodeType == 3) { - // Insert marker after/before startContainer - ec.parentNode.insertBefore(marker, ec); - - // Move selection to end marker and move caret to end offset - ieRng2.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng2.move('character', eo); - ieRng.setEndPoint('EndToStart', ieRng2); - } else { - ieRng2.moveToElementText(ec); - ieRng2.collapse(!!skipEnd); - ieRng.setEndPoint('EndToEnd', ieRng2); - } - - ieRng.select(); - ieRng.scrollIntoView(); - }; - - this.getRangeAt = function() { - // Setup new range if the cache is empty - if (!range || !compareRanges(lastIERng, selection.getRng())) { - range = getRange(); - - // Store away text range for next call - lastIERng = selection.getRng(); - } - - // Return cached range - return range; - }; - - this.destroy = function() { - // Destroy cached range and last IE range to avoid memory leaks - lastIERng = range = null; - }; - - // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode - if (selection.dom.boxModel) { - (function() { - var doc = dom.doc, body = doc.body, started, startRng; - - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = TRUE; - - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); - - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } - - return rng; - }; - - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; - - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); - - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) - pointRng.setEndPoint('StartToStart', startRng); - else - pointRng.setEndPoint('EndToEnd', startRng); - - pointRng.select(); - } - } else - endSelection(); - } - - // Removes listeners - function endSelection() { - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - started = 0; - }; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) - endSelection(); - - started = 1; - - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); - - startRng.select(); - } - } - }); - })(); - } - }; - - // Expose the selection object - tinymce.dom.TridentSelection = Selection; -})(); +/** + * TridentSelection.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng); + + // Fix for edge case:
    ..
    ab|c
    + if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) { + checkRng = rng.duplicate(); + checkRng.collapse(start); + + offset = -1; + while (parent == checkRng.parentElement()) { + if (checkRng.move('character', -1) == 0) + break; + + offset++; + } + } + + offset = offset || checkRng.text.replace('\r\n', ' ').length; + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng); + + // Get the length of the text to find where the endpoint is relative to it's container + offset = checkRng.text.replace('\r\n', ' ').length; + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var node, parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root && parent.nodeType !== 9) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else { + // Empty node selection for example
    |
    + marker = doc.createTextNode('\uFEFF'); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { + if (startOffset == endOffset - 1) { + try { + ctrlRng = body.createControlRange(); + ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.select(); + return; + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); diff --git a/js/tiny_mce/classes/firebug/FIREBUG.LICENSE b/js/tiny_mce/classes/firebug/FIREBUG.LICENSE new file mode 100644 index 00000000..8b9c44ab --- /dev/null +++ b/js/tiny_mce/classes/firebug/FIREBUG.LICENSE @@ -0,0 +1,30 @@ +Software License Agreement (BSD License) + +Copyright (c) 2007, Parakey Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Parakey Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Parakey Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/js/tiny_mce/classes/html/DomParser.js b/js/tiny_mce/classes/html/DomParser.js new file mode 100644 index 00000000..7024d7ce --- /dev/null +++ b/js/tiny_mce/classes/html/DomParser.js @@ -0,0 +1,577 @@ +/** + * DomParser.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var Node = tinymce.html.Node; + + /** + * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make + * sure that the node tree is valid according to the specified schema. So for example:

    a

    b

    c

    will become

    a

    b

    c

    + * + * @example + * var parser = new tinymce.html.DomParser({validate: true}, schema); + * var rootNode = parser.parse('

    content

    '); + * + * @class tinymce.html.DomParser + * @version 3.4 + */ + + /** + * Constructs a new DomParser instance. + * + * @constructor + * @method DomParser + * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. + * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. + */ + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); + + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); + + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; + + // Already removed + if (!node.parent) + continue; + + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); + + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); + + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); + + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; + + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } + + currentNode = tempNode; + } + + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } + + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } + + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } + + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } + + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; + + /** + * Runs the specified node though the element and attributes filters. + * + * @param {tinymce.html.Node} Node the node to run filters on. + * @return {tinymce.html.Node} The passed in node. + */ + self.filterNode = function(node) { + var i, name, list; + + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; + + if (name in node.attributes.map) { + list = matchedAttributes[name]; + + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } + + return node; + }; + + /** + * Adds a node filter function to the parser, the parser will collect the specified nodes by name + * and then execute the callback ones it has finished parsing the document. + * + * @example + * parser.addNodeFilter('p,h1', function(nodes, name) { + * for (var i = 0; i < nodes.length; i++) { + * console.log(nodes[i].name); + * } + * }); + * @method addNodeFilter + * @method {String} name Comma separated list of nodes to collect. + * @param {function} callback Callback function to execute once it has collected nodes. + */ + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; + + if (!list) + nodeFilters[name] = list = []; + + list.push(callback); + }); + }; + + /** + * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes + * and then execute the callback ones it has finished parsing the document. + * + * @example + * parser.addAttributeFilter('src,href', function(nodes, name) { + * for (var i = 0; i < nodes.length; i++) { + * console.log(nodes[i].name); + * } + * }); + * @method addAttributeFilter + * @method {String} name Comma separated list of nodes to collect. + * @param {function} callback Callback function to execute once it has collected nodes. + */ + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } + + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; + + /** + * Parses the specified HTML string into a DOM like node tree and returns the result. + * + * @example + * var rootNode = new DomParser({...}).parse('text'); + * @method parse + * @param {String} html Html string to sax parse. + * @param {Object} args Optional args object that gets passed to all filter functions. + * @return {tinymce.html.Node} Root node containing the tree. + */ + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], + endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } + + node = next; + }; + }; + + function createNode(name, type) { + var node = new Node(name, type), list; + + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + return node; + }; + + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; + + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; + + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results + + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, + + text: function(text, raw) { + var textNode; + + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); + + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } + + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, + + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, + + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, + + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, + + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; + + node.append(newNode); + + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); + + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; + + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; + + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } + + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + } + }, + + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!whiteSpaceElements[node.name]) { + // Trim whitespace at beginning of block + for (textNode = node.firstChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } + + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } + + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } + + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like
    + if (!node.attributes.map.name) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } + + node = node.parent; + } + } + }, schema); + + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; + } + + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); + + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } + + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; + + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } + + return rootNode; + }; + + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = schema.getBlockElements(), + nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the right of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } + + prev = prev.prev; + } + + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } + } + }); + } + } +})(tinymce); diff --git a/js/tiny_mce/classes/html/Entities.js b/js/tiny_mce/classes/html/Entities.js new file mode 100644 index 00000000..071d6966 --- /dev/null +++ b/js/tiny_mce/classes/html/Entities.js @@ -0,0 +1,253 @@ +/** + * Entities.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#x|#)?([\w]+);/g, + asciiMap = { + 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", + 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", + 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", + 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", + 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" + }; + + // Raw entities + baseEntities = { + '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; + + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" + }; + + // Decodes text by using the browser + function nativeDecode(text) { + var elm; + + elm = document.createElement("div"); + elm.innerHTML = text; + + return elm.textContent || elm.innerText || text; + }; + + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; + + if (items) { + items = items.split(','); + radix = radix || 10; + + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } + + return lookup; + } + }; + + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + namedEntities = buildEntitiesLookup( + '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro' + , 32); + + tinymce.html = tinymce.html || {}; + + /** + * Entity encoder class. + * + * @class tinymce.html.SaxParser + * @static + * @version 3.4 + */ + tinymce.html.Entities = { + /** + * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded. + * + * @method encodeRaw + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @return {String} Entity encoded text. + */ + encodeRaw : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + /** + * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents + * since it doesn't know if the context is within a attribute or text node. This was added for compatibility + * and is exposed as the DOMUtils.encode function. + * + * @method encodeAllRaw + * @param {String} text Text to encode. + * @return {String} Entity encoded text. + */ + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + /** + * Encodes the specified string using numeric entities. The core entities will be encoded as named ones but all non lower ascii characters + * will be encoded into numeric entities. + * + * @method encodeNumeric + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @return {String} Entity encoded text. + */ + encodeNumeric : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) + return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; + + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, + + /** + * Encodes the specified string using named entities. The core entities will be encoded as named ones but all non lower ascii characters + * will be encoded into named entities. + * + * @method encodeNamed + * @param {String} text Text to encode. + * @param {Boolean} attr Optional flag to specify if the text is attribute contents. + * @param {Object} entities Optional parameter with entities to use. + * @return {String} Entity encoded text. + */ + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; + + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); + }, + + /** + * Returns an encode function based on the name(s) and it's optional entities. + * + * @method getEncodeFunc + * @param {String} name Comma separated list of encoders for example named,numeric. + * @param {String} entities Optional parameter with entities to use instead of the built in set. + * @return {function} Encode function to be used. + */ + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; + + entities = buildEntitiesLookup(entities) || namedEntities; + + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + }; + + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; + + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); + + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; + + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; + + return Entities.encodeNamed; + } + + // Numeric + if (name.numeric) + return Entities.encodeNumeric; + + // Raw encoder + return Entities.encodeRaw; + }, + + /** + * Decodes the specified string, this will replace entities with raw UTF characters. + * + * @param {String} text Text to entity decode. + * @return {String} Entity decoded string. + */ + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value, numeric.length === 2 ? 16 : 10); + + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; + + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); + } + + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); diff --git a/js/tiny_mce/classes/html/Node.js b/js/tiny_mce/classes/html/Node.js new file mode 100644 index 00000000..41cc0d98 --- /dev/null +++ b/js/tiny_mce/classes/html/Node.js @@ -0,0 +1,474 @@ +/** + * Node.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; + + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; + + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; + + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; + + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; + + if (sibling) + return sibling; + } + } + }; + + /** + * This class is a minimalistic implementation of a DOM like node used by the DomParser class. + * + * @example + * var node = new tinymce.html.Node('strong', 1); + * someRoot.append(node); + * + * @class tinymce.html.Node + * @version 3.4 + */ + + /** + * Constructs a new Node instance. + * + * @constructor + * @method Node + * @param {String} name Name of the node type. + * @param {Number} type Numeric type representing the node. + */ + function Node(name, type) { + this.name = name; + this.type = type; + + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } + + tinymce.extend(Node.prototype, { + /** + * Replaces the current node with the specified one. + * + * @example + * someNode.replace(someNewNode); + * + * @method replace + * @param {tinymce.html.Node} node Node to replace the current node with. + * @return {tinymce.html.Node} The old node that got replaced. + */ + replace : function(node) { + var self = this; + + if (node.parent) + node.remove(); + + self.insert(node, self); + self.remove(); + + return self; + }, + + /** + * Gets/sets or removes an attribute by name. + * + * @example + * someNode.attr("name", "value"); // Sets an attribute + * console.log(someNode.attr("name")); // Gets an attribute + * someNode.attr("name", null); // Removes an attribute + * + * @method attr + * @param {String} name Attribute name to set or get. + * @param {String} value Optional value to set. + * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. + */ + attr : function(name, value) { + var self = this, attrs, i, undef; + + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); + + return self; + } + + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; + + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } + } + + return self; + } + + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; + } + } + } else + attrs.push({name: name, value: value}); + + attrs.map[name] = value; + + return self; + } else { + return attrs.map[name]; + } + } + }, + + /** + * Does a shallow clones the node into a new node. It will also exclude id attributes since + * there should only be one id per document. + * + * @example + * var clonedNode = node.clone(); + * + * @method clone + * @return {tinymce.html.Node} New copy of the original node. + */ + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; + + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; + + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; + + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } + + clone.attributes = cloneAttrs; + } + + clone.value = self.value; + clone.shortEnded = self.shortEnded; + + return clone; + }, + + /** + * Wraps the node in in another node. + * + * @example + * node.wrap(wrapperNode); + * + * @method wrap + */ + wrap : function(wrapper) { + var self = this; + + self.parent.insert(wrapper, self); + wrapper.append(self); + + return self; + }, + + /** + * Unwraps the node in other words it removes the node but keeps the children. + * + * @example + * node.unwrap(); + * + * @method unwrap + */ + unwrap : function() { + var self = this, node, next; + + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } + + self.remove(); + }, + + /** + * Removes the node from it's parent. + * + * @example + * node.remove(); + * + * @method remove + * @return {tinymce.html.Node} Current node that got removed. + */ + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; + + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; + + if (next) + next.prev = null; + } else { + prev.next = next; + } + + if (parent.lastChild === self) { + parent.lastChild = prev; + + if (prev) + prev.next = null; + } else { + next.prev = prev; + } + + self.parent = self.next = self.prev = null; + } + + return self; + }, + + /** + * Appends a new node as a child of the current node. + * + * @example + * node.append(someNode); + * + * @method append + * @param {tinymce.html.Node} node Node to append as a child of the current one. + * @return {tinymce.html.Node} The node that got appended. + */ + append : function(node) { + var self = this, last; + + if (node.parent) + node.remove(); + + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; + + node.parent = self; + + return node; + }, + + /** + * Inserts a node at a specific position as a child of the current node. + * + * @example + * parentNode.insert(newChildNode, oldChildNode); + * + * @method insert + * @param {tinymce.html.Node} node Node to insert as a child of the current node. + * @param {tinymce.html.Node} ref_node Reference node to set node before/after. + * @param {Boolean} before Optional state to insert the node before the reference node. + * @return {tinymce.html.Node} The node that got inserted. + */ + insert : function(node, ref_node, before) { + var parent; + + if (node.parent) + node.remove(); + + parent = ref_node.parent || this; + + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; + + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; + + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } + + node.parent = parent; + + return node; + }, + + /** + * Get all children by name. + * + * @method getAll + * @param {String} name Name of the child nodes to collect. + * @return {Array} Array with child nodes matchin the specified name. + */ + getAll : function(name) { + var self = this, node, collection = []; + + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); + } + + return collection; + }, + + /** + * Removes all children of the current node. + * + * @method empty + * @return {tinymce.html.Node} The current node that got cleared. + */ + empty : function() { + var self = this, nodes, i, node; + + // Remove all children + if (self.firstChild) { + nodes = []; + + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); + + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } + + self.firstChild = self.lastChild = null; + + return self; + }, + + /** + * Returns true/false if the node is to be considered empty or not. + * + * @example + * node.isEmpty({img : true}); + * @method isEmpty + * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. + * @return {Boolean} true/false if the node is empty or not. + */ + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; + + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; + + // Keep empty elements like + if (elements[node.name]) + return false; + + // Keep elements with data attributes or name attribute like
    + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } + } + + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } + + return true; + }, + + /** + * Walks to the next or previous node and returns that node or null if it wasn't found. + * + * @method walk + * @param {Boolean} prev Optional previous node state defaults to false. + * @return {tinymce.html.Node} Node that is next to or previous of the current node. + */ + walk : function(prev) { + return walk(this, null, prev); + } + }); + + tinymce.extend(Node, { + /** + * Creates a node of a specific type. + * + * @static + * @method create + * @param {String} name Name of the node type to create for example "b" or "#text". + * @param {Object} attrs Name/value collection of attributes that will be applied to elements. + */ + create : function(name, attrs) { + var node, attrName; + + // Create node + node = new Node(name, typeLookup[name] || 1); + + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } + + return node; + } + }); + + tinymce.html.Node = Node; +})(tinymce); diff --git a/js/tiny_mce/classes/html/SaxParser.js b/js/tiny_mce/classes/html/SaxParser.js new file mode 100644 index 00000000..b5bb716c --- /dev/null +++ b/js/tiny_mce/classes/html/SaxParser.js @@ -0,0 +1,355 @@ +/** + * SaxParser.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will + * always execute the events in the right order for tag soup code like

    . It will also remove elements + * and attributes that doesn't fit the schema if the validate setting is enabled. + * + * @example + * var parser = new tinymce.html.SaxParser({ + * validate: true, + * + * comment: function(text) { + * console.log('Comment:', text); + * }, + * + * cdata: function(text) { + * console.log('CDATA:', text); + * }, + * + * text: function(text, raw) { + * console.log('Text:', text, 'Raw:', raw); + * }, + * + * start: function(name, attrs, empty) { + * console.log('Start:', name, attrs, empty); + * }, + * + * end: function(name) { + * console.log('End:', name); + * }, + * + * pi: function(name, text) { + * console.log('PI:', name, text); + * }, + * + * doctype: function(text) { + * console.log('DocType:', text); + * } + * }, schema); + * @class tinymce.html.SaxParser + * @version 3.4 + */ + + /** + * Constructs a new SaxParser instance. + * + * @constructor + * @method SaxParser + * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. + * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. + */ + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; + + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); + + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; + + // Add handler functions from settings and setup default handlers + tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { + if (name) + self[name] = settings[name] || noop; + }); + + /** + * Parses the specified HTML string and executes the callbacks for each item it finds. + * + * @example + * new SaxParser({...}).parse('text'); + * @method parse + * @param {String} html Html string to sax parse. + */ + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; + + function processEndTag(name) { + var pos, i; + + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } + + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; + + if (name.valid) + self.end(name.name); + } + + // Remove the open elements from the stack + stack.length = pos; + } + }; + + // Precompile RegExps and map objects + tokenRegExp = new RegExp('<(?:' + + '(?:!--([\\w\\W]*?)-->)|' + // Comment + '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA + '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE + '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI + '(?:\\/([^>]+)>)|' + // End element + '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/)>)' + // Start element + ')', 'g'); + + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; + specialElements = { + 'script' : /<\/script[^>]*>/gi, + 'style' : /<\/style[^>]*>/gi, + 'noscript' : /<\/noscript[^>]*>/gi + }; + + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + removeInternalElements = settings.remove_internals; + fixSelfClosing = settings.fix_self_closing; + isIE = tinymce.isIE; + invalidPrefixRegExp = /^:/; + + while (matches = tokenRegExp.exec(html)) { + // Text + if (index < matches.index) + self.text(decode(html.substr(index, matches.index - index))); + + if (value = matches[6]) { // End element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + processEndTag(value); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + isShortEnded = value in shortEndedElements; + + // Is self closing tag for example an
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } + + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element + + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; + + attrList = []; + attrList.map = {}; + + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; + + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute + + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; + + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } + + // No rule matched + if (i === -1) + attrRule = null; + } + + // No attribute rule found + if (!attrRule) + return; + + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } + + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } + + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; + + if (!(name in attrList.map)) { + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } + + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } + + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } + + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } + + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; + + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; + + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); + + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } + + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } + + index = matches.index + matches[0].length; + } + + // Text + if (index < html.length) + self.text(decode(html.substr(index))); + + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; + + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); diff --git a/js/tiny_mce/classes/html/Schema.js b/js/tiny_mce/classes/html/Schema.js new file mode 100644 index 00000000..f430c0dd --- /dev/null +++ b/js/tiny_mce/classes/html/Schema.js @@ -0,0 +1,663 @@ +/** + * Schema.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {}, + defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each; + + function split(str, delim) { + return str.split(delim || ','); + }; + + /** + * Unpacks the specified lookup and string data it will also parse it into an object + * map with sub object for it's children. This will later also include the attributes. + */ + function unpack(lookup, data) { + var key, elements = {}; + + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; + + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } + + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); + + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) + } + }); + + return elements; + }; + + // Build a lookup table for block elements both lowercase and uppercase + blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + + 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + + 'noscript,menu,isindex,samp,header,footer,article,section,hgroup'; + blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase())); + + // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size + transitional = unpack({ + Z : 'H|K|N|O|P', + Y : 'X|form|R|Q', + ZG : 'E|span|width|align|char|charoff|valign', + X : 'p|T|div|U|W|isindex|fieldset|table', + ZF : 'E|align|char|charoff|valign', + W : 'pre|hr|blockquote|address|center|noframes', + ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', + ZD : '[E][S]', + U : 'ul|ol|dl|menu|dir', + ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', + T : 'h1|h2|h3|h4|h5|h6', + ZB : 'X|S|Q', + S : 'R|P', + ZA : 'a|G|J|M|O|P', + R : 'a|H|K|N|O', + Q : 'noscript|P', + P : 'ins|del|script', + O : 'input|select|textarea|label|button', + N : 'M|L', + M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', + L : 'sub|sup', + K : 'J|I', + J : 'tt|i|b|u|s|strike', + I : 'big|small|font|basefont', + H : 'G|F', + G : 'br|span|bdo', + F : 'object|applet|img|map|iframe', + E : 'A|B|C', + D : 'accesskey|tabindex|onfocus|onblur', + C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : 'lang|xml:lang|dir', + A : 'id|class|style|title' + }, 'script[id|charset|type|language|src|defer|xml:space][]' + + 'style[B|id|type|media|title|xml:space][]' + + 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + + 'param[id|name|value|valuetype|type][]' + + 'p[E|align][#|S]' + + 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + + 'br[A|clear][]' + + 'span[E][#|S]' + + 'bdo[A|C|B][#|S]' + + 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + + 'h1[E|align][#|S]' + + 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + + 'map[B|C|A|name][X|form|Q|area]' + + 'h2[E|align][#|S]' + + 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + + 'h3[E|align][#|S]' + + 'tt[E][#|S]' + + 'i[E][#|S]' + + 'b[E][#|S]' + + 'u[E][#|S]' + + 's[E][#|S]' + + 'strike[E][#|S]' + + 'big[E][#|S]' + + 'small[E][#|S]' + + 'font[A|B|size|color|face][#|S]' + + 'basefont[id|size|color|face][]' + + 'em[E][#|S]' + + 'strong[E][#|S]' + + 'dfn[E][#|S]' + + 'code[E][#|S]' + + 'q[E|cite][#|S]' + + 'samp[E][#|S]' + + 'kbd[E][#|S]' + + 'var[E][#|S]' + + 'cite[E][#|S]' + + 'abbr[E][#|S]' + + 'acronym[E][#|S]' + + 'sub[E][#|S]' + + 'sup[E][#|S]' + + 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + + 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + + 'optgroup[E|disabled|label][option]' + + 'option[E|selected|disabled|label|value][]' + + 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + + 'label[E|for|accesskey|onfocus|onblur][#|S]' + + 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + + 'h4[E|align][#|S]' + + 'ins[E|cite|datetime][#|Y]' + + 'h5[E|align][#|S]' + + 'del[E|cite|datetime][#|Y]' + + 'h6[E|align][#|S]' + + 'div[E|align][#|Y]' + + 'ul[E|type|compact][li]' + + 'li[E|type|value][#|Y]' + + 'ol[E|type|compact|start][li]' + + 'dl[E|compact][dt|dd]' + + 'dt[E][#|S]' + + 'dd[E][#|Y]' + + 'menu[E|compact][li]' + + 'dir[E|compact][li]' + + 'pre[E|width|xml:space][#|ZA]' + + 'hr[E|align|noshade|size|width][]' + + 'blockquote[E|cite][#|Y]' + + 'address[E][#|S|p]' + + 'center[E][#|Y]' + + 'noframes[E][#|Y]' + + 'isindex[A|B|prompt][]' + + 'fieldset[E][#|legend|Y]' + + 'legend[E|accesskey|align][#|S]' + + 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + + 'caption[E|align][#|S]' + + 'col[ZG][]' + + 'colgroup[ZG][col]' + + 'thead[ZF][tr]' + + 'tr[ZF|bgcolor][th|td]' + + 'th[E|ZE][#|Y]' + + 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + + 'noscript[E][#|Y]' + + 'td[E|ZE][#|Y]' + + 'tfoot[ZF][tr]' + + 'tbody[ZF][tr]' + + 'area[E|D|shape|coords|href|nohref|alt|target][]' + + 'base[id|href|target][]' + + 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' + ); + + boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls'); + shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source'); + nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap); + defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea'); + selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); + + /** + * Schema validator class. + * + * @class tinymce.html.Schema + * @example + * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) + * alert('span is valid child of p.'); + * + * if (tinymce.activeEditor.schema.getElementRule('p')) + * alert('P is a valid element.'); + * + * @class tinymce.html.Schema + * @version 3.4 + */ + + /** + * Constructs a new Schema instance. + * + * @constructor + * @method Schema + * @param {Object} settings Name/value settings object. + */ + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap; + + settings = settings || {}; + + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; + + // Build styles list + if (settings.valid_styles) { + validStyles = {}; + + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); + }); + } + + whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap; + + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; + + // Parses the specified valid_elements string and adds to the current rules + // This function is a bit hard to read since it's heavily optimized for speed + function addValidElements(valid_elements) { + var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, + elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, + attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, + hasPatternsRegExp = /[*?+]/; + + if (valid_elements) { + // Split valid elements into an array with rules + valid_elements = split(valid_elements); + + if (elements['@']) { + globalAttributes = elements['@'].attributes; + globalAttributesOrder = elements['@'].attributesOrder; + } + + // Loop all rules + for (ei = 0, el = valid_elements.length; ei < el; ei++) { + // Parse element rule + matches = elementRuleRegExp.exec(valid_elements[ei]); + if (matches) { + // Setup local names for matches + prefix = matches[1]; + elementName = matches[2]; + outputName = matches[3]; + attrData = matches[4]; + + // Create new attributes and attributesOrder + attributes = {}; + attributesOrder = []; + + // Create the new element + element = { + attributes : attributes, + attributesOrder : attributesOrder + }; + + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; + + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; + + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; + + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); + } + + // Attributes defined + if (attrData) { + attrData = split(attrData, '|'); + for (ai = 0, al = attrData.length; ai < al; ai++) { + matches = attrRuleRegExp.exec(attrData[ai]); + if (matches) { + attr = {}; + attrType = matches[1]; + attrName = matches[2].replace(/::/g, ':'); + prefix = matches[3]; + value = matches[4]; + + // Required + if (attrType === '!') { + element.attributesRequired = element.attributesRequired || []; + element.attributesRequired.push(attrName); + attr.required = true; + } + + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } + + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } + + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } + + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } + + // Check for attribute patterns + if (hasPatternsRegExp.test(attrName)) { + element.attributePatterns = element.attributePatterns || []; + attr.pattern = patternToRegExp(attrName); + element.attributePatterns.push(attr); + } else { + // Add attribute to order list if it doesn't already exist + if (!attributes[attrName]) + attributesOrder.push(attrName); + + attributes[attrName] = attr; + } + } + } + } + + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } + + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } + + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } + } + } + }; + + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; + + addValidElements(valid_elements); + + each(transitional, function(element, name) { + children[name] = element.children; + }); + }; + + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; + + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + inline = matches[1] === '~', + cloneName = inline ? 'span' : 'div', + name = matches[2]; + + children[name] = children[cloneName]; + customElementsMap[name] = cloneName; + + // If it's not marked as inline then add it to valid block elements + if (!inline) + blockElementsMap[name] = {}; + + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); + } + }; + + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; + + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; + + if (matches) { + prefix = matches[1]; + + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; + + parent = children[matches[2]]; + + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); + } + }; + + function getElementRule(name) { + var element = elements[name], i; + + // Exact match found + if (element) + return element; + + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; + } + }; + + if (!settings.valid_elements) { + // No valid elements defined then clone the elements from the transitional spec + each(transitional, function(element, name) { + elements[name] = { + attributes : element.attributes, + attributesOrder : element.attributesOrder + }; + + children[name] = element.children; + }); + + // Switch these + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); + + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; + + // Remove these if they are empty by default + each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) { + elements[name].removeEmpty = true; + }); + + // Padd these by default + each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { + elements[name].paddEmpty = true; + }); + } else + setValidElements(settings.valid_elements); + + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); + + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); + + // If the user didn't allow span only allow internal spans + if (!getElementRule('span')) + addValidElements('span[!data-mce-type|*]'); + + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } + + /** + * Name/value map object with valid parents and children to those parents. + * + * @example + * children = { + * div:{p:{}, h1:{}} + * }; + * @field children + * @type {Object} + */ + self.children = children; + + /** + * Name/value map object with valid styles for each element. + * + * @field styles + * @type {Object} + */ + self.styles = validStyles; + + /** + * Returns a map with boolean attributes. + * + * @method getBoolAttrs + * @return {Object} Name/value lookup map for boolean attributes. + */ + self.getBoolAttrs = function() { + return boolAttrMap; + }; + + /** + * Returns a map with block elements. + * + * @method getBoolAttrs + * @return {Object} Name/value lookup map for block elements. + */ + self.getBlockElements = function() { + return blockElementsMap; + }; + + /** + * Returns a map with short ended elements such as BR or IMG. + * + * @method getShortEndedElements + * @return {Object} Name/value lookup map for short ended elements. + */ + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; + + /** + * Returns a map with self closing tags such as
  • . + * + * @method getSelfClosingElements + * @return {Object} Name/value lookup map for self closing tags elements. + */ + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; + + /** + * Returns a map with elements that should be treated as contents regardless if it has text + * content in them or not such as TD, VIDEO or IMG. + * + * @method getNonEmptyElements + * @return {Object} Name/value lookup map for non empty elements. + */ + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; + + /** + * Returns a map with elements where white space is to be preserved like PRE or SCRIPT. + * + * @method getWhiteSpaceElements + * @return {Object} Name/value lookup map for white space elements. + */ + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; + + /** + * Returns true/false if the specified element and it's child is valid or not + * according to the schema. + * + * @method isValidChild + * @param {String} name Element name to check for. + * @param {String} child Element child to verify. + * @return {Boolean} True/false if the element is a valid child of the specified parent. + */ + self.isValidChild = function(name, child) { + var parent = children[name]; + + return !!(parent && parent[child]); + }; + + /** + * Returns true/false if the specified element is valid or not + * according to the schema. + * + * @method getElementRule + * @param {String} name Element name to check for. + * @return {Object} Element object or undefined if the element isn't valid. + */ + self.getElementRule = getElementRule; + + /** + * Returns an map object of all custom elements. + * + * @method getCustomElements + * @return {Object} Name/value map object of all custom elements. + */ + self.getCustomElements = function() { + return customElementsMap; + }; + + /** + * Parses a valid elements string and adds it to the schema. The valid elements format is for example "element[attr=default|otherattr]". + * Existing rules will be replaced with the ones specified, so this extends the schema. + * + * @method addValidElements + * @param {String} valid_elements String in the valid elements format to be parsed. + */ + self.addValidElements = addValidElements; + + /** + * Parses a valid elements string and sets it to the schema. The valid elements format is for example "element[attr=default|otherattr]". + * Existing rules will be replaced with the ones specified, so this extends the schema. + * + * @method setValidElements + * @param {String} valid_elements String in the valid elements format to be parsed. + */ + self.setValidElements = setValidElements; + + /** + * Adds custom non HTML elements to the schema. + * + * @method addCustomElements + * @param {String} custom_elements Comma separated list of custom elements to add. + */ + self.addCustomElements = addCustomElements; + + /** + * Parses a valid children string and adds them to the schema structure. The valid children format is for example: "element[child1|child2]". + * + * @method addValidChildren + * @param {String} valid_children Valid children elements string to parse + */ + self.addValidChildren = addValidChildren; + }; + + // Expose boolMap and blockElementMap as static properties for usage in DOMUtils + tinymce.html.Schema.boolAttrMap = boolAttrMap; + tinymce.html.Schema.blockElementsMap = blockElementsMap; +})(tinymce); diff --git a/js/tiny_mce/classes/html/Serializer.js b/js/tiny_mce/classes/html/Serializer.js new file mode 100644 index 00000000..ac4fee74 --- /dev/null +++ b/js/tiny_mce/classes/html/Serializer.js @@ -0,0 +1,152 @@ +/** + * Serializer.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + /** + * This class is used to serialize down the DOM tree into a string using a Writer instance. + * + * + * @example + * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('

    text

    ')); + * @class tinymce.html.Serializer + * @version 3.4 + */ + + /** + * Constructs a new Serializer instance. + * + * @constructor + * @method Serializer + * @param {Object} settings Name/value settings object. + * @param {tinymce.html.Schema} schema Schema instance to use. + */ + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; + + /** + * Serializes the specified node into a string. + * + * @example + * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('

    text

    ')); + * @method serialize + * @param {tinymce.html.Node} node Node instance to serialize. + * @return {String} String with HTML based on DOM tree. + */ + self.serialize = function(node) { + var handlers, validate; + + validate = settings.validate; + + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, + + // #comment + 8: function(node) { + writer.comment(node.value); + }, + + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, + + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; + + writer.reset(); + + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; + + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; + + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; + + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; + + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + attrs = sortedAttrs; + } + + writer.start(node.name, attrs, isEmpty); + + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + + writer.end(name); + } + } else + handler(node); + } + + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); + + return writer.getContent(); + }; + } +})(tinymce); diff --git a/js/tiny_mce/classes/html/Styles.js b/js/tiny_mce/classes/html/Styles.js new file mode 100644 index 00000000..edf1e13a --- /dev/null +++ b/js/tiny_mce/classes/html/Styles.js @@ -0,0 +1,279 @@ +/** + * Styles.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +/** + * This class is used to parse CSS styles it also compresses styles to reduce the output size. + * + * @example + * var Styles = new tinymce.html.Styles({ + * url_converter: function(url) { + * return url; + * } + * }); + * + * styles = Styles.parse('border: 1px solid red'); + * styles.color = 'red'; + * + * console.log(new tinymce.html.StyleSerializer().serialize(styles)); + * + * @class tinymce.html.Styles + * @version 3.4 + */ +tinymce.html.Styles = function(settings, schema) { + var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, + urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, + styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, + trimRightRegExp = /\s+$/, + urlColorRegExp = /rgb/, + undef, i, encodingLookup = {}, encodingItems; + + settings = settings || {}; + + encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '\uFEFF' + i; + encodingLookup['\uFEFF' + i] = encodingItems[i]; + } + + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); + + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; + + return '#' + hex(r) + hex(g) + hex(b); + }; + + return { + /** + * Parses the specified RGB color value and returns a hex version of that color. + * + * @method toHex + * @param {String} color RGB string value like rgb(1,2,3) + * @return {String} Hex version of that RGB value like #FF00FF. + */ + toHex : function(color) { + return color.replace(rgbRegExp, toHex); + }, + + /** + * Parses the specified style value into an object collection. This parser will also + * merge and remove any redundant items that browsers might have added. It will also convert non hex + * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. + * + * @method parse + * @param {String} css Style value to parse for example: border:1px solid red;. + * @return {Object} Object representation of that style like {border : '1px solid red'} + */ + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; + + function compress(prefix, suffix) { + var top, right, bottom, left; + + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; + + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; + + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; + + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; + + // Compress + styles[prefix + suffix] = left; + delete styles[prefix + '-top' + suffix]; + delete styles[prefix + '-right' + suffix]; + delete styles[prefix + '-bottom' + suffix]; + delete styles[prefix + '-left' + suffix]; + }; + + /** + * Checks if the specific style can be compressed in other words if all border-width are equal. + */ + function canCompress(key) { + var value = styles[key], i; + + if (!value || value.indexOf(' ') < 0) + return; + + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; + } + + styles[key] = value[0]; + + return true; + }; + + /** + * Compresses multiple styles into one style. + */ + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; + + if (!canCompress(b)) + return; + + if (!canCompress(c)) + return; + + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; + + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; + + return encodingLookup[str]; + }; + + // Decodes the specified string by replacing all _ with it's original value \" \' etc + // It will also decode the \" \' if keep_slashes is set to fale or omitted + function decode(str, keep_slashes) { + if (isEncoded) { + str = str.replace(/\uFEFF[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } + + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); + + return str; + } + + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); + }); + + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); + + if (name && value.length > 0) { + // Opera will produce 700 instead of bold in their style values + if (name === 'font-weight' && value === '700') + value = 'bold'; + else if (name === 'color' || name === 'background-color') // Lowercase colors like RED + value = value.toLowerCase(); + + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); + + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) { + str = str || str2; + + if (str) { + str = decode(str); + + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } + + url = decode(url || url2 || url3); + + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); + + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }); + + styles[name] = isEncoded ? decode(value, true) : value; + } + + styleRegExp.lastIndex = matches.index + matches[0].length; + } + + // Compress the styles to reduce it's size for example IE will expand styles + compress("border", ""); + compress("border", "-width"); + compress("border", "-color"); + compress("border", "-style"); + compress("padding", ""); + compress("margin", ""); + compress2('border', 'border-width', 'border-style', 'border-color'); + + // Remove pointless border, IE produces these + if (styles.border === 'medium none') + delete styles.border; + } + + return styles; + }, + + /** + * Serializes the specified style object into a string. + * + * @method serialize + * @param {Object} styles Object to serialize as string for example: {border : '1px solid red'} + * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized. + * @return {String} String representation of the style object for example: border: 1px solid red. + */ + serialize : function(styles, element_name) { + var css = '', name, value; + + function serializeStyles(name) { + var styleList, i, l, value; + + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; + + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(element_name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + + return css; + } + }; +}; diff --git a/js/tiny_mce/classes/html/Writer.js b/js/tiny_mce/classes/html/Writer.js new file mode 100644 index 00000000..2abfac64 --- /dev/null +++ b/js/tiny_mce/classes/html/Writer.js @@ -0,0 +1,186 @@ +/** + * Writer.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +/** + * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. + * + * @class tinymce.html.Writer + * @example + * var writer = new tinymce.html.Writer({indent : true}); + * var parser = new tinymce.html.SaxParser(writer).parse('


    '); + * console.log(writer.getContent()); + * + * @class tinymce.html.Writer + * @version 3.4 + */ + +/** + * Constructs a new Writer instance. + * + * @constructor + * @method Writer + * @param {Object} settings Name/value settings object. + */ +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; + + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; + + return { + /** + * Writes the a start element such as

    . + * + * @method start + * @param {String} name Name of the element. + * @param {Array} attrs Optional attribute array or undefined if it hasn't any. + * @param {Boolean} empty Optional empty state if the tag should end like
    . + */ + start: function(name, attrs, empty) { + var i, l, attr, value; + + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } + } + + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; + + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + /** + * Writes the a end element such as

    . + * + * @method end + * @param {String} name Name of the element. + */ + end: function(name) { + var value; + + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ + + html.push(''); + + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + /** + * Writes a text node. + * + * @method text + * @param {String} text String to write out. + * @param {Boolean} raw Optional raw state if true the contents wont get encoded. + */ + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, + + /** + * Writes a cdata node such as . + * + * @method cdata + * @param {String} text String to write out inside the cdata. + */ + cdata: function(text) { + html.push(''); + }, + + /** + * Writes a comment node such as . + * + * @method cdata + * @param {String} text String to write out inside the comment. + */ + comment: function(text) { + html.push(''); + }, + + /** + * Writes a PI node such as . + * + * @method pi + * @param {String} name Name of the pi. + * @param {String} text String to write out inside the pi. + */ + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); + + if (indent) + html.push('\n'); + }, + + /** + * Writes a doctype node such as . + * + * @method doctype + * @param {String} text String to write out inside the doctype. + */ + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, + + /** + * Resets the internal buffer if one wants to reuse the writer. + * + * @method reset + */ + reset: function() { + html.length = 0; + }, + + /** + * Returns the contents that got serialized. + * + * @method getContent + * @return {String} HTML contents that got written down. + */ + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; diff --git a/js/tiny_mce/classes/tinymce.js b/js/tiny_mce/classes/tinymce.js index d0ff8357..6afea2fe 100644 --- a/js/tiny_mce/classes/tinymce.js +++ b/js/tiny_mce/classes/tinymce.js @@ -1,655 +1,869 @@ -/** - * tinymce.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(win) { - var whiteSpaceRe = /^\s*|\s*$/g, - undefined; - - /** - * Core namespace with core functionality for the TinyMCE API all sub classes will be added to this namespace/object. - * - * @static - * @class tinymce - * @example - * // Using each method - * tinymce.each([1, 2, 3], function(v, i) { - * console.log(i + '=' + v); - * }); - * - * // Checking for a specific browser - * if (tinymce.isIE) - * console.log("IE"); - */ - var tinymce = { - /** - * Major version of TinyMCE build. - * - * @property majorVersion - * @type String - */ - majorVersion : '@@tinymce_major_version@@', - - /** - * Major version of TinyMCE build. - * - * @property minorVersion - * @type String - */ - minorVersion : '@@tinymce_minor_version@@', - - /** - * Release date of TinyMCE build. - * - * @property minorVersion - * @type String - */ - releaseDate : '@@tinymce_release_date@@', - - /** - * Initializes the TinyMCE global namespace this will setup browser detection and figure out where TinyMCE is running from. - */ - _init : function() { - var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; - - /** - * Constant that is true if the browser is Opera. - * - * @property isOpera - * @type Boolean - * @final - */ - t.isOpera = win.opera && opera.buildNumber; - - /** - * Constant that is true if the browser is WebKit (Safari/Chrome). - * - * @property isWebKit - * @type Boolean - * @final - */ - t.isWebKit = /WebKit/.test(ua); - - /** - * Constant that is true if the browser is IE. - * - * @property isIE - * @type Boolean - * @final - */ - t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); - - /** - * Constant that is true if the browser is IE 6 or older. - * - * @property isIE6 - * @type Boolean - * @final - */ - t.isIE6 = t.isIE && /MSIE [56]/.test(ua); - - /** - * Constant that is true if the browser is Gecko. - * - * @property isGecko - * @type Boolean - * @final - */ - t.isGecko = !t.isWebKit && /Gecko/.test(ua); - - /** - * Constant that is true if the os is Mac OS. - * - * @property isMac - * @type Boolean - * @final - */ - t.isMac = ua.indexOf('Mac') != -1; - - /** - * Constant that is true if the runtime is Adobe Air. - * - * @property isAir - * @type Boolean - * @final - */ - t.isAir = /adobeair/i.test(ua); - - // TinyMCE .NET webcontrol might be setting the values for TinyMCE - if (win.tinyMCEPreInit) { - t.suffix = tinyMCEPreInit.suffix; - t.baseURL = tinyMCEPreInit.base; - t.query = tinyMCEPreInit.query; - return; - } - - // Get suffix and base - t.suffix = ''; - - // If base element found, add that infront of baseURL - nl = d.getElementsByTagName('base'); - for (i=0; i : - s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); - cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name - - // Create namespace for new class - ns = t.createNS(s[3].replace(/\.\w+$/, '')); - - // Class already exists - if (ns[cn]) - return; - - // Make pure static class - if (s[2] == 'static') { - ns[cn] = p; - - if (this.onCreate) - this.onCreate(s[2], s[3], ns[cn]); - - return; - } - - // Create default constructor - if (!p[cn]) { - p[cn] = function() {}; - de = 1; - } - - // Add constructor and methods - ns[cn] = p[cn]; - t.extend(ns[cn].prototype, p); - - // Extend - if (s[5]) { - sp = t.resolve(s[5]).prototype; - scn = s[5].match(/\.(\w+)$/i)[1]; // Class name - - // Extend constructor - c = ns[cn]; - if (de) { - // Add passthrough constructor - ns[cn] = function() { - return sp[scn].apply(this, arguments); - }; - } else { - // Add inherit constructor - ns[cn] = function() { - this.parent = sp[scn]; - return c.apply(this, arguments); - }; - } - ns[cn].prototype[cn] = ns[cn]; - - // Add super methods - t.each(sp, function(f, n) { - ns[cn].prototype[n] = sp[n]; - }); - - // Add overridden methods - t.each(p, function(f, n) { - // Extend methods if needed - if (sp[n]) { - ns[cn].prototype[n] = function() { - this.parent = sp[n]; - return f.apply(this, arguments); - }; - } else { - if (n != cn) - ns[cn].prototype[n] = f; - } - }); - } - - // Add static methods - t.each(p['static'], function(f, n) { - ns[cn][n] = f; - }); - - if (this.onCreate) - this.onCreate(s[2], s[3], ns[cn].prototype); - }, - - /** - * Executed the specified function for each item in a object tree. - * - * @method walk - * @param {Object} o Object tree to walk though. - * @param {function} f Function to call for each item. - * @param {String} n Optional name of collection inside the objects to walk for example childNodes. - * @param {String} s Optional scope to execute the function in. - */ - walk : function(o, f, n, s) { - s = s || this; - - if (o) { - if (n) - o = o[n]; - - tinymce.each(o, function(o, i) { - if (f.call(s, o, i, n) === false) - return false; - - tinymce.walk(o, f, n, s); - }); - } - }, - - /** - * Creates a namespace on a specific object. - * - * @method createNS - * @param {String} n Namespace to create for example a.b.c.d. - * @param {Object} o Optional object to add namespace to, defaults to window. - * @return {Object} New namespace object the last item in path. - */ - createNS : function(n, o) { - var i, v; - - o = o || win; - - n = n.split('.'); - for (i=0; i=534; + + // TinyMCE .NET webcontrol might be setting the values for TinyMCE + if (win.tinyMCEPreInit) { + t.suffix = tinyMCEPreInit.suffix; + t.baseURL = tinyMCEPreInit.base; + t.query = tinyMCEPreInit.query; + return; + } + + // Get suffix and base + t.suffix = ''; + + // If base element found, add that infront of baseURL + nl = d.getElementsByTagName('base'); + for (i=0; i 3;}); + */ + grep : function(a, f) { + var o = []; + + tinymce.each(a, function(v) { + if (!f || f(v)) + o.push(v); + }); + + return o; + }, + + /** + * Returns the index of a value in an array, this method will return -1 if the item wasn't found. + * + * @method inArray + * @param {Array} a Array/Object to search for value in. + * @param {Object} v Value to check for inside the array. + * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found. + * @example + * // Get index of value in array this will alert 1 since 2 is at that index + * alert(tinymce.inArray([1,2,3], 2)); + */ + inArray : function(a, v) { + var i, l; + + if (a) { + for (i = 0, l = a.length; i < l; i++) { + if (a[i] === v) + return i; + } + } + + return -1; + }, + + /** + * Extends an object with the specified other object(s). + * + * @method extend + * @param {Object} o Object to extend with new items. + * @param {Object} e..n Object(s) to extend the specified object with. + * @return {Object} o New extended object, same reference as the input object. + * @example + * // Extends obj1 with two new fields + * var obj = tinymce.extend(obj1, { + * somefield1 : 'a', + * somefield2 : 'a' + * }); + * + * // Extends obj with obj2 and obj3 + * tinymce.extend(obj, obj2, obj3); + */ + extend : function(o, e) { + var i, l, a = arguments; + + for (i = 1, l = a.length; i < l; i++) { + e = a[i]; + + tinymce.each(e, function(v, n) { + if (v !== undefined) + o[n] = v; + }); + } + + return o; + }, + + // #endif + + /** + * Removes whitespace from the beginning and end of a string. + * + * @method trim + * @param {String} s String to remove whitespace from. + * @return {String} New string with removed whitespace. + */ + trim : function(s) { + return (s ? '' + s : '').replace(whiteSpaceRe, ''); + }, + + /** + * Creates a class, subclass or static singleton. + * More details on this method can be found in the Wiki. + * + * @method create + * @param {String} s Class name, inheritage and prefix. + * @param {Object} p Collection of methods to add to the class. + * @param {Object} root Optional root object defaults to the global window object. + * @example + * // Creates a basic class + * tinymce.create('tinymce.somepackage.SomeClass', { + * SomeClass : function() { + * // Class constructor + * }, + * + * method : function() { + * // Some method + * } + * }); + * + * // Creates a basic subclass class + * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', { + * SomeSubClass: function() { + * // Class constructor + * this.parent(); // Call parent constructor + * }, + * + * method : function() { + * // Some method + * this.parent(); // Call parent method + * }, + * + * 'static' : { + * staticMethod : function() { + * // Static method + * } + * } + * }); + * + * // Creates a singleton/static class + * tinymce.create('static tinymce.somepackage.SomeSingletonClass', { + * method : function() { + * // Some method + * } + * }); + */ + create : function(s, p, root) { + var t = this, sp, ns, cn, scn, c, de = 0; + + // Parse : : + s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); + cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name + + // Create namespace for new class + ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); + + // Class already exists + if (ns[cn]) + return; + + // Make pure static class + if (s[2] == 'static') { + ns[cn] = p; + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn]); + + return; + } + + // Create default constructor + if (!p[cn]) { + p[cn] = function() {}; + de = 1; + } + + // Add constructor and methods + ns[cn] = p[cn]; + t.extend(ns[cn].prototype, p); + + // Extend + if (s[5]) { + sp = t.resolve(s[5]).prototype; + scn = s[5].match(/\.(\w+)$/i)[1]; // Class name + + // Extend constructor + c = ns[cn]; + if (de) { + // Add passthrough constructor + ns[cn] = function() { + return sp[scn].apply(this, arguments); + }; + } else { + // Add inherit constructor + ns[cn] = function() { + this.parent = sp[scn]; + return c.apply(this, arguments); + }; + } + ns[cn].prototype[cn] = ns[cn]; + + // Add super methods + t.each(sp, function(f, n) { + ns[cn].prototype[n] = sp[n]; + }); + + // Add overridden methods + t.each(p, function(f, n) { + // Extend methods if needed + if (sp[n]) { + ns[cn].prototype[n] = function() { + this.parent = sp[n]; + return f.apply(this, arguments); + }; + } else { + if (n != cn) + ns[cn].prototype[n] = f; + } + }); + } + + // Add static methods + t.each(p['static'], function(f, n) { + ns[cn][n] = f; + }); + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn].prototype); + }, + + /** + * Executed the specified function for each item in a object tree. + * + * @method walk + * @param {Object} o Object tree to walk though. + * @param {function} f Function to call for each item. + * @param {String} n Optional name of collection inside the objects to walk for example childNodes. + * @param {String} s Optional scope to execute the function in. + */ + walk : function(o, f, n, s) { + s = s || this; + + if (o) { + if (n) + o = o[n]; + + tinymce.each(o, function(o, i) { + if (f.call(s, o, i, n) === false) + return false; + + tinymce.walk(o, f, n, s); + }); + } + }, + + /** + * Creates a namespace on a specific object. + * + * @method createNS + * @param {String} n Namespace to create for example a.b.c.d. + * @param {Object} o Optional object to add namespace to, defaults to window. + * @return {Object} New namespace object the last item in path. + * @example + * // Create some namespace + * tinymce.createNS('tinymce.somepackage.subpackage'); + * + * // Add a singleton + * var tinymce.somepackage.subpackage.SomeSingleton = { + * method : function() { + * // Some method + * } + * }; + */ + createNS : function(n, o) { + var i, v; + + o = o || win; + + n = n.split('.'); + for (i=0; i'; - - if (s.image) - h += '' + l + ''; - else - h += '' + (l ? '' + l + '' : '') + ''; - - return h; - }, - - /** - * Post render handler. This function will be called after the UI has been - * rendered so that events can be added. - * - * @method postRender - */ - postRender : function() { - var t = this, s = t.settings; - - tinymce.dom.Event.add(t.id, 'click', function(e) { - if (!t.isDisabled()) - return s.onclick.call(s.scope, e); - }); - } - }); -})(tinymce); +/** + * Button.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM; + + /** + * This class is used to create a UI button. A button is basically a link + * that is styled to look like a button or icon. + * + * @class tinymce.ui.Button + * @extends tinymce.ui.Control + */ + tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { + /** + * Constructs a new button control instance. + * + * @constructor + * @method Button + * @param {String} id Control id for the button. + * @param {Object} s Optional name/value settings object. + * @param {Editor} ed Optional the editor instance this button is for. + */ + Button : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceButton'; + }, + + /** + * Renders the button as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the button control element. + */ + renderHTML : function() { + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + l; + else + h += '' + (l ? '' + l + '' : ''); + + h += ''; + h += ''; + return h; + }, + + /** + * Post render handler. This function will be called after the UI has been + * rendered so that events can be added. + * + * @method postRender + */ + postRender : function() { + var t = this, s = t.settings; + + tinymce.dom.Event.add(t.id, 'click', function(e) { + if (!t.isDisabled()) + return s.onclick.call(s.scope, e); + }); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/ColorSplitButton.js b/js/tiny_mce/classes/ui/ColorSplitButton.js index 9391dc88..d94adeb4 100644 --- a/js/tiny_mce/classes/ui/ColorSplitButton.js +++ b/js/tiny_mce/classes/ui/ColorSplitButton.js @@ -1,249 +1,285 @@ -/** - * ColorSplitButton.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; - - /** - * This class is used to create UI color split button. A color split button will present show a small color picker - * when you press the open menu. - * - * @class tinymce.ui.ColorSplitButton - * @extends tinymce.ui.SplitButton - */ - tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { - /** - * Constructs a new color split button control instance. - * - * @constructor - * @method ColorSplitButton - * @param {String} id Control id for the color split button. - * @param {Object} s Optional name/value settings object. - */ - ColorSplitButton : function(id, s) { - var t = this; - - t.parent(id, s); - - /** - * Settings object. - * - * @property settings - * @type Object - */ - t.settings = s = tinymce.extend({ - colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', - grid_width : 8, - default_color : '#888888' - }, t.settings); - - /** - * Fires when the menu is shown. - * - * @event onShowMenu - */ - t.onShowMenu = new tinymce.util.Dispatcher(t); - - /** - * Fires when the menu is hidden. - * - * @event onHideMenu - */ - t.onHideMenu = new tinymce.util.Dispatcher(t); - - /** - * Current color value. - * - * @property value - * @type String - */ - t.value = s.default_color; - }, - - /** - * Shows the color menu. The color menu is a layer places under the button - * and displays a table of colors for the user to pick from. - * - * @method showMenu - */ - showMenu : function() { - var t = this, r, p, e, p2; - - if (t.isDisabled()) - return; - - if (!t.isMenuRendered) { - t.renderMenu(); - t.isMenuRendered = true; - } - - if (t.isMenuVisible) - return t.hideMenu(); - - e = DOM.get(t.id); - DOM.show(t.id + '_menu'); - DOM.addClass(e, 'mceSplitButtonSelected'); - p2 = DOM.getPos(e); - DOM.setStyles(t.id + '_menu', { - left : p2.x, - top : p2.y + e.clientHeight, - zIndex : 200000 - }); - e = 0; - - Event.add(DOM.doc, 'mousedown', t.hideMenu, t); - t.onShowMenu.dispatch(t); - - if (t._focused) { - t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { - if (e.keyCode == 27) - t.hideMenu(); - }); - - DOM.select('a', t.id + '_menu')[0].focus(); // Select first link - } - - t.isMenuVisible = 1; - }, - - /** - * Hides the color menu. The optional event parameter is used to check where the event occured so it - * doesn't close them menu if it was a event inside the menu. - * - * @method hideMenu - * @param {Event} e Optional event object. - */ - hideMenu : function(e) { - var t = this; - - // Prevent double toogles by canceling the mouse click event to the button - if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) - return; - - if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { - DOM.removeClass(t.id, 'mceSplitButtonSelected'); - Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); - Event.remove(t.id + '_menu', 'keydown', t._keyHandler); - DOM.hide(t.id + '_menu'); - } - - t.onHideMenu.dispatch(t); - - t.isMenuVisible = 0; - }, - - /** - * Renders the menu to the DOM. - * - * @method renderMenu - */ - renderMenu : function() { - var t = this, m, i = 0, s = t.settings, n, tb, tr, w; - - w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); - m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); - DOM.add(m, 'span', {'class' : 'mceMenuLine'}); - - n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'}); - tb = DOM.add(n, 'tbody'); - - // Generate color grid - i = 0; - each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { - c = c.replace(/^#/, ''); - - if (!i--) { - tr = DOM.add(tb, 'tr'); - i = s.grid_width - 1; - } - - n = DOM.add(tr, 'td'); - - n = DOM.add(n, 'a', { - href : 'javascript:;', - style : { - backgroundColor : '#' + c - }, - _mce_color : '#' + c - }); - }); - - if (s.more_colors_func) { - n = DOM.add(tb, 'tr'); - n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); - n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); - - Event.add(n, 'click', function(e) { - s.more_colors_func.call(s.more_colors_scope || this); - return Event.cancel(e); // Cancel to fix onbeforeunload problem - }); - } - - DOM.addClass(m, 'mceColorSplitMenu'); - - Event.add(t.id + '_menu', 'click', function(e) { - var c; - - e = e.target; - - if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color'))) - t.setColor(c); - - return Event.cancel(e); // Prevent IE auto save warning - }); - - return w; - }, - - /** - * Sets the current color for the control and hides the menu if it should be visible. - * - * @method setColor - * @param {String} c Color code value in hex for example: #FF00FF - */ - setColor : function(c) { - var t = this; - - DOM.setStyle(t.id + '_preview', 'backgroundColor', c); - - t.value = c; - t.hideMenu(); - t.settings.onselect(c); - }, - - /** - * Post render event. This will be executed after the control has been rendered and can be used to - * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). - * - * @method postRender - */ - postRender : function() { - var t = this, id = t.id; - - t.parent(); - DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); - DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); - }, - - /** - * Destroys the control. This means it will be removed from the DOM and any - * events tied to it will also be removed. - * - * @method destroy - */ - destroy : function() { - this.parent(); - - Event.clear(this.id + '_menu'); - Event.clear(this.id + '_more'); - DOM.remove(this.id + '_menu'); - } - }); -})(tinymce); +/** + * ColorSplitButton.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; + + /** + * This class is used to create UI color split button. A color split button will present show a small color picker + * when you press the open menu. + * + * @class tinymce.ui.ColorSplitButton + * @extends tinymce.ui.SplitButton + */ + tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { + /** + * Constructs a new color split button control instance. + * + * @constructor + * @method ColorSplitButton + * @param {String} id Control id for the color split button. + * @param {Object} s Optional name/value settings object. + * @param {Editor} ed The editor instance this button is for. + */ + ColorSplitButton : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + /** + * Settings object. + * + * @property settings + * @type Object + */ + t.settings = s = tinymce.extend({ + colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', + grid_width : 8, + default_color : '#888888' + }, t.settings); + + /** + * Fires when the menu is shown. + * + * @event onShowMenu + */ + t.onShowMenu = new tinymce.util.Dispatcher(t); + + /** + * Fires when the menu is hidden. + * + * @event onHideMenu + */ + t.onHideMenu = new tinymce.util.Dispatcher(t); + + /** + * Current color value. + * + * @property value + * @type String + */ + t.value = s.default_color; + }, + + /** + * Shows the color menu. The color menu is a layer places under the button + * and displays a table of colors for the user to pick from. + * + * @method showMenu + */ + showMenu : function() { + var t = this, r, p, e, p2; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + e = DOM.get(t.id); + DOM.show(t.id + '_menu'); + DOM.addClass(e, 'mceSplitButtonSelected'); + p2 = DOM.getPos(e); + DOM.setStyles(t.id + '_menu', { + left : p2.x, + top : p2.y + e.clientHeight, + zIndex : 200000 + }); + e = 0; + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.onShowMenu.dispatch(t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.isMenuVisible = 1; + }, + + /** + * Hides the color menu. The optional event parameter is used to check where the event occured so it + * doesn't close them menu if it was a event inside the menu. + * + * @method hideMenu + * @param {Event} e Optional event object. + */ + hideMenu : function(e) { + var t = this; + + if (t.isMenuVisible) { + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } + + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + } + }, + + /** + * Renders the menu to the DOM. + * + * @method renderMenu + */ + renderMenu : function() { + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; + + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); + DOM.add(m, 'span', {'class' : 'mceMenuLine'}); + + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); + tb = DOM.add(n, 'tbody'); + + // Generate color grid + i = 0; + each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { + c = c.replace(/^#/, ''); + + if (!i--) { + tr = DOM.add(tb, 'tr'); + i = s.grid_width - 1; + } + + n = DOM.add(tr, 'td'); + n = DOM.add(n, 'a', { + role : 'option', + href : 'javascript:;', + style : { + backgroundColor : '#' + c + }, + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c + }); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } + }); + + if (s.more_colors_func) { + n = DOM.add(tb, 'tr'); + n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); + n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + + Event.add(n, 'click', function(e) { + s.more_colors_func.call(s.more_colors_scope || this); + return Event.cancel(e); // Cancel to fix onbeforeunload problem + }); + } + + DOM.addClass(m, 'mceColorSplitMenu'); + + new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); + + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = DOM.getParent(e.target, 'a', tb); + + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) + t.setColor(c); + + return Event.cancel(e); // Prevent IE auto save warning + }); + + return w; + }, + + /** + * Sets the current color for the control and hides the menu if it should be visible. + * + * @method setColor + * @param {String} c Color code value in hex for example: #FF00FF + */ + setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + /** + * Change the currently selected color for the control. + * + * @method displayColor + * @param {String} c Color code value in hex for example: #FF00FF + */ + displayColor : function(c) { + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); + + t.value = c; + }, + + /** + * Post render event. This will be executed after the control has been rendered and can be used to + * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). + * + * @method postRender + */ + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); + }, + + /** + * Destroys the control. This means it will be removed from the DOM and any + * events tied to it will also be removed. + * + * @method destroy + */ + destroy : function() { + this.parent(); + + Event.clear(this.id + '_menu'); + Event.clear(this.id + '_more'); + DOM.remove(this.id + '_menu'); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/Container.js b/js/tiny_mce/classes/ui/Container.js index 1a6a6aa3..aff916a6 100644 --- a/js/tiny_mce/classes/ui/Container.js +++ b/js/tiny_mce/classes/ui/Container.js @@ -1,66 +1,66 @@ -/** - * Container.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -/** - * This class is the base class for all container controls like toolbars. This class should not - * be instantiated directly other container controls should inherit from this one. - * - * @class tinymce.ui.Container - * @extends tinymce.ui.Control - */ -tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { - /** - * Base contrustor a new container control instance. - * - * @constructor - * @method Container - * @param {String} id Control id to use for the container. - * @param {Object} s Optional name/value settings object. - */ - Container : function(id, s) { - this.parent(id, s); - - /** - * Array of controls added to the container. - * - * @property controls - * @type Array - */ - this.controls = []; - - this.lookup = {}; - }, - - /** - * Adds a control to the collection of controls for the container. - * - * @method add - * @param {tinymce.ui.Control} c Control instance to add to the container. - * @return {tinymce.ui.Control} Same control instance that got passed in. - */ - add : function(c) { - this.lookup[c.id] = c; - this.controls.push(c); - - return c; - }, - - /** - * Returns a control by id from the containers collection. - * - * @method get - * @param {String} n Id for the control to retrive. - * @return {tinymce.ui.Control} Control instance by the specified name or undefined if it wasn't found. - */ - get : function(n) { - return this.lookup[n]; - } -}); - +/** + * Container.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +/** + * This class is the base class for all container controls like toolbars. This class should not + * be instantiated directly other container controls should inherit from this one. + * + * @class tinymce.ui.Container + * @extends tinymce.ui.Control + */ +tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { + /** + * Base contrustor a new container control instance. + * + * @constructor + * @method Container + * @param {String} id Control id to use for the container. + * @param {Object} s Optional name/value settings object. + */ + Container : function(id, s, editor) { + this.parent(id, s, editor); + + /** + * Array of controls added to the container. + * + * @property controls + * @type Array + */ + this.controls = []; + + this.lookup = {}; + }, + + /** + * Adds a control to the collection of controls for the container. + * + * @method add + * @param {tinymce.ui.Control} c Control instance to add to the container. + * @return {tinymce.ui.Control} Same control instance that got passed in. + */ + add : function(c) { + this.lookup[c.id] = c; + this.controls.push(c); + + return c; + }, + + /** + * Returns a control by id from the containers collection. + * + * @method get + * @param {String} n Id for the control to retrive. + * @return {tinymce.ui.Control} Control instance by the specified name or undefined if it wasn't found. + */ + get : function(n) { + return this.lookup[n]; + } +}); + diff --git a/js/tiny_mce/classes/ui/Control.js b/js/tiny_mce/classes/ui/Control.js index d5635ffe..3550cf18 100644 --- a/js/tiny_mce/classes/ui/Control.js +++ b/js/tiny_mce/classes/ui/Control.js @@ -1,196 +1,198 @@ -/** - * Control.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - // Shorten class names - var DOM = tinymce.DOM, is = tinymce.is; - - /** - * This class is the base class for all controls like buttons, toolbars, containers. This class should not - * be instantiated directly other controls should inherit from this one. - * - * @class tinymce.ui.Control - */ - tinymce.create('tinymce.ui.Control', { - /** - * Constructs a new control instance. - * - * @constructor - * @method Control - * @param {String} id Control id. - * @param {Object} s Optional name/value settings object. - */ - Control : function(id, s) { - this.id = id; - this.settings = s = s || {}; - this.rendered = false; - this.onRender = new tinymce.util.Dispatcher(this); - this.classPrefix = ''; - this.scope = s.scope || this; - this.disabled = 0; - this.active = 0; - }, - - /** - * Sets the disabled state for the control. This will add CSS classes to the - * element that contains the control. So that it can be disabled visually. - * - * @method setDisabled - * @param {Boolean} s Boolean state if the control should be disabled or not. - */ - setDisabled : function(s) { - var e; - - if (s != this.disabled) { - e = DOM.get(this.id); - - // Add accessibility title for unavailable actions - if (e && this.settings.unavailable_prefix) { - if (s) { - this.prevTitle = e.title; - e.title = this.settings.unavailable_prefix + ": " + e.title; - } else - e.title = this.prevTitle; - } - - this.setState('Disabled', s); - this.setState('Enabled', !s); - this.disabled = s; - } - }, - - /** - * Returns true/false if the control is disabled or not. This is a method since you can then - * choose to check some class or some internal bool state in subclasses. - * - * @method isDisabled - * @return {Boolean} true/false if the control is disabled or not. - */ - isDisabled : function() { - return this.disabled; - }, - - /** - * Sets the activated state for the control. This will add CSS classes to the - * element that contains the control. So that it can be activated visually. - * - * @method setActive - * @param {Boolean} s Boolean state if the control should be activated or not. - */ - setActive : function(s) { - if (s != this.active) { - this.setState('Active', s); - this.active = s; - } - }, - - /** - * Returns true/false if the control is disabled or not. This is a method since you can then - * choose to check some class or some internal bool state in subclasses. - * - * @method isActive - * @return {Boolean} true/false if the control is disabled or not. - */ - isActive : function() { - return this.active; - }, - - /** - * Sets the specified class state for the control. - * - * @method setState - * @param {String} c Class name to add/remove depending on state. - * @param {Boolean} s True/false state if the class should be removed or added. - */ - setState : function(c, s) { - var n = DOM.get(this.id); - - c = this.classPrefix + c; - - if (s) - DOM.addClass(n, c); - else - DOM.removeClass(n, c); - }, - - /** - * Returns true/false if the control has been rendered or not. - * - * @method isRendered - * @return {Boolean} State if the control has been rendered or not. - */ - isRendered : function() { - return this.rendered; - }, - - /** - * Renders the control as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the button control element. - */ - renderHTML : function() { - }, - - /** - * Renders the control to the specified container element. - * - * @method renderTo - * @param {Element} n HTML DOM element to add control to. - */ - renderTo : function(n) { - DOM.setHTML(n, this.renderHTML()); - }, - - /** - * Post render event. This will be executed after the control has been rendered and can be used to - * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). - * - * @method postRender - */ - postRender : function() { - var t = this, b; - - // Set pending states - if (is(t.disabled)) { - b = t.disabled; - t.disabled = -1; - t.setDisabled(b); - } - - if (is(t.active)) { - b = t.active; - t.active = -1; - t.setActive(b); - } - }, - - /** - * Removes the control. This means it will be removed from the DOM and any - * events tied to it will also be removed. - * - * @method remove - */ - remove : function() { - DOM.remove(this.id); - this.destroy(); - }, - - /** - * Destroys the control will free any memory by removing event listeners etc. - * - * @method destroy - */ - destroy : function() { - tinymce.dom.Event.clear(this.id); - } - }); +/** + * Control.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + // Shorten class names + var DOM = tinymce.DOM, is = tinymce.is; + + /** + * This class is the base class for all controls like buttons, toolbars, containers. This class should not + * be instantiated directly other controls should inherit from this one. + * + * @class tinymce.ui.Control + */ + tinymce.create('tinymce.ui.Control', { + /** + * Constructs a new control instance. + * + * @constructor + * @method Control + * @param {String} id Control id. + * @param {Object} s Optional name/value settings object. + */ + Control : function(id, s, editor) { + this.id = id; + this.settings = s = s || {}; + this.rendered = false; + this.onRender = new tinymce.util.Dispatcher(this); + this.classPrefix = ''; + this.scope = s.scope || this; + this.disabled = 0; + this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); + }, + + /** + * Sets the disabled state for the control. This will add CSS classes to the + * element that contains the control. So that it can be disabled visually. + * + * @method setDisabled + * @param {Boolean} s Boolean state if the control should be disabled or not. + */ + setDisabled : function(s) { + if (s != this.disabled) { + this.setAriaProperty('disabled', s); + + this.setState('Disabled', s); + this.setState('Enabled', !s); + this.disabled = s; + } + }, + + /** + * Returns true/false if the control is disabled or not. This is a method since you can then + * choose to check some class or some internal bool state in subclasses. + * + * @method isDisabled + * @return {Boolean} true/false if the control is disabled or not. + */ + isDisabled : function() { + return this.disabled; + }, + + /** + * Sets the activated state for the control. This will add CSS classes to the + * element that contains the control. So that it can be activated visually. + * + * @method setActive + * @param {Boolean} s Boolean state if the control should be activated or not. + */ + setActive : function(s) { + if (s != this.active) { + this.setState('Active', s); + this.active = s; + this.setAriaProperty('pressed', s); + } + }, + + /** + * Returns true/false if the control is disabled or not. This is a method since you can then + * choose to check some class or some internal bool state in subclasses. + * + * @method isActive + * @return {Boolean} true/false if the control is disabled or not. + */ + isActive : function() { + return this.active; + }, + + /** + * Sets the specified class state for the control. + * + * @method setState + * @param {String} c Class name to add/remove depending on state. + * @param {Boolean} s True/false state if the class should be removed or added. + */ + setState : function(c, s) { + var n = DOM.get(this.id); + + c = this.classPrefix + c; + + if (s) + DOM.addClass(n, c); + else + DOM.removeClass(n, c); + }, + + /** + * Returns true/false if the control has been rendered or not. + * + * @method isRendered + * @return {Boolean} State if the control has been rendered or not. + */ + isRendered : function() { + return this.rendered; + }, + + /** + * Renders the control as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the button control element. + */ + renderHTML : function() { + }, + + /** + * Renders the control to the specified container element. + * + * @method renderTo + * @param {Element} n HTML DOM element to add control to. + */ + renderTo : function(n) { + DOM.setHTML(n, this.renderHTML()); + }, + + /** + * Post render event. This will be executed after the control has been rendered and can be used to + * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). + * + * @method postRender + */ + postRender : function() { + var t = this, b; + + // Set pending states + if (is(t.disabled)) { + b = t.disabled; + t.disabled = -1; + t.setDisabled(b); + } + + if (is(t.active)) { + b = t.active; + t.active = -1; + t.setActive(b); + } + }, + + /** + * Removes the control. This means it will be removed from the DOM and any + * events tied to it will also be removed. + * + * @method remove + */ + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + + /** + * Destroys the control will free any memory by removing event listeners etc. + * + * @method destroy + */ + destroy : function() { + tinymce.dom.Event.clear(this.id); + } + }); })(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/ui/DropMenu.js b/js/tiny_mce/classes/ui/DropMenu.js index cc4390c3..d3e766a6 100644 --- a/js/tiny_mce/classes/ui/DropMenu.js +++ b/js/tiny_mce/classes/ui/DropMenu.js @@ -1,409 +1,476 @@ -/** - * DropMenu.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; - - /** - * This class is used to create drop menus, a drop menu can be a - * context menu, or a menu for a list box or a menu bar. - * - * @class tinymce.ui.DropMenu - * @extends tinymce.ui.Menu - */ - tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { - /** - * Constructs a new drop menu control instance. - * - * @constructor - * @method DropMenu - * @param {String} id Button control id for the button. - * @param {Object} s Optional name/value settings object. - */ - DropMenu : function(id, s) { - s = s || {}; - s.container = s.container || DOM.doc.body; - s.offset_x = s.offset_x || 0; - s.offset_y = s.offset_y || 0; - s.vp_offset_x = s.vp_offset_x || 0; - s.vp_offset_y = s.vp_offset_y || 0; - - if (is(s.icons) && !s.icons) - s['class'] += ' mceNoIcons'; - - this.parent(id, s); - this.onShowMenu = new tinymce.util.Dispatcher(this); - this.onHideMenu = new tinymce.util.Dispatcher(this); - this.classPrefix = 'mceMenu'; - }, - - /** - * Created a new sub menu for the drop menu control. - * - * @method createMenu - * @param {Object} s Optional name/value settings object. - * @return {tinymce.ui.DropMenu} New drop menu instance. - */ - createMenu : function(s) { - var t = this, cs = t.settings, m; - - s.container = s.container || cs.container; - s.parent = t; - s.constrain = s.constrain || cs.constrain; - s['class'] = s['class'] || cs['class']; - s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; - s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; - m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); - - m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); - - return m; - }, - - /** - * Repaints the menu after new items have been added dynamically. - * - * @method update - */ - update : function() { - var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; - - tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth; - th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight; - - if (!DOM.boxModel) - t.element.setStyles({width : tw + 2, height : th + 2}); - else - t.element.setStyles({width : tw, height : th}); - - if (s.max_width) - DOM.setStyle(co, 'width', tw); - - if (s.max_height) { - DOM.setStyle(co, 'height', th); - - if (tb.clientHeight < s.max_height) - DOM.setStyle(co, 'overflow', 'hidden'); - } - }, - - /** - * Displays the menu at the specified cordinate. - * - * @method showMenu - * @param {Number} x Horizontal position of the menu. - * @param {Number} y Vertical position of the menu. - * @param {Numner} px Optional parent X position used when menus are cascading. - */ - showMenu : function(x, y, px) { - var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; - - t.collapse(1); - - if (t.isMenuVisible) - return; - - if (!t.rendered) { - co = DOM.add(t.settings.container, t.renderNode()); - - each(t.items, function(o) { - o.postRender(); - }); - - t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); - } else - co = DOM.get('menu_' + t.id); - - // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug - if (!tinymce.isOpera) - DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); - - DOM.show(co); - t.update(); - - x += s.offset_x || 0; - y += s.offset_y || 0; - vp.w -= 4; - vp.h -= 4; - - // Move inside viewport if not submenu - if (s.constrain) { - w = co.clientWidth - ot; - h = co.clientHeight - ot; - mx = vp.x + vp.w; - my = vp.y + vp.h; - - if ((x + s.vp_offset_x + w) > mx) - x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); - - if ((y + s.vp_offset_y + h) > my) - y = Math.max(0, (my - s.vp_offset_y) - h); - } - - DOM.setStyles(co, {left : x , top : y}); - t.element.update(); - - t.isMenuVisible = 1; - t.mouseClickFunc = Event.add(co, 'click', function(e) { - var m; - - e = e.target; - - if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { - m = t.items[e.id]; - - if (m.isDisabled()) - return; - - dm = t; - - while (dm) { - if (dm.hideMenu) - dm.hideMenu(); - - dm = dm.settings.parent; - } - - if (m.settings.onclick) - m.settings.onclick(e); - - return Event.cancel(e); // Cancel to fix onbeforeunload problem - } - }); - - if (t.hasMenus()) { - t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { - var m, r, mi; - - e = e.target; - if (e && (e = DOM.getParent(e, 'tr'))) { - m = t.items[e.id]; - - if (t.lastMenu) - t.lastMenu.collapse(1); - - if (m.isDisabled()) - return; - - if (e && DOM.hasClass(e, cp + 'ItemSub')) { - //p = DOM.getPos(s.container); - r = DOM.getRect(e); - m.showMenu((r.x + r.w - ot), r.y - ot, r.x); - t.lastMenu = m; - DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); - } - } - }); - } - - t.onShowMenu.dispatch(t); - - if (s.keyboard_focus) { - Event.add(co, 'keydown', t._keyHandler, t); - DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link - t._focusIdx = 0; - } - }, - - /** - * Hides the displayed menu. - * - * @method hideMenu - */ - hideMenu : function(c) { - var t = this, co = DOM.get('menu_' + t.id), e; - - if (!t.isMenuVisible) - return; - - Event.remove(co, 'mouseover', t.mouseOverFunc); - Event.remove(co, 'click', t.mouseClickFunc); - Event.remove(co, 'keydown', t._keyHandler); - DOM.hide(co); - t.isMenuVisible = 0; - - if (!c) - t.collapse(1); - - if (t.element) - t.element.hide(); - - if (e = DOM.get(t.id)) - DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); - - t.onHideMenu.dispatch(t); - }, - - /** - * Adds a new menu, menu item or sub classes of them to the drop menu. - * - * @method add - * @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu. - * @return {tinymce.ui.Control} Same as the input control, the menu or menu item. - */ - add : function(o) { - var t = this, co; - - o = t.parent(o); - - if (t.isRendered && (co = DOM.get('menu_' + t.id))) - t._add(DOM.select('tbody', co)[0], o); - - return o; - }, - - /** - * Collapses the menu, this will hide the menu and all menu items. - * - * @method collapse - * @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well. - */ - collapse : function(d) { - this.parent(d); - this.hideMenu(1); - }, - - /** - * Removes a specific sub menu or menu item from the drop menu. - * - * @method remove - * @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu. - * @return {tinymce.ui.Control} Control instance or null if it wasn't found. - */ - remove : function(o) { - DOM.remove(o.id); - this.destroy(); - - return this.parent(o); - }, - - /** - * Destroys the menu. This will remove the menu from the DOM and any events added to it etc. - * - * @method destroy - */ - destroy : function() { - var t = this, co = DOM.get('menu_' + t.id); - - Event.remove(co, 'mouseover', t.mouseOverFunc); - Event.remove(co, 'click', t.mouseClickFunc); - - if (t.element) - t.element.remove(); - - DOM.remove(co); - }, - - /** - * Renders the specified menu node to the dom. - * - * @method renderNode - * @return {Element} Container element for the drop menu. - */ - renderNode : function() { - var t = this, s = t.settings, n, tb, co, w; - - w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'}); - co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); - t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); - - if (s.menu_line) - DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); - -// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); - n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); - tb = DOM.add(n, 'tbody'); - - each(t.items, function(o) { - t._add(tb, o); - }); - - t.rendered = true; - - return w; - }, - - // Internal functions - - _keyHandler : function(e) { - var t = this, kc = e.keyCode; - - function focus(d) { - var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i]; - - if (e) { - t._focusIdx = i; - e.focus(); - } - }; - - switch (kc) { - case 38: - focus(-1); // Select first link - return; - - case 40: - focus(1); - return; - - case 13: - return; - - case 27: - return this.hideMenu(); - } - }, - - _add : function(tb, o) { - var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; - - if (s.separator) { - ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); - DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); - - if (n = ro.previousSibling) - DOM.addClass(n, 'mceLast'); - - return; - } - - n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); - n = it = DOM.add(n, 'td'); - n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); - - DOM.addClass(it, s['class']); -// n = DOM.add(n, 'span', {'class' : 'item'}); - - ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); - - if (s.icon_src) - DOM.add(ic, 'img', {src : s.icon_src}); - - n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); - - if (o.settings.style) - DOM.setAttrib(n, 'style', o.settings.style); - - if (tb.childNodes.length == 1) - DOM.addClass(ro, 'mceFirst'); - - if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) - DOM.addClass(ro, 'mceFirst'); - - if (o.collapse) - DOM.addClass(ro, cp + 'ItemSub'); - - if (n = ro.previousSibling) - DOM.removeClass(n, 'mceLast'); - - DOM.addClass(ro, 'mceLast'); - } - }); +/** + * DropMenu.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; + + /** + * This class is used to create drop menus, a drop menu can be a + * context menu, or a menu for a list box or a menu bar. + * + * @class tinymce.ui.DropMenu + * @extends tinymce.ui.Menu + * @example + * // Adds a menu to the currently active editor instance + * var dm = tinyMCE.activeEditor.controlManager.createDropMenu('somemenu'); + * + * // Add some menu items + * dm.add({title : 'Menu 1', onclick : function() { + * alert('Item 1 was clicked.'); + * }}); + * + * dm.add({title : 'Menu 2', onclick : function() { + * alert('Item 2 was clicked.'); + * }}); + * + * // Adds a submenu + * var sub1 = dm.addMenu({title : 'Menu 3'}); + * sub1.add({title : 'Menu 1.1', onclick : function() { + * alert('Item 1.1 was clicked.'); + * }}); + * + * // Adds a horizontal separator + * sub1.addSeparator(); + * + * sub1.add({title : 'Menu 1.2', onclick : function() { + * alert('Item 1.2 was clicked.'); + * }}); + * + * // Adds a submenu to the submenu + * var sub2 = sub1.addMenu({title : 'Menu 1.3'}); + * + * // Adds items to the sub sub menu + * sub2.add({title : 'Menu 1.3.1', onclick : function() { + * alert('Item 1.3.1 was clicked.'); + * }}); + * + * sub2.add({title : 'Menu 1.3.2', onclick : function() { + * alert('Item 1.3.2 was clicked.'); + * }}); + * + * dm.add({title : 'Menu 4', onclick : function() { + * alert('Item 3 was clicked.'); + * }}); + * + * // Display the menu at position 100, 100 + * dm.showMenu(100, 100); + */ + tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { + /** + * Constructs a new drop menu control instance. + * + * @constructor + * @method DropMenu + * @param {String} id Button control id for the button. + * @param {Object} s Optional name/value settings object. + */ + DropMenu : function(id, s) { + s = s || {}; + s.container = s.container || DOM.doc.body; + s.offset_x = s.offset_x || 0; + s.offset_y = s.offset_y || 0; + s.vp_offset_x = s.vp_offset_x || 0; + s.vp_offset_y = s.vp_offset_y || 0; + + if (is(s.icons) && !s.icons) + s['class'] += ' mceNoIcons'; + + this.parent(id, s); + this.onShowMenu = new tinymce.util.Dispatcher(this); + this.onHideMenu = new tinymce.util.Dispatcher(this); + this.classPrefix = 'mceMenu'; + }, + + /** + * Created a new sub menu for the drop menu control. + * + * @method createMenu + * @param {Object} s Optional name/value settings object. + * @return {tinymce.ui.DropMenu} New drop menu instance. + */ + createMenu : function(s) { + var t = this, cs = t.settings, m; + + s.container = s.container || cs.container; + s.parent = t; + s.constrain = s.constrain || cs.constrain; + s['class'] = s['class'] || cs['class']; + s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; + s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; + m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); + + m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); + + return m; + }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, + + /** + * Repaints the menu after new items have been added dynamically. + * + * @method update + */ + update : function() { + var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; + + tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth; + th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight; + + if (!DOM.boxModel) + t.element.setStyles({width : tw + 2, height : th + 2}); + else + t.element.setStyles({width : tw, height : th}); + + if (s.max_width) + DOM.setStyle(co, 'width', tw); + + if (s.max_height) { + DOM.setStyle(co, 'height', th); + + if (tb.clientHeight < s.max_height) + DOM.setStyle(co, 'overflow', 'hidden'); + } + }, + + /** + * Displays the menu at the specified cordinate. + * + * @method showMenu + * @param {Number} x Horizontal position of the menu. + * @param {Number} y Vertical position of the menu. + * @param {Numner} px Optional parent X position used when menus are cascading. + */ + showMenu : function(x, y, px) { + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; + + t.collapse(1); + + if (t.isMenuVisible) + return; + + if (!t.rendered) { + co = DOM.add(t.settings.container, t.renderNode()); + + each(t.items, function(o) { + o.postRender(); + }); + + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + } else + co = DOM.get('menu_' + t.id); + + // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug + if (!tinymce.isOpera) + DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); + + DOM.show(co); + t.update(); + + x += s.offset_x || 0; + y += s.offset_y || 0; + vp.w -= 4; + vp.h -= 4; + + // Move inside viewport if not submenu + if (s.constrain) { + w = co.clientWidth - ot; + h = co.clientHeight - ot; + mx = vp.x + vp.w; + my = vp.y + vp.h; + + if ((x + s.vp_offset_x + w) > mx) + x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); + + if ((y + s.vp_offset_y + h) > my) + y = Math.max(0, (my - s.vp_offset_y) - h); + } + + DOM.setStyles(co, {left : x , top : y}); + t.element.update(); + + t.isMenuVisible = 1; + t.mouseClickFunc = Event.add(co, 'click', function(e) { + var m; + + e = e.target; + + if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { + m = t.items[e.id]; + + if (m.isDisabled()) + return; + + dm = t; + + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + + if (m.settings.onclick) + m.settings.onclick(e); + + return Event.cancel(e); // Cancel to fix onbeforeunload problem + } + }); + + if (t.hasMenus()) { + t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { + var m, r, mi; + + e = e.target; + if (e && (e = DOM.getParent(e, 'tr'))) { + m = t.items[e.id]; + + if (t.lastMenu) + t.lastMenu.collapse(1); + + if (m.isDisabled()) + return; + + if (e && DOM.hasClass(e, cp + 'ItemSub')) { + //p = DOM.getPos(s.container); + r = DOM.getRect(e); + m.showMenu((r.x + r.w - ot), r.y - ot, r.x); + t.lastMenu = m; + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); + } + } + }); + } + + Event.add(co, 'keydown', t._keyHandler, t); + + t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + t._setupKeyboardNav(); + } + }, + + /** + * Hides the displayed menu. + * + * @method hideMenu + */ + hideMenu : function(c) { + var t = this, co = DOM.get('menu_' + t.id), e; + + if (!t.isMenuVisible) + return; + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + DOM.hide(co); + t.isMenuVisible = 0; + + if (!c) + t.collapse(1); + + if (t.element) + t.element.hide(); + + if (e = DOM.get(t.id)) + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); + + t.onHideMenu.dispatch(t); + }, + + /** + * Adds a new menu, menu item or sub classes of them to the drop menu. + * + * @method add + * @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu. + * @return {tinymce.ui.Control} Same as the input control, the menu or menu item. + */ + add : function(o) { + var t = this, co; + + o = t.parent(o); + + if (t.isRendered && (co = DOM.get('menu_' + t.id))) + t._add(DOM.select('tbody', co)[0], o); + + return o; + }, + + /** + * Collapses the menu, this will hide the menu and all menu items. + * + * @method collapse + * @param {Boolean} d Optional deep state. If this is set to true all children will be collapsed as well. + */ + collapse : function(d) { + this.parent(d); + this.hideMenu(1); + }, + + /** + * Removes a specific sub menu or menu item from the drop menu. + * + * @method remove + * @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu. + * @return {tinymce.ui.Control} Control instance or null if it wasn't found. + */ + remove : function(o) { + DOM.remove(o.id); + this.destroy(); + + return this.parent(o); + }, + + /** + * Destroys the menu. This will remove the menu from the DOM and any events added to it etc. + * + * @method destroy + */ + destroy : function() { + var t = this, co = DOM.get('menu_' + t.id); + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + + if (t.element) + t.element.remove(); + + DOM.remove(co); + }, + + /** + * Renders the specified menu node to the dom. + * + * @method renderNode + * @return {Element} Container element for the drop menu. + */ + renderNode : function() { + var t = this, s = t.settings, n, tb, co, w; + + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + + if (s.menu_line) + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); + +// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); + tb = DOM.add(n, 'tbody'); + + each(t.items, function(o) { + t._add(tb, o); + }); + + t.rendered = true; + + return w; + }, + + // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.select('#menu_' + t.id)[0]; + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, + + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; + } + }, + + _add : function(tb, o) { + var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; + + if (s.separator) { + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); + + if (n = ro.previousSibling) + DOM.addClass(n, 'mceLast'); + + return; + } + + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } + + DOM.addClass(it, s['class']); +// n = DOM.add(n, 'span', {'class' : 'item'}); + + ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); + + if (s.icon_src) + DOM.add(ic, 'img', {src : s.icon_src}); + + n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); + + if (o.settings.style) + DOM.setAttrib(n, 'style', o.settings.style); + + if (tb.childNodes.length == 1) + DOM.addClass(ro, 'mceFirst'); + + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) + DOM.addClass(ro, 'mceFirst'); + + if (o.collapse) + DOM.addClass(ro, cp + 'ItemSub'); + + if (n = ro.previousSibling) + DOM.removeClass(n, 'mceLast'); + + DOM.addClass(ro, 'mceLast'); + } + }); })(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/classes/ui/KeyboardNavigation.js b/js/tiny_mce/classes/ui/KeyboardNavigation.js new file mode 100644 index 00000000..04efbbc3 --- /dev/null +++ b/js/tiny_mce/classes/ui/KeyboardNavigation.js @@ -0,0 +1,183 @@ +/** + * KeyboardNavigation.js + * + * Copyright 2011, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + /** + * This class provides basic keyboard navigation using the arrow keys to children of a component. + * For example, this class handles moving between the buttons on the toolbars. + * + * @class tinymce.ui.KeyboardNavigation + */ + tinymce.create('tinymce.ui.KeyboardNavigation', { + /** + * Create a new KeyboardNavigation instance to handle the focus for a specific element. + * + * @constructor + * @method KeyboardNavigation + * @param {Object} settings the settings object to define how keyboard navigation works. + * @param {DOMUtils} dom the DOMUtils instance to use. + * + * @setting {Element/String} root the root element or ID of the root element for the control. + * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an id attribute which maps to the actual DOM element. If the actual elements are passed without an ID then one is automatically assigned. + * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates cancelling. + * @setting {Function} onAction (optional) the action handler to call when the user activates an item. + * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items. + * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items. + * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true. + */ + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + /** + * Destroys the KeyboardNavigation and unbinds any focus/blur event handles it might have added. + * + * @method destroy + */ + t.destroy = function() { + each(items, function(item) { + dom.unbind(dom.get(item.id), 'focus', itemFocussed); + dom.unbind(dom.get(item.id), 'blur', itemBlurred); + }); + + dom.unbind(dom.get(root), 'focus', rootFocussed); + dom.unbind(dom.get(root), 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + if (excludeFromTabOrder) { + dom.bind(item.id, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + dom.setAttrib(item.id, 'tabindex', tabindex); + dom.bind(dom.get(item.id), 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + dom.bind(dom.get(root), 'focus', rootFocussed); + dom.bind(dom.get(root), 'keydown', rootKeydown); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/ListBox.js b/js/tiny_mce/classes/ui/ListBox.js index 44549105..5acbd76f 100644 --- a/js/tiny_mce/classes/ui/ListBox.js +++ b/js/tiny_mce/classes/ui/ListBox.js @@ -1,382 +1,428 @@ -/** - * ListBox.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; - - /** - * This class is used to create list boxes/select list. This one will generate - * a non native control. This one has the benefits of having visual items added. - * - * @class tinymce.ui.ListBox - * @extends tinymce.ui.Control - */ - tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { - /** - * Constructs a new listbox control instance. - * - * @constructor - * @method ListBox - * @param {String} id Control id for the list box. - * @param {Object} s Optional name/value settings object. - */ - ListBox : function(id, s) { - var t = this; - - t.parent(id, s); - - /** - * Array of ListBox items. - * - * @property items - * @type Array - */ - t.items = []; - - /** - * Fires when the selection has been changed. - * - * @event onChange - */ - t.onChange = new Dispatcher(t); - - /** - * Fires after the element has been rendered to DOM. - * - * @event onPostRender - */ - t.onPostRender = new Dispatcher(t); - - /** - * Fires when a new item is added. - * - * @event onAdd - */ - t.onAdd = new Dispatcher(t); - - /** - * Fires when the menu gets rendered. - * - * @event onRenderMenu - */ - t.onRenderMenu = new tinymce.util.Dispatcher(this); - - t.classPrefix = 'mceListBox'; - }, - - /** - * Selects a item/option by value. This will both add a visual selection to the - * item and change the title of the control to the title of the option. - * - * @method select - * @param {String/function} va Value to look for inside the list box or a function selector. - */ - select : function(va) { - var t = this, fv, f; - - if (va == undefined) - return t.selectByIndex(-1); - - // Is string or number make function selector - if (va && va.call) - f = va; - else { - f = function(v) { - return v == va; - }; - } - - // Do we need to do something? - if (va != t.selectedValue) { - // Find item - each(t.items, function(o, i) { - if (f(o.value)) { - fv = 1; - t.selectByIndex(i); - return false; - } - }); - - if (!fv) - t.selectByIndex(-1); - } - }, - - /** - * Selects a item/option by index. This will both add a visual selection to the - * item and change the title of the control to the title of the option. - * - * @method selectByIndex - * @param {String} idx Index to select, pass -1 to select menu/title of select box. - */ - selectByIndex : function(idx) { - var t = this, e, o; - - if (idx != t.selectedIndex) { - e = DOM.get(t.id + '_text'); - o = t.items[idx]; - - if (o) { - t.selectedValue = o.value; - t.selectedIndex = idx; - DOM.setHTML(e, DOM.encode(o.title)); - DOM.removeClass(e, 'mceTitle'); - } else { - DOM.setHTML(e, DOM.encode(t.settings.title)); - DOM.addClass(e, 'mceTitle'); - t.selectedValue = t.selectedIndex = null; - } - - e = 0; - } - }, - - /** - * Adds a option item to the list box. - * - * @method add - * @param {String} n Title for the new option. - * @param {String} v Value for the new option. - * @param {Object} o Optional object with settings like for example class. - */ - add : function(n, v, o) { - var t = this; - - o = o || {}; - o = tinymce.extend(o, { - title : n, - value : v - }); - - t.items.push(o); - t.onAdd.dispatch(t, o); - }, - - /** - * Returns the number of items inside the list box. - * - * @method getLength - * @param {Number} Number of items inside the list box. - */ - getLength : function() { - return this.items.length; - }, - - /** - * Renders the list box as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the list box control element. - */ - renderHTML : function() { - var h = '', t = this, s = t.settings, cp = t.classPrefix; - - h = ''; - h += ''; - h += ''; - h += '
    ' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '') + '
    '; - - return h; - }, - - /** - * Displays the drop menu with all items. - * - * @method showMenu - */ - showMenu : function() { - var t = this, p1, p2, e = DOM.get(this.id), m; - - if (t.isDisabled() || t.items.length == 0) - return; - - if (t.menu && t.menu.isMenuVisible) - return t.hideMenu(); - - if (!t.isMenuRendered) { - t.renderMenu(); - t.isMenuRendered = true; - } - - p1 = DOM.getPos(this.settings.menu_container); - p2 = DOM.getPos(e); - - m = t.menu; - m.settings.offset_x = p2.x; - m.settings.offset_y = p2.y; - m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus - - // Select in menu - if (t.oldID) - m.items[t.oldID].setSelected(0); - - each(t.items, function(o) { - if (o.value === t.selectedValue) { - m.items[o.id].setSelected(1); - t.oldID = o.id; - } - }); - - m.showMenu(0, e.clientHeight); - - Event.add(DOM.doc, 'mousedown', t.hideMenu, t); - DOM.addClass(t.id, t.classPrefix + 'Selected'); - - //DOM.get(t.id + '_text').focus(); - }, - - /** - * Hides the drop menu. - * - * @method hideMenu - */ - hideMenu : function(e) { - var t = this; - - if (t.menu && t.menu.isMenuVisible) { - // Prevent double toogles by canceling the mouse click event to the button - if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) - return; - - if (!e || !DOM.getParent(e.target, '.mceMenu')) { - DOM.removeClass(t.id, t.classPrefix + 'Selected'); - Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); - t.menu.hideMenu(); - } - } - }, - - /** - * Renders the menu to the DOM. - * - * @method renderMenu - */ - renderMenu : function() { - var t = this, m; - - m = t.settings.control_manager.createDropMenu(t.id + '_menu', { - menu_line : 1, - 'class' : t.classPrefix + 'Menu mceNoIcons', - max_width : 150, - max_height : 150 - }); - - m.onHideMenu.add(t.hideMenu, t); - - m.add({ - title : t.settings.title, - 'class' : 'mceMenuItemTitle', - onclick : function() { - if (t.settings.onselect('') !== false) - t.select(''); // Must be runned after - } - }); - - each(t.items, function(o) { - // No value then treat it as a title - if (o.value === undefined) { - m.add({ - title : o.title, - 'class' : 'mceMenuItemTitle', - onclick : function() { - if (t.settings.onselect('') !== false) - t.select(''); // Must be runned after - } - }); - } else { - o.id = DOM.uniqueId(); - o.onclick = function() { - if (t.settings.onselect(o.value) !== false) - t.select(o.value); // Must be runned after - }; - - m.add(o); - } - }); - - t.onRenderMenu.dispatch(t, m); - t.menu = m; - }, - - /** - * Post render event. This will be executed after the control has been rendered and can be used to - * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). - * - * @method postRender - */ - postRender : function() { - var t = this, cp = t.classPrefix; - - Event.add(t.id, 'click', t.showMenu, t); - Event.add(t.id + '_text', 'focus', function(e) { - if (!t._focused) { - t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) { - var idx = -1, v, kc = e.keyCode; - - // Find current index - each(t.items, function(v, i) { - if (t.selectedValue == v.value) - idx = i; - }); - - // Move up/down - if (kc == 38) - v = t.items[idx - 1]; - else if (kc == 40) - v = t.items[idx + 1]; - else if (kc == 13) { - // Fake select on enter - v = t.selectedValue; - t.selectedValue = null; // Needs to be null to fake change - t.settings.onselect(v); - return Event.cancel(e); - } - - if (v) { - t.hideMenu(); - t.select(v.value); - } - }); - } - - t._focused = 1; - }); - Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;}); - - // Old IE doesn't have hover on all elements - if (tinymce.isIE6 || !DOM.boxModel) { - Event.add(t.id, 'mouseover', function() { - if (!DOM.hasClass(t.id, cp + 'Disabled')) - DOM.addClass(t.id, cp + 'Hover'); - }); - - Event.add(t.id, 'mouseout', function() { - if (!DOM.hasClass(t.id, cp + 'Disabled')) - DOM.removeClass(t.id, cp + 'Hover'); - }); - } - - t.onPostRender.dispatch(t, DOM.get(t.id)); - }, - - /** - * Destroys the ListBox i.e. clear memory and events. - * - * @method destroy - */ - destroy : function() { - this.parent(); - - Event.clear(this.id + '_text'); - Event.clear(this.id + '_open'); - } - }); -})(tinymce); \ No newline at end of file +/** + * ListBox.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + /** + * This class is used to create list boxes/select list. This one will generate + * a non native control. This one has the benefits of having visual items added. + * + * @class tinymce.ui.ListBox + * @extends tinymce.ui.Control + * @example + * // Creates a new plugin class and a custom listbox + * tinymce.create('tinymce.plugins.ExamplePlugin', { + * createControl: function(n, cm) { + * switch (n) { + * case 'mylistbox': + * var mlb = cm.createListBox('mylistbox', { + * title : 'My list box', + * onselect : function(v) { + * tinyMCE.activeEditor.windowManager.alert('Value selected:' + v); + * } + * }); + * + * // Add some values to the list box + * mlb.add('Some item 1', 'val1'); + * mlb.add('some item 2', 'val2'); + * mlb.add('some item 3', 'val3'); + * + * // Return the new listbox instance + * return mlb; + * } + * + * return null; + * } + * }); + * + * // Register plugin with a short name + * tinymce.PluginManager.add('example', tinymce.plugins.ExamplePlugin); + * + * // Initialize TinyMCE with the new plugin and button + * tinyMCE.init({ + * ... + * plugins : '-example', // - means TinyMCE will not try to load it + * theme_advanced_buttons1 : 'mylistbox' // Add the new example listbox to the toolbar + * }); + */ + tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { + /** + * Constructs a new listbox control instance. + * + * @constructor + * @method ListBox + * @param {String} id Control id for the list box. + * @param {Object} s Optional name/value settings object. + * @param {Editor} ed Optional the editor instance this button is for. + */ + ListBox : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + /** + * Array of ListBox items. + * + * @property items + * @type Array + */ + t.items = []; + + /** + * Fires when the selection has been changed. + * + * @event onChange + */ + t.onChange = new Dispatcher(t); + + /** + * Fires after the element has been rendered to DOM. + * + * @event onPostRender + */ + t.onPostRender = new Dispatcher(t); + + /** + * Fires when a new item is added. + * + * @event onAdd + */ + t.onAdd = new Dispatcher(t); + + /** + * Fires when the menu gets rendered. + * + * @event onRenderMenu + */ + t.onRenderMenu = new tinymce.util.Dispatcher(this); + + t.classPrefix = 'mceListBox'; + }, + + /** + * Selects a item/option by value. This will both add a visual selection to the + * item and change the title of the control to the title of the option. + * + * @method select + * @param {String/function} va Value to look for inside the list box or a function selector. + */ + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + /** + * Selects a item/option by index. This will both add a visual selection to the + * item and change the title of the control to the title of the option. + * + * @method selectByIndex + * @param {String} idx Index to select, pass -1 to select menu/title of select box. + */ + selectByIndex : function(idx) { + var t = this, e, o, label; + + if (idx != t.selectedIndex) { + e = DOM.get(t.id + '_text'); + label = DOM.get(t.id + '_voiceDesc'); + o = t.items[idx]; + + if (o) { + t.selectedValue = o.value; + t.selectedIndex = idx; + DOM.setHTML(e, DOM.encode(o.title)); + DOM.setHTML(label, t.settings.title + " - " + o.title); + DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); + } else { + DOM.setHTML(e, DOM.encode(t.settings.title)); + DOM.setHTML(label, DOM.encode(t.settings.title)); + DOM.addClass(e, 'mceTitle'); + t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); + } + e = 0; + } + }, + + /** + * Adds a option item to the list box. + * + * @method add + * @param {String} n Title for the new option. + * @param {String} v Value for the new option. + * @param {Object} o Optional object with settings like for example class. + */ + add : function(n, v, o) { + var t = this; + + o = o || {}; + o = tinymce.extend(o, { + title : n, + value : v + }); + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + /** + * Returns the number of items inside the list box. + * + * @method getLength + * @param {Number} Number of items inside the list box. + */ + getLength : function() { + return this.items.length; + }, + + /** + * Renders the list box as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the list box control element. + */ + renderHTML : function() { + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = ''; + h += ''; + h += ''; + h += ''; + + return h; + }, + + /** + * Displays the drop menu with all items. + * + * @method showMenu + */ + showMenu : function() { + var t = this, p2, e = DOM.get(this.id), m; + + if (t.isDisabled() || t.items.length == 0) + return; + + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus + + // Select in menu + if (t.oldID) + m.items[t.oldID].setSelected(0); + + each(t.items, function(o) { + if (o.value === t.selectedValue) { + m.items[o.id].setSelected(1); + t.oldID = o.id; + } + }); + + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); + + //DOM.get(t.id + '_text').focus(); + }, + + /** + * Hides the drop menu. + * + * @method hideMenu + */ + hideMenu : function(e) { + var t = this; + + if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + t.menu.hideMenu(); + } + } + }, + + /** + * Renders the menu to the DOM. + * + * @method renderMenu + */ + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : t.classPrefix + 'Menu mceNoIcons', + max_width : 150, + max_height : 150 + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + m.add({ + title : t.settings.title, + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + + each(t.items, function(o) { + // No value then treat it as a title + if (o.value === undefined) { + m.add({ + title : o.title, + role : "option", + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + } else { + o.id = DOM.uniqueId(); + o.role= "option"; + o.onclick = function() { + if (t.settings.onselect(o.value) !== false) + t.select(o.value); // Must be runned after + }; + + m.add(o); + } + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + /** + * Post render event. This will be executed after the control has been rendered and can be used to + * set states, add events to the control etc. It's recommended for subclasses of the control to call this method by using this.parent(). + * + * @method postRender + */ + postRender : function() { + var t = this, cp = t.classPrefix; + + Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { + if (!t._focused) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { + // Fake select on enter + v = t.selectedValue; + t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); + t.settings.onselect(v); + } + }); + } + + t._focused = 1; + }); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); + }); + } + + t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + /** + * Destroys the ListBox i.e. clear memory and events. + * + * @method destroy + */ + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); + Event.clear(this.id + '_open'); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/MenuButton.js b/js/tiny_mce/classes/ui/MenuButton.js index 6633bd4a..5c121ca0 100644 --- a/js/tiny_mce/classes/ui/MenuButton.js +++ b/js/tiny_mce/classes/ui/MenuButton.js @@ -1,142 +1,176 @@ -/** - * MenuButton.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; - - /** - * This class is used to create a UI button. A button is basically a link - * that is styled to look like a button or icon. - * - * @class tinymce.ui.MenuButton - * @extends tinymce.ui.Control - */ - tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { - /** - * Constructs a new split button control instance. - * - * @constructor - * @method MenuButton - * @param {String} id Control id for the split button. - * @param {Object} s Optional name/value settings object. - */ - MenuButton : function(id, s) { - this.parent(id, s); - - /** - * Fires when the menu is rendered. - * - * @event onRenderMenu - */ - this.onRenderMenu = new tinymce.util.Dispatcher(this); - - s.menu_container = s.menu_container || DOM.doc.body; - }, - - /** - * Shows the menu. - * - * @method showMenu - */ - showMenu : function() { - var t = this, p1, p2, e = DOM.get(t.id), m; - - if (t.isDisabled()) - return; - - if (!t.isMenuRendered) { - t.renderMenu(); - t.isMenuRendered = true; - } - - if (t.isMenuVisible) - return t.hideMenu(); - - p1 = DOM.getPos(t.settings.menu_container); - p2 = DOM.getPos(e); - - m = t.menu; - m.settings.offset_x = p2.x; - m.settings.offset_y = p2.y; - m.settings.vp_offset_x = p2.x; - m.settings.vp_offset_y = p2.y; - m.settings.keyboard_focus = t._focused; - m.showMenu(0, e.clientHeight); - - Event.add(DOM.doc, 'mousedown', t.hideMenu, t); - t.setState('Selected', 1); - - t.isMenuVisible = 1; - }, - - /** - * Renders the menu to the DOM. - * - * @method renderMenu - */ - renderMenu : function() { - var t = this, m; - - m = t.settings.control_manager.createDropMenu(t.id + '_menu', { - menu_line : 1, - 'class' : this.classPrefix + 'Menu', - icons : t.settings.icons - }); - - m.onHideMenu.add(t.hideMenu, t); - - t.onRenderMenu.dispatch(t, m); - t.menu = m; - }, - - /** - * Hides the menu. The optional event parameter is used to check where the event occured so it - * doesn't close them menu if it was a event inside the menu. - * - * @method hideMenu - * @param {Event} e Optional event object. - */ - hideMenu : function(e) { - var t = this; - - // Prevent double toogles by canceling the mouse click event to the button - if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) - return; - - if (!e || !DOM.getParent(e.target, '.mceMenu')) { - t.setState('Selected', 0); - Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); - if (t.menu) - t.menu.hideMenu(); - } - - t.isMenuVisible = 0; - }, - - /** - * Post render handler. This function will be called after the UI has been - * rendered so that events can be added. - * - * @method postRender - */ - postRender : function() { - var t = this, s = t.settings; - - Event.add(t.id, 'click', function() { - if (!t.isDisabled()) { - if (s.onclick) - s.onclick(t.value); - - t.showMenu(); - } - }); - } - }); -})(tinymce); +/** + * MenuButton.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + /** + * This class is used to create a UI button. A button is basically a link + * that is styled to look like a button or icon. + * + * @class tinymce.ui.MenuButton + * @extends tinymce.ui.Control + * @example + * // Creates a new plugin class and a custom menu button + * tinymce.create('tinymce.plugins.ExamplePlugin', { + * createControl: function(n, cm) { + * switch (n) { + * case 'mymenubutton': + * var c = cm.createSplitButton('mysplitbutton', { + * title : 'My menu button', + * image : 'some.gif' + * }); + * + * c.onRenderMenu.add(function(c, m) { + * m.add({title : 'Some title', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + * + * m.add({title : 'Some item 1', onclick : function() { + * alert('Some item 1 was clicked.'); + * }}); + * + * m.add({title : 'Some item 2', onclick : function() { + * alert('Some item 2 was clicked.'); + * }}); + * }); + * + * // Return the new menubutton instance + * return c; + * } + * + * return null; + * } + * }); + */ + tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { + /** + * Constructs a new split button control instance. + * + * @constructor + * @method MenuButton + * @param {String} id Control id for the split button. + * @param {Object} s Optional name/value settings object. + * @param {Editor} ed Optional the editor instance this button is for. + */ + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); + + /** + * Fires when the menu is rendered. + * + * @event onRenderMenu + */ + this.onRenderMenu = new tinymce.util.Dispatcher(this); + + s.menu_container = s.menu_container || DOM.doc.body; + }, + + /** + * Shows the menu. + * + * @method showMenu + */ + showMenu : function() { + var t = this, p1, p2, e = DOM.get(t.id), m; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + p1 = DOM.getPos(t.settings.menu_container); + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.vp_offset_x = p2.x; + m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.setState('Selected', 1); + + t.isMenuVisible = 1; + }, + + /** + * Renders the menu to the DOM. + * + * @method renderMenu + */ + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : this.classPrefix + 'Menu', + icons : t.settings.icons + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + /** + * Hides the menu. The optional event parameter is used to check where the event occured so it + * doesn't close them menu if it was a event inside the menu. + * + * @method hideMenu + * @param {Event} e Optional event object. + */ + hideMenu : function(e) { + var t = this; + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + t.setState('Selected', 0); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + if (t.menu) + t.menu.hideMenu(); + } + + t.isMenuVisible = 0; + }, + + /** + * Post render handler. This function will be called after the UI has been + * rendered so that events can be added. + * + * @method postRender + */ + postRender : function() { + var t = this, s = t.settings; + + Event.add(t.id, 'click', function() { + if (!t.isDisabled()) { + if (s.onclick) + s.onclick(t.value); + + t.showMenu(); + } + }); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/MenuItem.js b/js/tiny_mce/classes/ui/MenuItem.js index af76922e..a6a145bc 100644 --- a/js/tiny_mce/classes/ui/MenuItem.js +++ b/js/tiny_mce/classes/ui/MenuItem.js @@ -1,73 +1,74 @@ -/** - * MenuItem.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; - - /** - * This class is base class for all menu item types like DropMenus items etc. This class should not - * be instantiated directly other menu items should inherit from this one. - * - * @class tinymce.ui.MenuItem - * @extends tinymce.ui.Control - */ - tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { - /** - * Constructs a new button control instance. - * - * @constructor - * @method MenuItem - * @param {String} id Button control id for the button. - * @param {Object} s Optional name/value settings object. - */ - MenuItem : function(id, s) { - this.parent(id, s); - this.classPrefix = 'mceMenuItem'; - }, - - /** - * Sets the selected state for the control. This will add CSS classes to the - * element that contains the control. So that it can be selected visually. - * - * @method setSelected - * @param {Boolean} s Boolean state if the control should be selected or not. - */ - setSelected : function(s) { - this.setState('Selected', s); - this.selected = s; - }, - - /** - * Returns true/false if the control is selected or not. - * - * @method isSelected - * @return {Boolean} true/false if the control is selected or not. - */ - isSelected : function() { - return this.selected; - }, - - /** - * Post render handler. This function will be called after the UI has been - * rendered so that events can be added. - * - * @method postRender - */ - postRender : function() { - var t = this; - - t.parent(); - - // Set pending state - if (is(t.selected)) - t.setSelected(t.selected); - } - }); -})(tinymce); +/** + * MenuItem.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + /** + * This class is base class for all menu item types like DropMenus items etc. This class should not + * be instantiated directly other menu items should inherit from this one. + * + * @class tinymce.ui.MenuItem + * @extends tinymce.ui.Control + */ + tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { + /** + * Constructs a new button control instance. + * + * @constructor + * @method MenuItem + * @param {String} id Button control id for the button. + * @param {Object} s Optional name/value settings object. + */ + MenuItem : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceMenuItem'; + }, + + /** + * Sets the selected state for the control. This will add CSS classes to the + * element that contains the control. So that it can be selected visually. + * + * @method setSelected + * @param {Boolean} s Boolean state if the control should be selected or not. + */ + setSelected : function(s) { + this.setState('Selected', s); + this.setAriaProperty('checked', !!s); + this.selected = s; + }, + + /** + * Returns true/false if the control is selected or not. + * + * @method isSelected + * @return {Boolean} true/false if the control is selected or not. + */ + isSelected : function() { + return this.selected; + }, + + /** + * Post render handler. This function will be called after the UI has been + * rendered so that events can be added. + * + * @method postRender + */ + postRender : function() { + var t = this; + + t.parent(); + + // Set pending state + if (is(t.selected)) + t.setSelected(t.selected); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/NativeListBox.js b/js/tiny_mce/classes/ui/NativeListBox.js index 1689e05f..32cfac01 100644 --- a/js/tiny_mce/classes/ui/NativeListBox.js +++ b/js/tiny_mce/classes/ui/NativeListBox.js @@ -1,208 +1,217 @@ -/** - * NativeListBox.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; - - /** - * This class is used to create list boxes/select list. This one will generate - * a native control the way that the browser produces them by default. - * - * @class tinymce.ui.NativeListBox - * @extends tinymce.ui.ListBox - */ - tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { - /** - * Constructs a new button control instance. - * - * @constructor - * @method NativeListBox - * @param {String} id Button control id for the button. - * @param {Object} s Optional name/value settings object. - */ - NativeListBox : function(id, s) { - this.parent(id, s); - this.classPrefix = 'mceNativeListBox'; - }, - - /** - * Sets the disabled state for the control. This will add CSS classes to the - * element that contains the control. So that it can be disabled visually. - * - * @method setDisabled - * @param {Boolean} s Boolean state if the control should be disabled or not. - */ - setDisabled : function(s) { - DOM.get(this.id).disabled = s; - }, - - /** - * Returns true/false if the control is disabled or not. This is a method since you can then - * choose to check some class or some internal bool state in subclasses. - * - * @method isDisabled - * @return {Boolean} true/false if the control is disabled or not. - */ - isDisabled : function() { - return DOM.get(this.id).disabled; - }, - - /** - * Selects a item/option by value. This will both add a visual selection to the - * item and change the title of the control to the title of the option. - * - * @method select - * @param {String/function} va Value to look for inside the list box or a function selector. - */ - select : function(va) { - var t = this, fv, f; - - if (va == undefined) - return t.selectByIndex(-1); - - // Is string or number make function selector - if (va && va.call) - f = va; - else { - f = function(v) { - return v == va; - }; - } - - // Do we need to do something? - if (va != t.selectedValue) { - // Find item - each(t.items, function(o, i) { - if (f(o.value)) { - fv = 1; - t.selectByIndex(i); - return false; - } - }); - - if (!fv) - t.selectByIndex(-1); - } - }, - - /** - * Selects a item/option by index. This will both add a visual selection to the - * item and change the title of the control to the title of the option. - * - * @method selectByIndex - * @param {String} idx Index to select, pass -1 to select menu/title of select box. - */ - selectByIndex : function(idx) { - DOM.get(this.id).selectedIndex = idx + 1; - this.selectedValue = this.items[idx] ? this.items[idx].value : null; - }, - - /** - * Adds a option item to the list box. - * - * @method add - * @param {String} n Title for the new option. - * @param {String} v Value for the new option. - * @param {Object} o Optional object with settings like for example class. - */ - add : function(n, v, a) { - var o, t = this; - - a = a || {}; - a.value = v; - - if (t.isRendered()) - DOM.add(DOM.get(this.id), 'option', a, n); - - o = { - title : n, - value : v, - attribs : a - }; - - t.items.push(o); - t.onAdd.dispatch(t, o); - }, - - /** - * Executes the specified callback function for the menu item. In this case when the user clicks the menu item. - * - * @method getLength - */ - getLength : function() { - return this.items.length; - }, - - /** - * Renders the list box as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the list box control element. - */ - renderHTML : function() { - var h, t = this; - - h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); - - each(t.items, function(it) { - h += DOM.createHTML('option', {value : it.value}, it.title); - }); - - h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h); - - return h; - }, - - /** - * Post render handler. This function will be called after the UI has been - * rendered so that events can be added. - * - * @method postRender - */ - postRender : function() { - var t = this, ch; - - t.rendered = true; - - function onChange(e) { - var v = t.items[e.target.selectedIndex - 1]; - - if (v && (v = v.value)) { - t.onChange.dispatch(t, v); - - if (t.settings.onselect) - t.settings.onselect(v); - } - }; - - Event.add(t.id, 'change', onChange); - - // Accessibility keyhandler - Event.add(t.id, 'keydown', function(e) { - var bf; - - Event.remove(t.id, 'change', ch); - - bf = Event.add(t.id, 'blur', function() { - Event.add(t.id, 'change', onChange); - Event.remove(t.id, 'blur', bf); - }); - - if (e.keyCode == 13 || e.keyCode == 32) { - onChange(e); - return Event.cancel(e); - } - }); - - t.onPostRender.dispatch(t, DOM.get(t.id)); - } - }); -})(tinymce); \ No newline at end of file +/** + * NativeListBox.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + /** + * This class is used to create list boxes/select list. This one will generate + * a native control the way that the browser produces them by default. + * + * @class tinymce.ui.NativeListBox + * @extends tinymce.ui.ListBox + */ + tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { + /** + * Constructs a new button control instance. + * + * @constructor + * @method NativeListBox + * @param {String} id Button control id for the button. + * @param {Object} s Optional name/value settings object. + */ + NativeListBox : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceNativeListBox'; + }, + + /** + * Sets the disabled state for the control. This will add CSS classes to the + * element that contains the control. So that it can be disabled visually. + * + * @method setDisabled + * @param {Boolean} s Boolean state if the control should be disabled or not. + */ + setDisabled : function(s) { + DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); + }, + + /** + * Returns true/false if the control is disabled or not. This is a method since you can then + * choose to check some class or some internal bool state in subclasses. + * + * @method isDisabled + * @return {Boolean} true/false if the control is disabled or not. + */ + isDisabled : function() { + return DOM.get(this.id).disabled; + }, + + /** + * Selects a item/option by value. This will both add a visual selection to the + * item and change the title of the control to the title of the option. + * + * @method select + * @param {String/function} va Value to look for inside the list box or a function selector. + */ + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + /** + * Selects a item/option by index. This will both add a visual selection to the + * item and change the title of the control to the title of the option. + * + * @method selectByIndex + * @param {String} idx Index to select, pass -1 to select menu/title of select box. + */ + selectByIndex : function(idx) { + DOM.get(this.id).selectedIndex = idx + 1; + this.selectedValue = this.items[idx] ? this.items[idx].value : null; + }, + + /** + * Adds a option item to the list box. + * + * @method add + * @param {String} n Title for the new option. + * @param {String} v Value for the new option. + * @param {Object} o Optional object with settings like for example class. + */ + add : function(n, v, a) { + var o, t = this; + + a = a || {}; + a.value = v; + + if (t.isRendered()) + DOM.add(DOM.get(this.id), 'option', a, n); + + o = { + title : n, + value : v, + attribs : a + }; + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + /** + * Executes the specified callback function for the menu item. In this case when the user clicks the menu item. + * + * @method getLength + */ + getLength : function() { + return this.items.length; + }, + + /** + * Renders the list box as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the list box control element. + */ + renderHTML : function() { + var h, t = this; + + h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); + + each(t.items, function(it) { + h += DOM.createHTML('option', {value : it.value}, it.title); + }); + + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); + return h; + }, + + /** + * Post render handler. This function will be called after the UI has been + * rendered so that events can be added. + * + * @method postRender + */ + postRender : function() { + var t = this, ch, changeListenerAdded = true; + + t.rendered = true; + + function onChange(e) { + var v = t.items[e.target.selectedIndex - 1]; + + if (v && (v = v.value)) { + t.onChange.dispatch(t, v); + + if (t.settings.onselect) + t.settings.onselect(v); + } + }; + + Event.add(t.id, 'change', onChange); + + // Accessibility keyhandler + Event.add(t.id, 'keydown', function(e) { + var bf; + + Event.remove(t.id, 'change', ch); + changeListenerAdded = false; + + bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; + Event.add(t.id, 'change', onChange); + Event.remove(t.id, 'blur', bf); + }); + + //prevent default left and right keys on chrome - so that the keyboard navigation is used. + if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { + return Event.prevent(e); + } + + if (e.keyCode == 13 || e.keyCode == 32) { + onChange(e); + return Event.cancel(e); + } + }); + + t.onPostRender.dispatch(t, DOM.get(t.id)); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/Separator.js b/js/tiny_mce/classes/ui/Separator.js index 8ecd6903..f254761d 100644 --- a/js/tiny_mce/classes/ui/Separator.js +++ b/js/tiny_mce/classes/ui/Separator.js @@ -1,41 +1,42 @@ -/** - * Separator.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -/** - * This class is used to create vertical separator between other controls. - * - * @class tinymce.ui.Separator - * @extends tinymce.ui.Control - */ -tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { - /** - * Separator constructor. - * - * @constructor - * @method Separator - * @param {String} id Control id to use for the Separator. - * @param {Object} s Optional name/value settings object. - */ - Separator : function(id, s) { - this.parent(id, s); - this.classPrefix = 'mceSeparator'; - }, - - /** - * Renders the separator as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the separator control element. - */ - renderHTML : function() { - return tinymce.DOM.createHTML('span', {'class' : this.classPrefix}); - } -}); +/** + * Separator.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +/** + * This class is used to create vertical separator between other controls. + * + * @class tinymce.ui.Separator + * @extends tinymce.ui.Control + */ +tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + /** + * Separator constructor. + * + * @constructor + * @method Separator + * @param {String} id Control id to use for the Separator. + * @param {Object} s Optional name/value settings object. + */ + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + this.setDisabled(true); + }, + + /** + * Renders the separator as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the separator control element. + */ + renderHTML : function() { + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); + } +}); diff --git a/js/tiny_mce/classes/ui/SplitButton.js b/js/tiny_mce/classes/ui/SplitButton.js index 945f5449..5af3808d 100644 --- a/js/tiny_mce/classes/ui/SplitButton.js +++ b/js/tiny_mce/classes/ui/SplitButton.js @@ -1,102 +1,154 @@ -/** - * SplitButton.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; - - /** - * This class is used to create a split button. A button with a menu attached to it. - * - * @class tinymce.ui.SplitButton - * @extends tinymce.ui.Button - */ - tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { - /** - * Constructs a new split button control instance. - * - * @constructor - * @method SplitButton - * @param {String} id Control id for the split button. - * @param {Object} s Optional name/value settings object. - */ - SplitButton : function(id, s) { - this.parent(id, s); - this.classPrefix = 'mceSplitButton'; - }, - - /** - * Renders the split button as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the split button control element. - */ - renderHTML : function() { - var h, t = this, s = t.settings, h1; - - h = ''; - - if (s.image) - h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']}); - else - h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); - - h += '' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; - - h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}); - h += '' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; - - h += ''; - - return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h); - }, - - /** - * Post render handler. This function will be called after the UI has been - * rendered so that events can be added. - * - * @method postRender - */ - postRender : function() { - var t = this, s = t.settings; - - if (s.onclick) { - Event.add(t.id + '_action', 'click', function() { - if (!t.isDisabled()) - s.onclick(t.value); - }); - } - - Event.add(t.id + '_open', 'click', t.showMenu, t); - Event.add(t.id + '_open', 'focus', function() {t._focused = 1;}); - Event.add(t.id + '_open', 'blur', function() {t._focused = 0;}); - - // Old IE doesn't have hover on all elements - if (tinymce.isIE6 || !DOM.boxModel) { - Event.add(t.id, 'mouseover', function() { - if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) - DOM.addClass(t.id, 'mceSplitButtonHover'); - }); - - Event.add(t.id, 'mouseout', function() { - if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) - DOM.removeClass(t.id, 'mceSplitButtonHover'); - }); - } - }, - - destroy : function() { - this.parent(); - - Event.clear(this.id + '_action'); - Event.clear(this.id + '_open'); - } - }); -})(tinymce); +/** + * SplitButton.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + /** + * This class is used to create a split button. A button with a menu attached to it. + * + * @class tinymce.ui.SplitButton + * @extends tinymce.ui.Button + * @example + * // Creates a new plugin class and a custom split button + * tinymce.create('tinymce.plugins.ExamplePlugin', { + * createControl: function(n, cm) { + * switch (n) { + * case 'mysplitbutton': + * var c = cm.createSplitButton('mysplitbutton', { + * title : 'My split button', + * image : 'some.gif', + * onclick : function() { + * alert('Button was clicked.'); + * } + * }); + * + * c.onRenderMenu.add(function(c, m) { + * m.add({title : 'Some title', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + * + * m.add({title : 'Some item 1', onclick : function() { + * alert('Some item 1 was clicked.'); + * }}); + * + * m.add({title : 'Some item 2', onclick : function() { + * alert('Some item 2 was clicked.'); + * }}); + * }); + * + * // Return the new splitbutton instance + * return c; + * } + * + * return null; + * } + * }); + */ + tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { + /** + * Constructs a new split button control instance. + * + * @constructor + * @method SplitButton + * @param {String} id Control id for the split button. + * @param {Object} s Optional name/value settings object. + * @param {Editor} ed Optional the editor instance this button is for. + */ + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceSplitButton'; + }, + + /** + * Renders the split button as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the split button control element. + */ + renderHTML : function() { + var h, t = this, s = t.settings, h1; + + h = ''; + + if (s.image) + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); + else + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); + + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h += ''; + h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); + }, + + /** + * Post render handler. This function will be called after the UI has been + * rendered so that events can be added. + * + * @method postRender + */ + postRender : function() { + var t = this, s = t.settings, activate; + + if (s.onclick) { + activate = function(evt) { + if (!t.isDisabled()) { + s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } + }); + } + + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.addClass(t.id, 'mceSplitButtonHover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.removeClass(t.id, 'mceSplitButtonHover'); + }); + } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); + Event.clear(this.id); + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/Toolbar.js b/js/tiny_mce/classes/ui/Toolbar.js index 4c6142ed..a78e589f 100644 --- a/js/tiny_mce/classes/ui/Toolbar.js +++ b/js/tiny_mce/classes/ui/Toolbar.js @@ -1,85 +1,89 @@ -/** - * Toolbar.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -/** - * This class is used to create toolbars a toolbar is a container for other controls like buttons etc. - * - * @class tinymce.ui.Toolbar - * @extends tinymce.ui.Container - */ -tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { - /** - * Renders the toolbar as a HTML string. This method is much faster than using the DOM and when - * creating a whole toolbar with buttons it does make a lot of difference. - * - * @method renderHTML - * @return {String} HTML for the toolbar control. - */ - renderHTML : function() { - var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl; - - cl = t.controls; - for (i=0; i')); - } - - // Add toolbar end before list box and after the previous button - // This is to fix the o2k7 editor skins - if (pr && co.ListBox) { - if (pr.Button || pr.SplitButton) - h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); - } - - // Render control HTML - - // IE 8 quick fix, needed to propertly generate a hit area for anchors - if (dom.stdMode) - h += '' + co.renderHTML() + ''; - else - h += '' + co.renderHTML() + ''; - - // Add toolbar start after list box and before the next button - // This is to fix the o2k7 editor skins - if (nx && co.ListBox) { - if (nx.Button || nx.SplitButton) - h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); - } - } - - c = 'mceToolbarEnd'; - - if (co.Button) - c += ' mceToolbarEndButton'; - else if (co.SplitButton) - c += ' mceToolbarEndSplitButton'; - else if (co.ListBox) - c += ' mceToolbarEndListBox'; - - h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); - - return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '' + h + ''); - } -}); +/** + * Toolbar.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; +/** + * This class is used to create toolbars a toolbar is a container for other controls like buttons etc. + * + * @class tinymce.ui.Toolbar + * @extends tinymce.ui.Container + */ +tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { + /** + * Renders the toolbar as a HTML string. This method is much faster than using the DOM and when + * creating a whole toolbar with buttons it does make a lot of difference. + * + * @method renderHTML + * @return {String} HTML for the toolbar control. + */ + renderHTML : function() { + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) + c += ' mceToolbarEndButton'; + else if (co.SplitButton) + c += ' mceToolbarEndSplitButton'; + else if (co.ListBox) + c += ' mceToolbarEndListBox'; + + h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); + + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); + } +}); +})(tinymce); diff --git a/js/tiny_mce/classes/ui/ToolbarGroup.js b/js/tiny_mce/classes/ui/ToolbarGroup.js new file mode 100644 index 00000000..3bc74658 --- /dev/null +++ b/js/tiny_mce/classes/ui/ToolbarGroup.js @@ -0,0 +1,81 @@ +/** + * ToolbarGroup.js + * + * Copyright 2010, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +/** + * This class is used to group a set of toolbars together and control the keyboard navigation and focus. + * + * @class tinymce.ui.ToolbarGroup + * @extends tinymce.ui.Container + */ +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + /** + * Renders the toolbar group as a HTML string. + * + * @method renderHTML + * @return {String} HTML for the toolbar control. + */ + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + var t = this; + dom.get(t.id).focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + //Move focus if webkit so that navigation back will read the item. + if (tinymce.isWebKit) { + dom.get(t.editor.id+"_ifr").focus(); + } + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); diff --git a/js/tiny_mce/classes/util/Cookie.js b/js/tiny_mce/classes/util/Cookie.js index 8209bd70..4004df20 100644 --- a/js/tiny_mce/classes/util/Cookie.js +++ b/js/tiny_mce/classes/util/Cookie.js @@ -1,129 +1,138 @@ -/** - * Cookie.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var each = tinymce.each; - - /** - * This class contains simple cookie manangement functions. - * - * @class tinymce.util.Cookie - * @static - */ - tinymce.create('static tinymce.util.Cookie', { - /** - * Parses the specified query string into an name/value object. - * - * @method getHash - * @param {String} n String to parse into a n Hashtable object. - * @return {Object} Name/Value object with items parsed from querystring. - */ - getHash : function(n) { - var v = this.get(n), h; - - if (v) { - each(v.split('&'), function(v) { - v = v.split('='); - h = h || {}; - h[unescape(v[0])] = unescape(v[1]); - }); - } - - return h; - }, - - /** - * Sets a hashtable name/value object to a cookie. - * - * @method setHash - * @param {String} n Name of the cookie. - * @param {Object} v Hashtable object to set as cookie. - * @param {Date} e Optional date object for the expiration of the cookie. - * @param {String} p Optional path to restrict the cookie to. - * @param {String} d Optional domain to restrict the cookie to. - * @param {String} s Is the cookie secure or not. - */ - setHash : function(n, v, e, p, d, s) { - var o = ''; - - each(v, function(v, k) { - o += (!o ? '' : '&') + escape(k) + '=' + escape(v); - }); - - this.set(n, o, e, p, d, s); - }, - - /** - * Gets the raw data of a cookie by name. - * - * @method get - * @param {String} n Name of cookie to retrive. - * @return {String} Cookie data string. - */ - get : function(n) { - var c = document.cookie, e, p = n + "=", b; - - // Strict mode - if (!c) - return; - - b = c.indexOf("; " + p); - - if (b == -1) { - b = c.indexOf(p); - - if (b != 0) - return null; - } else - b += 2; - - e = c.indexOf(";", b); - - if (e == -1) - e = c.length; - - return unescape(c.substring(b + p.length, e)); - }, - - /** - * Sets a raw cookie string. - * - * @method set - * @param {String} n Name of the cookie. - * @param {String} v Raw cookie data. - * @param {Date} e Optional date object for the expiration of the cookie. - * @param {String} p Optional path to restrict the cookie to. - * @param {String} d Optional domain to restrict the cookie to. - * @param {String} s Is the cookie secure or not. - */ - set : function(n, v, e, p, d, s) { - document.cookie = n + "=" + escape(v) + - ((e) ? "; expires=" + e.toGMTString() : "") + - ((p) ? "; path=" + escape(p) : "") + - ((d) ? "; domain=" + d : "") + - ((s) ? "; secure" : ""); - }, - - /** - * Removes/deletes a cookie by name. - * - * @method remove - * @param {String} n Cookie name to remove/delete. - * @param {Strong} p Optional path to remove the cookie from. - */ - remove : function(n, p) { - var d = new Date(); - - d.setTime(d.getTime() - 1000); - - this.set(n, '', d, p, d); - } - }); -})(); +/** + * Cookie.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each; + + /** + * This class contains simple cookie manangement functions. + * + * @class tinymce.util.Cookie + * @static + * @example + * // Gets a cookie from the browser + * console.debug(tinymce.util.Cookie.get('mycookie')); + * + * // Gets a hash table cookie from the browser and takes out the x parameter from it + * console.debug(tinymce.util.Cookie.getHash('mycookie').x); + * + * // Sets a hash table cookie to the browser + * tinymce.util.Cookie.setHash({x : '1', y : '2'}); + */ + tinymce.create('static tinymce.util.Cookie', { + /** + * Parses the specified query string into an name/value object. + * + * @method getHash + * @param {String} n String to parse into a n Hashtable object. + * @return {Object} Name/Value object with items parsed from querystring. + */ + getHash : function(n) { + var v = this.get(n), h; + + if (v) { + each(v.split('&'), function(v) { + v = v.split('='); + h = h || {}; + h[unescape(v[0])] = unescape(v[1]); + }); + } + + return h; + }, + + /** + * Sets a hashtable name/value object to a cookie. + * + * @method setHash + * @param {String} n Name of the cookie. + * @param {Object} v Hashtable object to set as cookie. + * @param {Date} e Optional date object for the expiration of the cookie. + * @param {String} p Optional path to restrict the cookie to. + * @param {String} d Optional domain to restrict the cookie to. + * @param {String} s Is the cookie secure or not. + */ + setHash : function(n, v, e, p, d, s) { + var o = ''; + + each(v, function(v, k) { + o += (!o ? '' : '&') + escape(k) + '=' + escape(v); + }); + + this.set(n, o, e, p, d, s); + }, + + /** + * Gets the raw data of a cookie by name. + * + * @method get + * @param {String} n Name of cookie to retrive. + * @return {String} Cookie data string. + */ + get : function(n) { + var c = document.cookie, e, p = n + "=", b; + + // Strict mode + if (!c) + return; + + b = c.indexOf("; " + p); + + if (b == -1) { + b = c.indexOf(p); + + if (b != 0) + return null; + } else + b += 2; + + e = c.indexOf(";", b); + + if (e == -1) + e = c.length; + + return unescape(c.substring(b + p.length, e)); + }, + + /** + * Sets a raw cookie string. + * + * @method set + * @param {String} n Name of the cookie. + * @param {String} v Raw cookie data. + * @param {Date} e Optional date object for the expiration of the cookie. + * @param {String} p Optional path to restrict the cookie to. + * @param {String} d Optional domain to restrict the cookie to. + * @param {String} s Is the cookie secure or not. + */ + set : function(n, v, e, p, d, s) { + document.cookie = n + "=" + escape(v) + + ((e) ? "; expires=" + e.toGMTString() : "") + + ((p) ? "; path=" + escape(p) : "") + + ((d) ? "; domain=" + d : "") + + ((s) ? "; secure" : ""); + }, + + /** + * Removes/deletes a cookie by name. + * + * @method remove + * @param {String} n Cookie name to remove/delete. + * @param {Strong} p Optional path to remove the cookie from. + */ + remove : function(n, p) { + var d = new Date(); + + d.setTime(d.getTime() - 1000); + + this.set(n, '', d, p, d); + } + }); +})(); diff --git a/js/tiny_mce/classes/util/Dispatcher.js b/js/tiny_mce/classes/util/Dispatcher.js index 86588a64..4ffdf5aa 100644 --- a/js/tiny_mce/classes/util/Dispatcher.js +++ b/js/tiny_mce/classes/util/Dispatcher.js @@ -1,106 +1,112 @@ -/** - * Dispatcher.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -/** - * This class is used to dispatch event to observers/listeners. - * All internal events inside TinyMCE uses this class. - * - * @class tinymce.util.Dispatcher - */ -tinymce.create('tinymce.util.Dispatcher', { - scope : null, - listeners : null, - - /** - * Constructs a new event dispatcher object. - * - * @constructor - * @method Dispatcher - * @param {Object} s Optional default execution scope for all observer functions. - */ - Dispatcher : function(s) { - this.scope = s || this; - this.listeners = []; - }, - - /** - * Add an observer function to be executed when a dispatch call is done. - * - * @method add - * @param {function} cb Callback function to execute when a dispatch event occurs. - * @param {Object} s Optional execution scope, defaults to the one specified in the class constructor. - * @return {function} Returns the same function as the one passed on. - */ - add : function(cb, s) { - this.listeners.push({cb : cb, scope : s || this.scope}); - - return cb; - }, - - /** - * Add an observer function to be executed to the top of the list of observers. - * - * @method addToTop - * @param {function} cb Callback function to execute when a dispatch event occurs. - * @param {Object} s Optional execution scope, defaults to the one specified in the class constructor. - * @return {function} Returns the same function as the one passed on. - */ - addToTop : function(cb, s) { - this.listeners.unshift({cb : cb, scope : s || this.scope}); - - return cb; - }, - - /** - * Removes an observer function. - * - * @method remove - * @param {function} cb Observer function to remove. - * @return {function} The same function that got passed in or null if it wasn't found. - */ - remove : function(cb) { - var l = this.listeners, o = null; - - tinymce.each(l, function(c, i) { - if (cb == c.cb) { - o = cb; - l.splice(i, 1); - return false; - } - }); - - return o; - }, - - /** - * Dispatches an event to all observers/listeners. - * - * @method dispatch - * @param {Object} .. Any number of arguments to dispatch. - * @return {Object} Last observer functions return value. - */ - dispatch : function() { - var s, a = arguments, i, li = this.listeners, c; - - // Needs to be a real loop since the listener count might change while looping - // And this is also more efficient - for (i = 0; i 0 ? ',' : '') + s(o[i]); - - return v + ']'; - } - - v = '{'; - - for (i in o) - v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : ''; - - return v + '}'; - } - - return '' + o; - }, - - /** - * Unserializes/parses the specified JSON string into a object. - * - * @method parse - * @param {string} s JSON String to parse into a JavaScript object. - * @return {Object} Object from input JSON string or undefined if it failed. - */ - parse : function(s) { - try { - return eval('(' + s + ')'); - } catch (ex) { - // Ignore - } - } - - /**#@-*/ -}); +/** + * JSON.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + function serialize(o, quote) { + var i, v, t; + + quote = quote || '"'; + + if (o == null) + return 'null'; + + t = typeof o; + + if (t == 'string') { + v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; + + return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { + // Make sure single quotes never get encoded inside double quotes for JSON compatibility + if (quote === '"' && a === "'") + return a; + + i = v.indexOf(b); + + if (i + 1) + return '\\' + v.charAt(i + 1); + + a = b.charCodeAt().toString(16); + + return '\\u' + '0000'.substring(a.length) + a; + }) + quote; + } + + if (t == 'object') { + if (o.hasOwnProperty && o instanceof Array) { + for (i=0, v = '['; i 0 ? ',' : '') + serialize(o[i], quote); + + return v + ']'; + } + + v = '{'; + + for (i in o) { + if (o.hasOwnProperty(i)) { + v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : ''; + } + } + + return v + '}'; + } + + return '' + o; + }; + + /** + * JSON parser and serializer class. + * + * @class tinymce.util.JSON + * @static + * @example + * // JSON parse a string into an object + * var obj = tinymce.util.JSON.parse(somestring); + * + * // JSON serialize a object into an string + * var str = tinymce.util.JSON.serialize(obj); + */ + tinymce.util.JSON = { + /** + * Serializes the specified object as a JSON string. + * + * @method serialize + * @param {Object} obj Object to serialize as a JSON string. + * @param {String} quote Optional quote string defaults to ". + * @return {string} JSON string serialized from input. + */ + serialize: serialize, + + /** + * Unserializes/parses the specified JSON string into a object. + * + * @method parse + * @param {string} s JSON String to parse into a JavaScript object. + * @return {Object} Object from input JSON string or undefined if it failed. + */ + parse: function(s) { + try { + return eval('(' + s + ')'); + } catch (ex) { + // Ignore + } + } + + /**#@-*/ + }; +})(); diff --git a/js/tiny_mce/classes/util/JSONRequest.js b/js/tiny_mce/classes/util/JSONRequest.js index 7e62b305..6af6e824 100644 --- a/js/tiny_mce/classes/util/JSONRequest.js +++ b/js/tiny_mce/classes/util/JSONRequest.js @@ -1,89 +1,112 @@ -/** - * JSONRequest.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; - - /** - * This class enables you to use JSON-RPC to call backend methods. - * - * @class tinymce.util.JSONRequest - */ - tinymce.create('tinymce.util.JSONRequest', { - /** - * Constructs a new JSONRequest instance. - * - * @constructor - * @method JSONRequest - * @param {Object} s Optional settings object. - */ - JSONRequest : function(s) { - this.settings = extend({ - }, s); - this.count = 0; - }, - - /** - * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. - * - * @method send - * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. - */ - send : function(o) { - var ecb = o.error, scb = o.success; - - o = extend(this.settings, o); - - o.success = function(c, x) { - c = JSON.parse(c); - - if (typeof(c) == 'undefined') { - c = { - error : 'JSON Parse error.' - }; - } - - if (c.error) - ecb.call(o.error_scope || o.scope, c.error, x); - else - scb.call(o.success_scope || o.scope, c.result); - }; - - o.error = function(ty, x) { - ecb.call(o.error_scope || o.scope, ty, x); - }; - - o.data = JSON.serialize({ - id : o.id || 'c' + (this.count++), - method : o.method, - params : o.params - }); - - // JSON content type for Ruby on rails. Bug: #1883287 - o.content_type = 'application/json'; - - XHR.send(o); - }, - - 'static' : { - /** - * Simple helper function to send a JSON-RPC request without the need to initialize an object. - * Consult the Wiki API documentation for more details on what you can pass to this function. - * - * @method sendRPC - * @static - * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. - */ - sendRPC : function(o) { - return new tinymce.util.JSONRequest().send(o); - } - } - }); +/** + * JSONRequest.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; + + /** + * This class enables you to use JSON-RPC to call backend methods. + * + * @class tinymce.util.JSONRequest + * @example + * var json = new tinymce.util.JSONRequest({ + * url : 'somebackend.php' + * }); + * + * // Send RPC call 1 + * json.send({ + * method : 'someMethod1', + * params : ['a', 'b'], + * success : function(result) { + * console.dir(result); + * } + * }); + * + * // Send RPC call 2 + * json.send({ + * method : 'someMethod2', + * params : ['a', 'b'], + * success : function(result) { + * console.dir(result); + * } + * }); + */ + tinymce.create('tinymce.util.JSONRequest', { + /** + * Constructs a new JSONRequest instance. + * + * @constructor + * @method JSONRequest + * @param {Object} s Optional settings object. + */ + JSONRequest : function(s) { + this.settings = extend({ + }, s); + this.count = 0; + }, + + /** + * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. + * + * @method send + * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. + */ + send : function(o) { + var ecb = o.error, scb = o.success; + + o = extend(this.settings, o); + + o.success = function(c, x) { + c = JSON.parse(c); + + if (typeof(c) == 'undefined') { + c = { + error : 'JSON Parse error.' + }; + } + + if (c.error) + ecb.call(o.error_scope || o.scope, c.error, x); + else + scb.call(o.success_scope || o.scope, c.result); + }; + + o.error = function(ty, x) { + if (ecb) + ecb.call(o.error_scope || o.scope, ty, x); + }; + + o.data = JSON.serialize({ + id : o.id || 'c' + (this.count++), + method : o.method, + params : o.params + }); + + // JSON content type for Ruby on rails. Bug: #1883287 + o.content_type = 'application/json'; + + XHR.send(o); + }, + + 'static' : { + /** + * Simple helper function to send a JSON-RPC request without the need to initialize an object. + * Consult the Wiki API documentation for more details on what you can pass to this function. + * + * @method sendRPC + * @static + * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. + */ + sendRPC : function(o) { + return new tinymce.util.JSONRequest().send(o); + } + } + }); }()); \ No newline at end of file diff --git a/js/tiny_mce/classes/util/Quirks.js b/js/tiny_mce/classes/util/Quirks.js new file mode 100644 index 00000000..1194afd7 --- /dev/null +++ b/js/tiny_mce/classes/util/Quirks.js @@ -0,0 +1,229 @@ +(function(tinymce) { + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE; + + /** + * Fixes a WebKit bug when deleting contents using backspace or delete key. + * WebKit will produce a span element if you delete across two block elements. + * + * Example: + *

    a

    |b

    + * + * Will produce this on backspace: + *

    ab

    + * + * This fixes the backspace to produce: + *

    a|b

    + * + * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784 + * + * This code is a bit of a hack and hopefully it will be fixed soon in WebKit. + */ + function cleanupStylesWhenDeleting(ed) { + var dom = ed.dom, selection = ed.selection; + + ed.onKeyDown.add(function(ed, e) { + var rng, blockElm, node, clonedSpan, isDelete; + + isDelete = e.keyCode == DELETE; + if (isDelete || e.keyCode == BACKSPACE) { + e.preventDefault(); + rng = selection.getRng(); + + // Find root block + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + + // On delete clone the root span of the next block element + if (isDelete) + blockElm = dom.getNext(blockElm, dom.isBlock); + + // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace + if (blockElm) { + node = blockElm.firstChild; + + // Ignore empty text nodes + while (node && node.nodeType == 3 && node.nodeValue.length == 0) + node = node.nextSibling; + + if (node && node.nodeName === 'SPAN') { + clonedSpan = node.cloneNode(false); + } + } + + // Do the backspace/delete actiopn + ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Find all odd apple-style-spans + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { + var bm = selection.getBookmark(); + + if (clonedSpan) { + dom.replace(clonedSpan.cloneNode(false), span, true); + } else { + dom.remove(span, true); + } + + // Restore the selection + selection.moveToBookmark(bm); + }); + } + }); + }; + + /** + * WebKit and IE doesn't empty the editor if you select all contents and hit backspace or delete. This fix will check if the body is empty + * like a

    or

    and then forcefully remove all contents. + */ + function emptyEditorWhenDeleting(ed) { + ed.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == DELETE || keyCode == BACKSPACE) { + if (ed.dom.isEmpty(ed.getBody())) { + ed.setContent('', {format : 'raw'}); + ed.nodeChanged(); + return; + } + } + }); + }; + + /** + * WebKit on MacOS X has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. + * So a fix where we just get the range and set the range back seems to do the trick. + */ + function inputMethodFocus(ed) { + ed.dom.bind(ed.getDoc(), 'focusin', function() { + ed.selection.setRng(ed.selection.getRng()); + }); + }; + + /** + * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the + * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is + * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js + * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other + * browsers + */ + function removeHrOnBackspace(ed) { + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode === BACKSPACE) { + if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) { + var node = ed.selection.getNode(); + var previousSibling = node.previousSibling; + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { + ed.dom.remove(previousSibling); + tinymce.dom.Event.cancel(e); + } + } + } + }) + } + + /** + * Firefox 3.x has an issue where the body element won't get proper focus if you click out + * side it's rectangle. + */ + function focusBody(ed) { + // Fix for a focus bug in FF 3.x where the body element + // wouldn't get proper focus if the user clicked on the HTML element + if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 + ed.onMouseDown.add(function(ed, e) { + if (e.target.nodeName === "HTML") { + var body = ed.getBody(); + + // Blur the body it's focused but not correctly focused + body.blur(); + + // Refocus the body after a little while + setTimeout(function() { + body.focus(); + }, 0); + } + }); + } + }; + + /** + * WebKit has a bug where it isn't possible to select image, hr or anchor elements + * by clicking on them so we need to fake that. + */ + function selectControlElements(ed) { + ed.onClick.add(function(ed, e) { + e = e.target; + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // Needs tobe the setBaseAndExtend or it will fail to select floated images + if (/^(IMG|HR)$/.test(e.nodeName)) + ed.selection.getSel().setBaseAndExtent(e, 0, e, 1); + + if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor')) + ed.selection.select(e); + + ed.nodeChanged(); + }); + }; + + /** + * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange + * event every 50ms since it would other wise update the UI when you type and it hogs the CPU. + */ + function selectionChangeNodeChanged(ed) { + var lastRng, selectionTimer; + + ed.dom.bind(ed.getDoc(), 'selectionchange', function() { + if (selectionTimer) { + clearTimeout(selectionTimer); + selectionTimer = 0; + } + + selectionTimer = window.setTimeout(function() { + var rng = ed.selection.getRng(); + + // Compare the ranges to see if it was a real change or not + if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { + ed.nodeChanged(); + lastRng = rng; + } + }, 50); + }); + } + + /** + * Screen readers on IE needs to have the role application set on the body. + */ + function ensureBodyHasRoleApplication(ed) { + document.body.setAttribute("role", "application"); + } + + tinymce.create('tinymce.util.Quirks', { + Quirks: function(ed) { + // WebKit + if (tinymce.isWebKit) { + cleanupStylesWhenDeleting(ed); + emptyEditorWhenDeleting(ed); + inputMethodFocus(ed); + selectControlElements(ed); + + // iOS + if (tinymce.isIDevice) { + selectionChangeNodeChanged(ed); + } + } + + // IE + if (tinymce.isIE) { + removeHrOnBackspace(ed); + emptyEditorWhenDeleting(ed); + ensureBodyHasRoleApplication(ed); + } + + // Gecko + if (tinymce.isGecko) { + removeHrOnBackspace(ed); + focusBody(ed); + } + } + }); +})(tinymce); diff --git a/js/tiny_mce/classes/util/URI.js b/js/tiny_mce/classes/util/URI.js index 7af64483..e0486d9d 100644 --- a/js/tiny_mce/classes/util/URI.js +++ b/js/tiny_mce/classes/util/URI.js @@ -1,303 +1,312 @@ -/** - * URI.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var each = tinymce.each; - - /** - * This class handles parsing, modification and serialization of URI/URL strings. - * @class tinymce.util.URI - */ - tinymce.create('tinymce.util.URI', { - /** - * Constucts a new URI instance. - * - * @constructor - * @method URI - * @param {String} u URI string to parse. - * @param {Object} s Optional settings object. - */ - URI : function(u, s) { - var t = this, o, a, b; - - // Trim whitespace - u = tinymce.trim(u); - - // Default settings - s = t.settings = s || {}; - - // Strange app protocol or local anchor - if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) { - t.source = u; - return; - } - - // Absolute path with no host, fake host and protocol - if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) - u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; - - // Relative path http:// or protocol relative //path - if (!/^\w*:?\/\//.test(u)) - u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u); - - // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) - u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something - u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); - each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { - var s = u[i]; - - // Zope 3 workaround, they use @@something - if (s) - s = s.replace(/\(mce_at\)/g, '@@'); - - t[v] = s; - }); - - if (b = s.base_uri) { - if (!t.protocol) - t.protocol = b.protocol; - - if (!t.userInfo) - t.userInfo = b.userInfo; - - if (!t.port && t.host == 'mce_host') - t.port = b.port; - - if (!t.host || t.host == 'mce_host') - t.host = b.host; - - t.source = ''; - } - - //t.path = t.path || '/'; - }, - - /** - * Sets the internal path part of the URI. - * - * @method setPath - * @param {string} p Path string to set. - */ - setPath : function(p) { - var t = this; - - p = /^(.*?)\/?(\w+)?$/.exec(p); - - // Update path parts - t.path = p[0]; - t.directory = p[1]; - t.file = p[2]; - - // Rebuild source - t.source = ''; - t.getURI(); - }, - - /** - * Converts the specified URI into a relative URI based on the current URI instance location. - * - * @method toRelative - * @param {String} u URI to convert into a relative path/URI. - * @return {String} Relative URI from the point specified in the current URI instance. - */ - toRelative : function(u) { - var t = this, o; - - if (u === "./") - return u; - - u = new tinymce.util.URI(u, {base_uri : t}); - - // Not on same domain/port or protocol - if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) - return u.getURI(); - - o = t.toRelPath(t.path, u.path); - - // Add query - if (u.query) - o += '?' + u.query; - - // Add anchor - if (u.anchor) - o += '#' + u.anchor; - - return o; - }, - - /** - * Converts the specified URI into a absolute URI based on the current URI instance location. - * - * @method toAbsolute - * @param {String} u URI to convert into a relative path/URI. - * @param {Boolean} nh No host and protocol prefix. - * @return {String} Absolute URI from the point specified in the current URI instance. - */ - toAbsolute : function(u, nh) { - var u = new tinymce.util.URI(u, {base_uri : this}); - - return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); - }, - - /** - * Converts a absolute path into a relative path. - * - * @method toRelPath - * @param {String} base Base point to convert the path from. - * @param {String} path Absolute path to convert into a relative path. - */ - toRelPath : function(base, path) { - var items, bp = 0, out = '', i, l; - - // Split the paths - base = base.substring(0, base.lastIndexOf('/')); - base = base.split('/'); - items = path.split('/'); - - if (base.length >= items.length) { - for (i = 0, l = base.length; i < l; i++) { - if (i >= items.length || base[i] != items[i]) { - bp = i + 1; - break; - } - } - } - - if (base.length < items.length) { - for (i = 0, l = items.length; i < l; i++) { - if (i >= base.length || base[i] != items[i]) { - bp = i + 1; - break; - } - } - } - - if (bp == 1) - return path; - - for (i = 0, l = base.length - (bp - 1); i < l; i++) - out += "../"; - - for (i = bp - 1, l = items.length; i < l; i++) { - if (i != bp - 1) - out += "/" + items[i]; - else - out += items[i]; - } - - return out; - }, - - /** - * Converts a relative path into a absolute path. - * - * @method toAbsPath - * @param {String} base Base point to convert the path from. - * @param {String} path Relative path to convert into an absolute path. - */ - toAbsPath : function(base, path) { - var i, nb = 0, o = [], tr, outPath; - - // Split paths - tr = /\/$/.test(path) ? '/' : ''; - base = base.split('/'); - path = path.split('/'); - - // Remove empty chunks - each(base, function(k) { - if (k) - o.push(k); - }); - - base = o; - - // Merge relURLParts chunks - for (i = path.length - 1, o = []; i >= 0; i--) { - // Ignore empty or . - if (path[i].length == 0 || path[i] == ".") - continue; - - // Is parent - if (path[i] == '..') { - nb++; - continue; - } - - // Move up - if (nb > 0) { - nb--; - continue; - } - - o.push(path[i]); - } - - i = base.length - nb; - - // If /a/b/c or / - if (i <= 0) - outPath = o.reverse().join('/'); - else - outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); - - // Add front / if it's needed - if (outPath.indexOf('/') !== 0) - outPath = '/' + outPath; - - // Add traling / if it's needed - if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) - outPath += tr; - - return outPath; - }, - - /** - * Returns the full URI of the internal structure. - * - * @method getURI - * @param {Boolean} nh Optional no host and protocol part. Defaults to false. - */ - getURI : function(nh) { - var s, t = this; - - // Rebuild source - if (!t.source || nh) { - s = ''; - - if (!nh) { - if (t.protocol) - s += t.protocol + '://'; - - if (t.userInfo) - s += t.userInfo + '@'; - - if (t.host) - s += t.host; - - if (t.port) - s += ':' + t.port; - } - - if (t.path) - s += t.path; - - if (t.query) - s += '?' + t.query; - - if (t.anchor) - s += '#' + t.anchor; - - t.source = s; - } - - return t.source; - } - }); -})(); +/** + * URI.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each; + + /** + * This class handles parsing, modification and serialization of URI/URL strings. + * @class tinymce.util.URI + */ + tinymce.create('tinymce.util.URI', { + /** + * Constucts a new URI instance. + * + * @constructor + * @method URI + * @param {String} u URI string to parse. + * @param {Object} s Optional settings object. + */ + URI : function(u, s) { + var t = this, o, a, b, base_url; + + // Trim whitespace + u = tinymce.trim(u); + + // Default settings + s = t.settings = s || {}; + + // Strange app protocol that isn't http/https or local anchor + // For example: mailto,skype,tel etc. + if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { + t.source = u; + return; + } + + // Absolute path with no host, fake host and protocol + if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) + u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; + + // Relative path http:// or protocol relative //path + if (!/^[\w-]*:?\/\//.test(u)) { + base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; + u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); + } + + // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) + u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something + u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); + each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { + var s = u[i]; + + // Zope 3 workaround, they use @@something + if (s) + s = s.replace(/\(mce_at\)/g, '@@'); + + t[v] = s; + }); + + if (b = s.base_uri) { + if (!t.protocol) + t.protocol = b.protocol; + + if (!t.userInfo) + t.userInfo = b.userInfo; + + if (!t.port && t.host == 'mce_host') + t.port = b.port; + + if (!t.host || t.host == 'mce_host') + t.host = b.host; + + t.source = ''; + } + + //t.path = t.path || '/'; + }, + + /** + * Sets the internal path part of the URI. + * + * @method setPath + * @param {string} p Path string to set. + */ + setPath : function(p) { + var t = this; + + p = /^(.*?)\/?(\w+)?$/.exec(p); + + // Update path parts + t.path = p[0]; + t.directory = p[1]; + t.file = p[2]; + + // Rebuild source + t.source = ''; + t.getURI(); + }, + + /** + * Converts the specified URI into a relative URI based on the current URI instance location. + * + * @method toRelative + * @param {String} u URI to convert into a relative path/URI. + * @return {String} Relative URI from the point specified in the current URI instance. + * @example + * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm + * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); + */ + toRelative : function(u) { + var t = this, o; + + if (u === "./") + return u; + + u = new tinymce.util.URI(u, {base_uri : t}); + + // Not on same domain/port or protocol + if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) + return u.getURI(); + + o = t.toRelPath(t.path, u.path); + + // Add query + if (u.query) + o += '?' + u.query; + + // Add anchor + if (u.anchor) + o += '#' + u.anchor; + + return o; + }, + + /** + * Converts the specified URI into a absolute URI based on the current URI instance location. + * + * @method toAbsolute + * @param {String} u URI to convert into a relative path/URI. + * @param {Boolean} nh No host and protocol prefix. + * @return {String} Absolute URI from the point specified in the current URI instance. + * @example + * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm + * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); + */ + toAbsolute : function(u, nh) { + var u = new tinymce.util.URI(u, {base_uri : this}); + + return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); + }, + + /** + * Converts a absolute path into a relative path. + * + * @method toRelPath + * @param {String} base Base point to convert the path from. + * @param {String} path Absolute path to convert into a relative path. + */ + toRelPath : function(base, path) { + var items, bp = 0, out = '', i, l; + + // Split the paths + base = base.substring(0, base.lastIndexOf('/')); + base = base.split('/'); + items = path.split('/'); + + if (base.length >= items.length) { + for (i = 0, l = base.length; i < l; i++) { + if (i >= items.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (base.length < items.length) { + for (i = 0, l = items.length; i < l; i++) { + if (i >= base.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (bp == 1) + return path; + + for (i = 0, l = base.length - (bp - 1); i < l; i++) + out += "../"; + + for (i = bp - 1, l = items.length; i < l; i++) { + if (i != bp - 1) + out += "/" + items[i]; + else + out += items[i]; + } + + return out; + }, + + /** + * Converts a relative path into a absolute path. + * + * @method toAbsPath + * @param {String} base Base point to convert the path from. + * @param {String} path Relative path to convert into an absolute path. + */ + toAbsPath : function(base, path) { + var i, nb = 0, o = [], tr, outPath; + + // Split paths + tr = /\/$/.test(path) ? '/' : ''; + base = base.split('/'); + path = path.split('/'); + + // Remove empty chunks + each(base, function(k) { + if (k) + o.push(k); + }); + + base = o; + + // Merge relURLParts chunks + for (i = path.length - 1, o = []; i >= 0; i--) { + // Ignore empty or . + if (path[i].length == 0 || path[i] == ".") + continue; + + // Is parent + if (path[i] == '..') { + nb++; + continue; + } + + // Move up + if (nb > 0) { + nb--; + continue; + } + + o.push(path[i]); + } + + i = base.length - nb; + + // If /a/b/c or / + if (i <= 0) + outPath = o.reverse().join('/'); + else + outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); + + // Add front / if it's needed + if (outPath.indexOf('/') !== 0) + outPath = '/' + outPath; + + // Add traling / if it's needed + if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) + outPath += tr; + + return outPath; + }, + + /** + * Returns the full URI of the internal structure. + * + * @method getURI + * @param {Boolean} nh Optional no host and protocol part. Defaults to false. + */ + getURI : function(nh) { + var s, t = this; + + // Rebuild source + if (!t.source || nh) { + s = ''; + + if (!nh) { + if (t.protocol) + s += t.protocol + '://'; + + if (t.userInfo) + s += t.userInfo + '@'; + + if (t.host) + s += t.host; + + if (t.port) + s += ':' + t.port; + } + + if (t.path) + s += t.path; + + if (t.query) + s += '?' + t.query; + + if (t.anchor) + s += '#' + t.anchor; + + t.source = s; + } + + return t.source; + } + }); +})(); diff --git a/js/tiny_mce/classes/util/VK.js b/js/tiny_mce/classes/util/VK.js new file mode 100644 index 00000000..08bc8fc2 --- /dev/null +++ b/js/tiny_mce/classes/util/VK.js @@ -0,0 +1,15 @@ +/** + * This file exposes a set of the common KeyCodes for use. Please grow it as needed. + */ + +(function(tinymce){ + tinymce.VK = { + DELETE: 46, + BACKSPACE: 8, + ENTER: 13, + TAB: 9, + SPACEBAR: 32, + UP: 38, + DOWN: 40 + } +})(tinymce); diff --git a/js/tiny_mce/classes/util/XHR.js b/js/tiny_mce/classes/util/XHR.js index a444c136..779c3d06 100644 --- a/js/tiny_mce/classes/util/XHR.js +++ b/js/tiny_mce/classes/util/XHR.js @@ -1,80 +1,88 @@ -/** - * XHR.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -/** - * This class enables you to send XMLHTTPRequests cross browser. - * @class tinymce.util.XHR - * @static - */ -tinymce.create('static tinymce.util.XHR', { - /** - * Sends a XMLHTTPRequest. - * Consult the Wiki for details on what settings this method takes. - * - * @method send - * @param {Object} o Object will target URL, callbacks and other info needed to make the request. - */ - send : function(o) { - var x, t, w = window, c = 0; - - // Default settings - o.scope = o.scope || this; - o.success_scope = o.success_scope || o.scope; - o.error_scope = o.error_scope || o.scope; - o.async = o.async === false ? false : true; - o.data = o.data || ''; - - function get(s) { - x = 0; - - try { - x = new ActiveXObject(s); - } catch (ex) { - } - - return x; - }; - - x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); - - if (x) { - if (x.overrideMimeType) - x.overrideMimeType(o.content_type); - - x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); - - if (o.content_type) - x.setRequestHeader('Content-Type', o.content_type); - - x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - - x.send(o.data); - - function ready() { - if (!o.async || x.readyState == 4 || c++ > 10000) { - if (o.success && c < 10000 && x.status == 200) - o.success.call(o.success_scope, '' + x.responseText, x, o); - else if (o.error) - o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); - - x = null; - } else - w.setTimeout(ready, 10); - }; - - // Syncronous request - if (!o.async) - return ready(); - - // Wait for response, onReadyStateChange can not be used since it leaks memory in IE - t = w.setTimeout(ready, 10); - } - } -}); +/** + * XHR.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +/** + * This class enables you to send XMLHTTPRequests cross browser. + * @class tinymce.util.XHR + * @static + * @example + * // Sends a low level Ajax request + * tinymce.util.XHR.send({ + * url : 'someurl', + * success : function(text) { + * console.debug(text); + * } + * }); + */ +tinymce.create('static tinymce.util.XHR', { + /** + * Sends a XMLHTTPRequest. + * Consult the Wiki for details on what settings this method takes. + * + * @method send + * @param {Object} o Object will target URL, callbacks and other info needed to make the request. + */ + send : function(o) { + var x, t, w = window, c = 0; + + // Default settings + o.scope = o.scope || this; + o.success_scope = o.success_scope || o.scope; + o.error_scope = o.error_scope || o.scope; + o.async = o.async === false ? false : true; + o.data = o.data || ''; + + function get(s) { + x = 0; + + try { + x = new ActiveXObject(s); + } catch (ex) { + } + + return x; + }; + + x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); + + if (x) { + if (x.overrideMimeType) + x.overrideMimeType(o.content_type); + + x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); + + if (o.content_type) + x.setRequestHeader('Content-Type', o.content_type); + + x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + x.send(o.data); + + function ready() { + if (!o.async || x.readyState == 4 || c++ > 10000) { + if (o.success && c < 10000 && x.status == 200) + o.success.call(o.success_scope, '' + x.responseText, x, o); + else if (o.error) + o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); + + x = null; + } else + w.setTimeout(ready, 10); + }; + + // Syncronous request + if (!o.async) + return ready(); + + // Wait for response, onReadyStateChange can not be used since it leaks memory in IE + t = w.setTimeout(ready, 10); + } + } +}); diff --git a/js/tiny_mce/jquery.tinymce.js b/js/tiny_mce/jquery.tinymce.js index 6fc34f0e..8e61a3cd 100644 --- a/js/tiny_mce/jquery.tinymce.js +++ b/js/tiny_mce/jquery.tinymce.js @@ -1 +1 @@ -(function(b){var e,d,a=[],c=window;b.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return}if(!j){return tinyMCE.get(p[0].id)}function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);if(v){s.onInit.add(function(){var x,y=v;if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}})}});b.each(r,function(t,s){s.render()})}if(!c.tinymce&&!d&&(g=j.script_url)){d=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}c.tinyMCEPreInit={base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!c.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");b.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}b.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;d=2;o();b.each(a,function(q,r){r()})}})}else{if(d===1){a.push(o)}else{o()}}};b.extend(b.expr[":"],{tinymce:function(g){return g.id&&!!tinyMCE.get(g.id)}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==e){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(c.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(c.tinymce)&&(l.is(":tinymce")))}var j={};b.each(["text","html","val"],function(n,l){var o=j[l]=b.fn[l],m=(l==="text");b.fn[l]=function(r){var p=this;if(!g(p)){return o.call(p,r)}if(r!==e){k.call(p.filter(":tinymce"),r);o.call(p.not(":tinymce"),r);return p}else{var q="";(m?p:p.eq(0)).each(function(t,u){var s=h(u);q+=s?(m?s.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):s.getContent()):o.call(b(u),r)});return q}}});b.each(["append","prepend"],function(n,m){var o=j[m]=b.fn[m],l=(m==="prepend");b.fn[m]=function(q){var p=this;if(!g(p)){return o.call(p,q)}if(q!==e){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.call(p.not(":tinymce"),q);return p}}});b.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=b.fn[l];b.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=b.fn.attr;b.fn.attr=function(n,q,o){var m=this;if((!n)||(n!=="value")||(!g(m))){return j.attr.call(m,n,q,o)}if(q!==e){k.call(m.filter(":tinymce"),q);j.attr.call(m.not(":tinymce"),n,q,o);return m}else{var p=m[0],l=h(p);return l?l.getContent():j.attr.call(b(p),n,q,o)}}}})(jQuery); \ No newline at end of file +(function(b){var e,d,a=[],c=window;b.fn.tinymce=function(j){var p=this,g,k,h,m,i,l="",n="";if(!p.length){return p}if(!j){return tinyMCE.get(p[0].id)}p.css("visibility","hidden");function o(){var r=[],q=0;if(f){f();f=null}p.each(function(t,u){var s,w=u.id,v=j.oninit;if(!w){u.id=w=tinymce.DOM.uniqueId()}s=new tinymce.Editor(w,j);r.push(s);s.onInit.add(function(){var x,y=v;p.css("visibility","");if(v){if(++q==r.length){if(tinymce.is(y,"string")){x=(y.indexOf(".")===-1)?null:tinymce.resolve(y.replace(/\.\w+$/,""));y=tinymce.resolve(y)}y.apply(x||tinymce,r)}}})});b.each(r,function(t,s){s.render()})}if(!c.tinymce&&!d&&(g=j.script_url)){d=1;h=g.substring(0,g.lastIndexOf("/"));if(/_(src|dev)\.js/g.test(g)){n="_src"}m=g.lastIndexOf("?");if(m!=-1){l=g.substring(m+1)}c.tinyMCEPreInit=c.tinyMCEPreInit||{base:h,suffix:n,query:l};if(g.indexOf("gzip")!=-1){i=j.language||"en";g=g+(/\?/.test(g)?"&":"?")+"js=true&core=true&suffix="+escape(n)+"&themes="+escape(j.theme)+"&plugins="+escape(j.plugins)+"&languages="+i;if(!c.tinyMCE_GZ){tinyMCE_GZ={start:function(){tinymce.suffix=n;function q(r){tinymce.ScriptLoader.markDone(tinyMCE.baseURI.toAbsolute(r))}q("langs/"+i+".js");q("themes/"+j.theme+"/editor_template"+n+".js");q("themes/"+j.theme+"/langs/"+i+".js");b.each(j.plugins.split(","),function(s,r){if(r){q("plugins/"+r+"/editor_plugin"+n+".js");q("plugins/"+r+"/langs/"+i+".js")}})},end:function(){}}}}b.ajax({type:"GET",url:g,dataType:"script",cache:true,success:function(){tinymce.dom.Event.domLoaded=1;d=2;if(j.script_loaded){j.script_loaded()}o();b.each(a,function(q,r){r()})}})}else{if(d===1){a.push(o)}else{o()}}return p};b.extend(b.expr[":"],{tinymce:function(g){return g.id&&!!tinyMCE.get(g.id)}});function f(){function i(l){if(l==="remove"){this.each(function(n,o){var m=h(o);if(m){m.remove()}})}this.find("span.mceEditor,div.mceEditor").each(function(n,o){var m=tinyMCE.get(o.id.replace(/_parent$/,""));if(m){m.remove()}})}function k(n){var m=this,l;if(n!==e){i.call(m);m.each(function(p,q){var o;if(o=tinyMCE.get(q.id)){o.setContent(n)}})}else{if(m.length>0){if(l=tinyMCE.get(m[0].id)){return l.getContent()}}}}function h(m){var l=null;(m)&&(m.id)&&(c.tinymce)&&(l=tinyMCE.get(m.id));return l}function g(l){return !!((l)&&(l.length)&&(c.tinymce)&&(l.is(":tinymce")))}var j={};b.each(["text","html","val"],function(n,l){var o=j[l]=b.fn[l],m=(l==="text");b.fn[l]=function(s){var p=this;if(!g(p)){return o.apply(p,arguments)}if(s!==e){k.call(p.filter(":tinymce"),s);o.apply(p.not(":tinymce"),arguments);return p}else{var r="";var q=arguments;(m?p:p.eq(0)).each(function(u,v){var t=h(v);r+=t?(m?t.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):t.getContent()):o.apply(b(v),q)});return r}}});b.each(["append","prepend"],function(n,m){var o=j[m]=b.fn[m],l=(m==="prepend");b.fn[m]=function(q){var p=this;if(!g(p)){return o.apply(p,arguments)}if(q!==e){p.filter(":tinymce").each(function(s,t){var r=h(t);r&&r.setContent(l?q+r.getContent():r.getContent()+q)});o.apply(p.not(":tinymce"),arguments);return p}}});b.each(["remove","replaceWith","replaceAll","empty"],function(m,l){var n=j[l]=b.fn[l];b.fn[l]=function(){i.call(this,l);return n.apply(this,arguments)}});j.attr=b.fn.attr;b.fn.attr=function(n,q,o){var m=this;if((!n)||(n!=="value")||(!g(m))){return j.attr.call(m,n,q,o)}if(q!==e){k.call(m.filter(":tinymce"),q);j.attr.call(m.not(":tinymce"),n,q,o);return m}else{var p=m[0],l=h(p);return l?l.getContent():j.attr.call(b(p),n,q,o)}}}})(jQuery); \ No newline at end of file diff --git a/js/tiny_mce/langs/en.js b/js/tiny_mce/langs/en.js index 075bef70..6379b0d9 100644 --- a/js/tiny_mce/langs/en.js +++ b/js/tiny_mce/langs/en.js @@ -1,169 +1 @@ -tinyMCE.addI18n({en:{ -common:{ -edit_confirm:"Do you want to use the WYSIWYG mode for this textarea?", -apply:"Apply", -insert:"Insert", -update:"Update", -cancel:"Cancel", -close:"Close", -browse:"Browse", -class_name:"Class", -not_set:"-- Not set --", -clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?", -clipboard_no_support:"Currently not supported by your browser, use keyboard shortcuts instead.", -popup_blocked:"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.", -invalid_data:"Error: Invalid values entered, these are marked in red.", -more_colors:"More colors" -}, -contextmenu:{ -align:"Alignment", -left:"Left", -center:"Center", -right:"Right", -full:"Full" -}, -insertdatetime:{ -date_fmt:"%Y-%m-%d", -time_fmt:"%H:%M:%S", -insertdate_desc:"Insert date", -inserttime_desc:"Insert time", -months_long:"January,February,March,April,May,June,July,August,September,October,November,December", -months_short:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", -day_long:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday", -day_short:"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun" -}, -print:{ -print_desc:"Print" -}, -preview:{ -preview_desc:"Preview" -}, -directionality:{ -ltr_desc:"Direction left to right", -rtl_desc:"Direction right to left" -}, -layer:{ -insertlayer_desc:"Insert new layer", -forward_desc:"Move forward", -backward_desc:"Move backward", -absolute_desc:"Toggle absolute positioning", -content:"New layer..." -}, -save:{ -save_desc:"Save", -cancel_desc:"Cancel all changes" -}, -nonbreaking:{ -nonbreaking_desc:"Insert non-breaking space character" -}, -iespell:{ -iespell_desc:"Run spell checking", -download:"ieSpell not detected. Do you want to install it now?" -}, -advhr:{ -advhr_desc:"Horizontal rule" -}, -emotions:{ -emotions_desc:"Emotions" -}, -searchreplace:{ -search_desc:"Find", -replace_desc:"Find/Replace" -}, -advimage:{ -image_desc:"Insert/edit image" -}, -advlink:{ -link_desc:"Insert/edit link" -}, -xhtmlxtras:{ -cite_desc:"Citation", -abbr_desc:"Abbreviation", -acronym_desc:"Acronym", -del_desc:"Deletion", -ins_desc:"Insertion", -attribs_desc:"Insert/Edit Attributes" -}, -style:{ -desc:"Edit CSS Style" -}, -paste:{ -paste_text_desc:"Paste as Plain Text", -paste_word_desc:"Paste from Word", -selectall_desc:"Select All" -}, -paste_dlg:{ -text_title:"Use CTRL+V on your keyboard to paste the text into the window.", -text_linebreaks:"Keep linebreaks", -word_title:"Use CTRL+V on your keyboard to paste the text into the window." -}, -table:{ -desc:"Inserts a new table", -row_before_desc:"Insert row before", -row_after_desc:"Insert row after", -delete_row_desc:"Delete row", -col_before_desc:"Insert column before", -col_after_desc:"Insert column after", -delete_col_desc:"Remove column", -split_cells_desc:"Split merged table cells", -merge_cells_desc:"Merge table cells", -row_desc:"Table row properties", -cell_desc:"Table cell properties", -props_desc:"Table properties", -paste_row_before_desc:"Paste table row before", -paste_row_after_desc:"Paste table row after", -cut_row_desc:"Cut table row", -copy_row_desc:"Copy table row", -del:"Delete table", -row:"Row", -col:"Column", -cell:"Cell" -}, -autosave:{ -unload_msg:"The changes you made will be lost if you navigate away from this page.", -restore_content: "Restore auto-saved content", -warning_message: "If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?" -}, -fullscreen:{ -desc:"Toggle fullscreen mode" -}, -media:{ -desc:"Insert / edit embedded media", -edit:"Edit embedded media" -}, -fullpage:{ -desc:"Document properties" -}, -template:{ -desc:"Insert predefined template content" -}, -visualchars:{ -desc:"Visual control characters on/off." -}, -spellchecker:{ -desc:"Toggle spellchecker", -menu:"Spellchecker settings", -ignore_word:"Ignore word", -ignore_words:"Ignore all", -langs:"Languages", -wait:"Please wait...", -sug:"Suggestions", -no_sug:"No suggestions", -no_mpell:"No misspellings found." -}, -pagebreak:{ -desc:"Insert page break." -}, -advlist : { - types : 'Types', - def : 'Default', - lower_alpha : "Lower alpha", - lower_greek : "Lower greek", - lower_roman : "Lower roman", - upper_alpha : "Upper alpha", - upper_roman : "Upper roman", - circle : "Circle", - disc : "Disc", - square : "Square" -} -}}); \ No newline at end of file +tinyMCE.addI18n({en:{common:{"more_colors":"More Colors...","invalid_data":"Error: Invalid values entered, these are marked in red.","popup_blocked":"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.","clipboard_no_support":"Currently not supported by your browser, use keyboard shortcuts instead.","clipboard_msg":"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?","not_set":"-- Not Set --","class_name":"Class",browse:"Browse",close:"Close",cancel:"Cancel",update:"Update",insert:"Insert",apply:"Apply","edit_confirm":"Do you want to use the WYSIWYG mode for this textarea?","invalid_data_number":"{#field} must be a number","invalid_data_min":"{#field} must be a number greater than {#min}","invalid_data_size":"{#field} must be a number or percentage",value:"(value)"},contextmenu:{full:"Full",right:"Right",center:"Center",left:"Left",align:"Alignment"},insertdatetime:{"day_short":"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun","day_long":"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday","months_short":"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec","months_long":"January,February,March,April,May,June,July,August,September,October,November,December","inserttime_desc":"Insert Time","insertdate_desc":"Insert Date","time_fmt":"%H:%M:%S","date_fmt":"%Y-%m-%d"},print:{"print_desc":"Print"},preview:{"preview_desc":"Preview"},directionality:{"rtl_desc":"Direction Right to Left","ltr_desc":"Direction Left to Right"},layer:{content:"New layer...","absolute_desc":"Toggle Absolute Positioning","backward_desc":"Move Backward","forward_desc":"Move Forward","insertlayer_desc":"Insert New Layer"},save:{"save_desc":"Save","cancel_desc":"Cancel All Changes"},nonbreaking:{"nonbreaking_desc":"Insert Non-Breaking Space Character"},iespell:{download:"ieSpell not detected. Do you want to install it now?","iespell_desc":"Check Spelling"},advhr:{"delta_height":"","delta_width":"","advhr_desc":"Insert Horizontal Line"},emotions:{"delta_height":"","delta_width":"","emotions_desc":"Emotions"},searchreplace:{"replace_desc":"Find/Replace","delta_width":"","delta_height":"","search_desc":"Find"},advimage:{"delta_width":"","image_desc":"Insert/Edit Image","delta_height":""},advlink:{"delta_height":"","delta_width":"","link_desc":"Insert/Edit Link"},xhtmlxtras:{"attribs_delta_height":"","attribs_delta_width":"","ins_delta_height":"","ins_delta_width":"","del_delta_height":"","del_delta_width":"","acronym_delta_height":"","acronym_delta_width":"","abbr_delta_height":"","abbr_delta_width":"","cite_delta_height":"","cite_delta_width":"","attribs_desc":"Insert/Edit Attributes","ins_desc":"Insertion","del_desc":"Deletion","acronym_desc":"Acronym","abbr_desc":"Abbreviation","cite_desc":"Citation"},style:{"delta_height":"","delta_width":"",desc:"Edit CSS Style"},paste:{"plaintext_mode_stick":"Paste is now in plain text mode. Click again to toggle back to regular paste mode.","plaintext_mode":"Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.","selectall_desc":"Select All","paste_word_desc":"Paste from Word","paste_text_desc":"Paste as Plain Text"},"paste_dlg":{"word_title":"Use Ctrl+V on your keyboard to paste the text into the window.","text_linebreaks":"Keep Linebreaks","text_title":"Use Ctrl+V on your keyboard to paste the text into the window."},table:{"merge_cells_delta_height":"","merge_cells_delta_width":"","table_delta_height":"","table_delta_width":"","cellprops_delta_height":"","cellprops_delta_width":"","rowprops_delta_height":"","rowprops_delta_width":"",cell:"Cell",col:"Column",row:"Row",del:"Delete Table","copy_row_desc":"Copy Table Row","cut_row_desc":"Cut Table Row","paste_row_after_desc":"Paste Table Row After","paste_row_before_desc":"Paste Table Row Before","props_desc":"Table Properties","cell_desc":"Table Cell Properties","row_desc":"Table Row Properties","merge_cells_desc":"Merge Table Cells","split_cells_desc":"Split Merged Table Cells","delete_col_desc":"Delete Column","col_after_desc":"Insert Column After","col_before_desc":"Insert Column Before","delete_row_desc":"Delete Row","row_after_desc":"Insert Row After","row_before_desc":"Insert Row Before",desc:"Insert/Edit Table"},autosave:{"warning_message":"If you restore the saved content, you will lose all the content that is currently in the editor.\n\nAre you sure you want to restore the saved content?","restore_content":"Restore auto-saved content.","unload_msg":"The changes you made will be lost if you navigate away from this page."},fullscreen:{desc:"Toggle Full Screen Mode"},media:{"delta_height":"","delta_width":"",edit:"Edit Embedded Media",desc:"Insert/Edit Embedded Media"},fullpage:{desc:"Document Properties","delta_width":"","delta_height":""},template:{desc:"Insert Predefined Template Content"},visualchars:{desc:"Show/Hide Visual Control Characters"},spellchecker:{desc:"Toggle Spell Checker",menu:"Spell Checker Settings","ignore_word":"Ignore Word","ignore_words":"Ignore All",langs:"Languages",wait:"Please wait...",sug:"Suggestions","no_sug":"No Suggestions","no_mpell":"No misspellings found.","learn_word":"Learn word"},pagebreak:{desc:"Insert Page Break for Printing"},advlist:{types:"Types",def:"Default","lower_alpha":"Lower Alpha","lower_greek":"Lower Greek","lower_roman":"Lower Roman","upper_alpha":"Upper Alpha","upper_roman":"Upper Roman",circle:"Circle",disc:"Disc",square:"Square"},colors:{"333300":"Dark olive","993300":"Burnt orange","000000":"Black","003300":"Dark green","003366":"Dark azure","000080":"Navy Blue","333399":"Indigo","333333":"Very dark gray","800000":"Maroon",FF6600:"Orange","808000":"Olive","008000":"Green","008080":"Teal","0000FF":"Blue","666699":"Grayish blue","808080":"Gray",FF0000:"Red",FF9900:"Amber","99CC00":"Yellow green","339966":"Sea green","33CCCC":"Turquoise","3366FF":"Royal blue","800080":"Purple","999999":"Medium gray",FF00FF:"Magenta",FFCC00:"Gold",FFFF00:"Yellow","00FF00":"Lime","00FFFF":"Aqua","00CCFF":"Sky blue","993366":"Brown",C0C0C0:"Silver",FF99CC:"Pink",FFCC99:"Peach",FFFF99:"Light yellow",CCFFCC:"Pale green",CCFFFF:"Pale cyan","99CCFF":"Light sky blue",CC99FF:"Plum",FFFFFF:"White"},aria:{"rich_text_area":"Rich Text Area"},wordcount:{words:"Words:"}}}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advhr/langs/en_dlg.js b/js/tiny_mce/plugins/advhr/langs/en_dlg.js index 873bfd8d..0c3bf15e 100644 --- a/js/tiny_mce/plugins/advhr/langs/en_dlg.js +++ b/js/tiny_mce/plugins/advhr/langs/en_dlg.js @@ -1,5 +1 @@ -tinyMCE.addI18n('en.advhr_dlg',{ -width:"Width", -size:"Height", -noshade:"No shadow" -}); \ No newline at end of file +tinyMCE.addI18n('en.advhr_dlg',{size:"Height",noshade:"No Shadow",width:"Width",normal:"Normal",widthunits:"Units"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advhr/rule.htm b/js/tiny_mce/plugins/advhr/rule.htm index fc37b2ae..11d36513 100644 --- a/js/tiny_mce/plugins/advhr/rule.htm +++ b/js/tiny_mce/plugins/advhr/rule.htm @@ -1,57 +1,58 @@ - - - - {#advhr.advhr_desc} - - - - - - - - - - -
    -
    - - - - - - - - - - - - - -
    - - -
    -
    -
    - -
    - - -
    - - - + + + + {#advhr.advhr_desc} + + + + + + + +
    + + +
    +
    + + + + + + + + + + + + + +
    + + + +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/advimage/editor_plugin.js b/js/tiny_mce/plugins/advimage/editor_plugin.js index 4c7a9c3a..d613a613 100644 --- a/js/tiny_mce/plugins/advimage/editor_plugin.js +++ b/js/tiny_mce/plugins/advimage/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.AdvancedImagePlugin",{init:function(a,b){a.addCommand("mceAdvImage",function(){if(a.dom.getAttrib(a.selection.getNode(),"class").indexOf("mceItem")!=-1){return}a.windowManager.open({file:b+"/image.htm",width:480+parseInt(a.getLang("advimage.delta_width",0)),height:385+parseInt(a.getLang("advimage.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("image",{title:"advimage.image_desc",cmd:"mceAdvImage"})},getInfo:function(){return{longname:"Advanced image",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advimage",tinymce.plugins.AdvancedImagePlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.AdvancedImagePlugin",{init:function(a,b){a.addCommand("mceAdvImage",function(){if(a.dom.getAttrib(a.selection.getNode(),"class","").indexOf("mceItem")!=-1){return}a.windowManager.open({file:b+"/image.htm",width:480+parseInt(a.getLang("advimage.delta_width",0)),height:385+parseInt(a.getLang("advimage.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("image",{title:"advimage.image_desc",cmd:"mceAdvImage"})},getInfo:function(){return{longname:"Advanced image",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advimage",tinymce.plugins.AdvancedImagePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advimage/editor_plugin_src.js b/js/tiny_mce/plugins/advimage/editor_plugin_src.js index 2625dd21..76df89a3 100644 --- a/js/tiny_mce/plugins/advimage/editor_plugin_src.js +++ b/js/tiny_mce/plugins/advimage/editor_plugin_src.js @@ -1,50 +1,50 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.AdvancedImagePlugin', { - init : function(ed, url) { - // Register commands - ed.addCommand('mceAdvImage', function() { - // Internal image object like a flash placeholder - if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1) - return; - - ed.windowManager.open({ - file : url + '/image.htm', - width : 480 + parseInt(ed.getLang('advimage.delta_width', 0)), - height : 385 + parseInt(ed.getLang('advimage.delta_height', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - // Register buttons - ed.addButton('image', { - title : 'advimage.image_desc', - cmd : 'mceAdvImage' - }); - }, - - getInfo : function() { - return { - longname : 'Advanced image', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('advimage', tinymce.plugins.AdvancedImagePlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.AdvancedImagePlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceAdvImage', function() { + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(ed.selection.getNode(), 'class', '').indexOf('mceItem') != -1) + return; + + ed.windowManager.open({ + file : url + '/image.htm', + width : 480 + parseInt(ed.getLang('advimage.delta_width', 0)), + height : 385 + parseInt(ed.getLang('advimage.delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('image', { + title : 'advimage.image_desc', + cmd : 'mceAdvImage' + }); + }, + + getInfo : function() { + return { + longname : 'Advanced image', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advimage', tinymce.plugins.AdvancedImagePlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advimage/image.htm b/js/tiny_mce/plugins/advimage/image.htm index 79cff3f1..835d3882 100644 --- a/js/tiny_mce/plugins/advimage/image.htm +++ b/js/tiny_mce/plugins/advimage/image.htm @@ -1,232 +1,235 @@ - - - - {#advimage_dlg.dialog_title} - - - - - - - - - -
    - - -
    -
    -
    - {#advimage_dlg.general} - - - - - - - - - - - - - - - - - - -
    - - - - -
     
    -
    - -
    - {#advimage_dlg.preview} - -
    -
    - -
    -
    - {#advimage_dlg.tab_appearance} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - {#advimage_dlg.example_img} - Lorem ipsum, Dolor sit amet, consectetuer adipiscing loreum ipsum edipiscing elit, sed diam - nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.Loreum ipsum - edipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam - erat volutpat. -
    -
    - x - px -
      - - - - -
    -
    -
    -
    - -
    -
    - {#advimage_dlg.swap_image} - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
     
    - - - - -
     
    -
    - -
    - {#advimage_dlg.misc} - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    - - - - -
     
    -
    -
    -
    - -
    - - -
    -
    - - + + + + {#advimage_dlg.dialog_title} + + + + + + + + + + +
    + + +
    +
    +
    + {#advimage_dlg.general} + + + + + + + + + + + + + + + + + + + +
    + +
    + {#advimage_dlg.preview} + +
    +
    + +
    +
    + {#advimage_dlg.tab_appearance} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + {#advimage_dlg.example_img} + Lorem ipsum, Dolor sit amet, consectetuer adipiscing loreum ipsum edipiscing elit, sed diam + nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.Loreum ipsum + edipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam + erat volutpat. +
    +
    + + x + + px +
      + + + + +
    +
    +
    +
    + +
    +
    + {#advimage_dlg.swap_image} + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
     
    + + + + +
     
    +
    + +
    + {#advimage_dlg.misc} + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + + + + +
     
    +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/advimage/js/image.js b/js/tiny_mce/plugins/advimage/js/image.js index 3bda86a2..7e3cf00e 100644 --- a/js/tiny_mce/plugins/advimage/js/image.js +++ b/js/tiny_mce/plugins/advimage/js/image.js @@ -1,443 +1,462 @@ -var ImageDialog = { - preInit : function() { - var url; - - tinyMCEPopup.requireLangPack(); - - if (url = tinyMCEPopup.getParam("external_image_list_url")) - document.write(''); - }, - - init : function(ed) { - var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode(); - - tinyMCEPopup.resizeToInnerSize(); - this.fillClassList('class_list'); - this.fillFileList('src_list', 'tinyMCEImageList'); - this.fillFileList('over_list', 'tinyMCEImageList'); - this.fillFileList('out_list', 'tinyMCEImageList'); - TinyMCE_EditableSelects.init(); - - if (n.nodeName == 'IMG') { - nl.src.value = dom.getAttrib(n, 'src'); - nl.width.value = dom.getAttrib(n, 'width'); - nl.height.value = dom.getAttrib(n, 'height'); - nl.alt.value = dom.getAttrib(n, 'alt'); - nl.title.value = dom.getAttrib(n, 'title'); - nl.vspace.value = this.getAttrib(n, 'vspace'); - nl.hspace.value = this.getAttrib(n, 'hspace'); - nl.border.value = this.getAttrib(n, 'border'); - selectByValue(f, 'align', this.getAttrib(n, 'align')); - selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true); - nl.style.value = dom.getAttrib(n, 'style'); - nl.id.value = dom.getAttrib(n, 'id'); - nl.dir.value = dom.getAttrib(n, 'dir'); - nl.lang.value = dom.getAttrib(n, 'lang'); - nl.usemap.value = dom.getAttrib(n, 'usemap'); - nl.longdesc.value = dom.getAttrib(n, 'longdesc'); - nl.insert.value = ed.getLang('update'); - - if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover'))) - nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); - - if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout'))) - nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); - - if (ed.settings.inline_styles) { - // Move attribs to styles - if (dom.getAttrib(n, 'align')) - this.updateStyle('align'); - - if (dom.getAttrib(n, 'hspace')) - this.updateStyle('hspace'); - - if (dom.getAttrib(n, 'border')) - this.updateStyle('border'); - - if (dom.getAttrib(n, 'vspace')) - this.updateStyle('vspace'); - } - } - - // Setup browse button - document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); - if (isVisible('srcbrowser')) - document.getElementById('src').style.width = '260px'; - - // Setup browse button - document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image'); - if (isVisible('overbrowser')) - document.getElementById('onmouseoversrc').style.width = '260px'; - - // Setup browse button - document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image'); - if (isVisible('outbrowser')) - document.getElementById('onmouseoutsrc').style.width = '260px'; - - // If option enabled default contrain proportions to checked - if (ed.getParam("advimage_constrain_proportions", true)) - f.constrain.checked = true; - - // Check swap image if valid data - if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value) - this.setSwapImage(true); - else - this.setSwapImage(false); - - this.changeAppearance(); - this.showPreviewImage(nl.src.value, 1); - }, - - insert : function(file, title) { - var ed = tinyMCEPopup.editor, t = this, f = document.forms[0]; - - if (f.src.value === '') { - if (ed.selection.getNode().nodeName == 'IMG') { - ed.dom.remove(ed.selection.getNode()); - ed.execCommand('mceRepaint'); - } - - tinyMCEPopup.close(); - return; - } - - if (tinyMCEPopup.getParam("accessibility_warnings", 1)) { - if (!f.alt.value) { - tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) { - if (s) - t.insertAndClose(); - }); - - return; - } - } - - t.insertAndClose(); - }, - - insertAndClose : function() { - var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el; - - tinyMCEPopup.restoreSelection(); - - // Fixes crash in Safari - if (tinymce.isWebKit) - ed.getWin().focus(); - - if (!ed.settings.inline_styles) { - args = { - vspace : nl.vspace.value, - hspace : nl.hspace.value, - border : nl.border.value, - align : getSelectValue(f, 'align') - }; - } else { - // Remove deprecated values - args = { - vspace : '', - hspace : '', - border : '', - align : '' - }; - } - - tinymce.extend(args, { - src : nl.src.value, - width : nl.width.value, - height : nl.height.value, - alt : nl.alt.value, - title : nl.title.value, - 'class' : getSelectValue(f, 'class_list'), - style : nl.style.value, - id : nl.id.value, - dir : nl.dir.value, - lang : nl.lang.value, - usemap : nl.usemap.value, - longdesc : nl.longdesc.value - }); - - args.onmouseover = args.onmouseout = ''; - - if (f.onmousemovecheck.checked) { - if (nl.onmouseoversrc.value) - args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';"; - - if (nl.onmouseoutsrc.value) - args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';"; - } - - el = ed.selection.getNode(); - - if (el && el.nodeName == 'IMG') { - ed.dom.setAttribs(el, args); - } else { - ed.execCommand('mceInsertContent', false, '', {skip_undo : 1}); - ed.dom.setAttribs('__mce_tmp', args); - ed.dom.setAttrib('__mce_tmp', 'id', ''); - ed.undoManager.add(); - } - - tinyMCEPopup.close(); - }, - - getAttrib : function(e, at) { - var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; - - if (ed.settings.inline_styles) { - switch (at) { - case 'align': - if (v = dom.getStyle(e, 'float')) - return v; - - if (v = dom.getStyle(e, 'vertical-align')) - return v; - - break; - - case 'hspace': - v = dom.getStyle(e, 'margin-left') - v2 = dom.getStyle(e, 'margin-right'); - - if (v && v == v2) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - - case 'vspace': - v = dom.getStyle(e, 'margin-top') - v2 = dom.getStyle(e, 'margin-bottom'); - if (v && v == v2) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - - case 'border': - v = 0; - - tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { - sv = dom.getStyle(e, 'border-' + sv + '-width'); - - // False or not the same as prev - if (!sv || (sv != v && v !== 0)) { - v = 0; - return false; - } - - if (sv) - v = sv; - }); - - if (v) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - } - } - - if (v = dom.getAttrib(e, at)) - return v; - - return ''; - }, - - setSwapImage : function(st) { - var f = document.forms[0]; - - f.onmousemovecheck.checked = st; - setBrowserDisabled('overbrowser', !st); - setBrowserDisabled('outbrowser', !st); - - if (f.over_list) - f.over_list.disabled = !st; - - if (f.out_list) - f.out_list.disabled = !st; - - f.onmouseoversrc.disabled = !st; - f.onmouseoutsrc.disabled = !st; - }, - - fillClassList : function(id) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; - - if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { - cl = []; - - tinymce.each(v.split(';'), function(v) { - var p = v.split('='); - - cl.push({'title' : p[0], 'class' : p[1]}); - }); - } else - cl = tinyMCEPopup.editor.dom.getClasses(); - - if (cl.length > 0) { - lst.options.length = 0; - lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); - - tinymce.each(cl, function(o) { - lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); - }); - } else - dom.remove(dom.getParent(id, 'tr')); - }, - - fillFileList : function(id, l) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; - - l = window[l]; - lst.options.length = 0; - - if (l && l.length > 0) { - lst.options[lst.options.length] = new Option('', ''); - - tinymce.each(l, function(o) { - lst.options[lst.options.length] = new Option(o[0], o[1]); - }); - } else - dom.remove(dom.getParent(id, 'tr')); - }, - - resetImageData : function() { - var f = document.forms[0]; - - f.elements.width.value = f.elements.height.value = ''; - }, - - updateImageData : function(img, st) { - var f = document.forms[0]; - - if (!st) { - f.elements.width.value = img.width; - f.elements.height.value = img.height; - } - - this.preloadImg = img; - }, - - changeAppearance : function() { - var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg'); - - if (img) { - if (ed.getParam('inline_styles')) { - ed.dom.setAttrib(img, 'style', f.style.value); - } else { - img.align = f.align.value; - img.border = f.border.value; - img.hspace = f.hspace.value; - img.vspace = f.vspace.value; - } - } - }, - - changeHeight : function() { - var f = document.forms[0], tp, t = this; - - if (!f.constrain.checked || !t.preloadImg) { - return; - } - - if (f.width.value == "" || f.height.value == "") - return; - - tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height; - f.height.value = tp.toFixed(0); - }, - - changeWidth : function() { - var f = document.forms[0], tp, t = this; - - if (!f.constrain.checked || !t.preloadImg) { - return; - } - - if (f.width.value == "" || f.height.value == "") - return; - - tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width; - f.width.value = tp.toFixed(0); - }, - - updateStyle : function(ty) { - var dom = tinyMCEPopup.dom, st, v, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value}); - - if (tinyMCEPopup.editor.settings.inline_styles) { - // Handle align - if (ty == 'align') { - dom.setStyle(img, 'float', ''); - dom.setStyle(img, 'vertical-align', ''); - - v = getSelectValue(f, 'align'); - if (v) { - if (v == 'left' || v == 'right') - dom.setStyle(img, 'float', v); - else - img.style.verticalAlign = v; - } - } - - // Handle border - if (ty == 'border') { - dom.setStyle(img, 'border', ''); - - v = f.border.value; - if (v || v == '0') { - if (v == '0') - img.style.border = '0'; - else - img.style.border = v + 'px solid black'; - } - } - - // Handle hspace - if (ty == 'hspace') { - dom.setStyle(img, 'marginLeft', ''); - dom.setStyle(img, 'marginRight', ''); - - v = f.hspace.value; - if (v) { - img.style.marginLeft = v + 'px'; - img.style.marginRight = v + 'px'; - } - } - - // Handle vspace - if (ty == 'vspace') { - dom.setStyle(img, 'marginTop', ''); - dom.setStyle(img, 'marginBottom', ''); - - v = f.vspace.value; - if (v) { - img.style.marginTop = v + 'px'; - img.style.marginBottom = v + 'px'; - } - } - - // Merge - dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText), 'img'); - } - }, - - changeMouseMove : function() { - }, - - showPreviewImage : function(u, st) { - if (!u) { - tinyMCEPopup.dom.setHTML('prev', ''); - return; - } - - if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true)) - this.resetImageData(); - - u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u); - - if (!st) - tinyMCEPopup.dom.setHTML('prev', ''); - else - tinyMCEPopup.dom.setHTML('prev', ''); - } -}; - -ImageDialog.preInit(); -tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); +var ImageDialog = { + preInit : function() { + var url; + + tinyMCEPopup.requireLangPack(); + + if (url = tinyMCEPopup.getParam("external_image_list_url")) + document.write(''); + }, + + init : function(ed) { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode(), fl = tinyMCEPopup.getParam('external_image_list', 'tinyMCEImageList'); + + tinyMCEPopup.resizeToInnerSize(); + this.fillClassList('class_list'); + this.fillFileList('src_list', fl); + this.fillFileList('over_list', fl); + this.fillFileList('out_list', fl); + TinyMCE_EditableSelects.init(); + + if (n.nodeName == 'IMG') { + nl.src.value = dom.getAttrib(n, 'src'); + nl.width.value = dom.getAttrib(n, 'width'); + nl.height.value = dom.getAttrib(n, 'height'); + nl.alt.value = dom.getAttrib(n, 'alt'); + nl.title.value = dom.getAttrib(n, 'title'); + nl.vspace.value = this.getAttrib(n, 'vspace'); + nl.hspace.value = this.getAttrib(n, 'hspace'); + nl.border.value = this.getAttrib(n, 'border'); + selectByValue(f, 'align', this.getAttrib(n, 'align')); + selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true); + nl.style.value = dom.getAttrib(n, 'style'); + nl.id.value = dom.getAttrib(n, 'id'); + nl.dir.value = dom.getAttrib(n, 'dir'); + nl.lang.value = dom.getAttrib(n, 'lang'); + nl.usemap.value = dom.getAttrib(n, 'usemap'); + nl.longdesc.value = dom.getAttrib(n, 'longdesc'); + nl.insert.value = ed.getLang('update'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover'))) + nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout'))) + nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1'); + + if (ed.settings.inline_styles) { + // Move attribs to styles + if (dom.getAttrib(n, 'align')) + this.updateStyle('align'); + + if (dom.getAttrib(n, 'hspace')) + this.updateStyle('hspace'); + + if (dom.getAttrib(n, 'border')) + this.updateStyle('border'); + + if (dom.getAttrib(n, 'vspace')) + this.updateStyle('vspace'); + } + } + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image'); + if (isVisible('overbrowser')) + document.getElementById('onmouseoversrc').style.width = '260px'; + + // Setup browse button + document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image'); + if (isVisible('outbrowser')) + document.getElementById('onmouseoutsrc').style.width = '260px'; + + // If option enabled default contrain proportions to checked + if (ed.getParam("advimage_constrain_proportions", true)) + f.constrain.checked = true; + + // Check swap image if valid data + if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value) + this.setSwapImage(true); + else + this.setSwapImage(false); + + this.changeAppearance(); + this.showPreviewImage(nl.src.value, 1); + }, + + insert : function(file, title) { + var ed = tinyMCEPopup.editor, t = this, f = document.forms[0]; + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (tinyMCEPopup.getParam("accessibility_warnings", 1)) { + if (!f.alt.value) { + tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) { + if (s) + t.insertAndClose(); + }); + + return; + } + } + + t.insertAndClose(); + }, + + insertAndClose : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + // Fixes crash in Safari + if (tinymce.isWebKit) + ed.getWin().focus(); + + if (!ed.settings.inline_styles) { + args = { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }; + } else { + // Remove deprecated values + args = { + vspace : '', + hspace : '', + border : '', + align : '' + }; + } + + tinymce.extend(args, { + src : nl.src.value.replace(/ /g, '%20'), + width : nl.width.value, + height : nl.height.value, + alt : nl.alt.value, + title : nl.title.value, + 'class' : getSelectValue(f, 'class_list'), + style : nl.style.value, + id : nl.id.value, + dir : nl.dir.value, + lang : nl.lang.value, + usemap : nl.usemap.value, + longdesc : nl.longdesc.value + }); + + args.onmouseover = args.onmouseout = ''; + + if (f.onmousemovecheck.checked) { + if (nl.onmouseoversrc.value) + args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';"; + + if (nl.onmouseoutsrc.value) + args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';"; + } + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + } else { + tinymce.each(args, function(value, name) { + if (value === "") { + delete args[name]; + } + }); + + ed.execCommand('mceInsertContent', false, tinyMCEPopup.editor.dom.createHTML('img', args), {skip_undo : 1}); + ed.undoManager.add(); + } + + tinyMCEPopup.editor.execCommand('mceRepaint'); + tinyMCEPopup.editor.focus(); + tinyMCEPopup.close(); + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + setSwapImage : function(st) { + var f = document.forms[0]; + + f.onmousemovecheck.checked = st; + setBrowserDisabled('overbrowser', !st); + setBrowserDisabled('outbrowser', !st); + + if (f.over_list) + f.over_list.disabled = !st; + + if (f.out_list) + f.out_list.disabled = !st; + + f.onmouseoversrc.disabled = !st; + f.onmouseoutsrc.disabled = !st; + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options.length = 0; + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = typeof(l) === 'function' ? l() : window[l]; + lst.options.length = 0; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.elements.width.value = f.elements.height.value = ''; + }, + + updateImageData : function(img, st) { + var f = document.forms[0]; + + if (!st) { + f.elements.width.value = img.width; + f.elements.height.value = img.height; + } + + this.preloadImg = img; + }, + + changeAppearance : function() { + var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg'); + + if (img) { + if (ed.getParam('inline_styles')) { + ed.dom.setAttrib(img, 'style', f.style.value); + } else { + img.align = f.align.value; + img.border = f.border.value; + img.hspace = f.hspace.value; + img.vspace = f.vspace.value; + } + } + }, + + changeHeight : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height; + f.height.value = tp.toFixed(0); + }, + + changeWidth : function() { + var f = document.forms[0], tp, t = this; + + if (!f.constrain.checked || !t.preloadImg) { + return; + } + + if (f.width.value == "" || f.height.value == "") + return; + + tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width; + f.width.value = tp.toFixed(0); + }, + + updateStyle : function(ty) { + var dom = tinyMCEPopup.dom, b, bStyle, bColor, v, isIE = tinymce.isIE, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value}); + + if (tinyMCEPopup.editor.settings.inline_styles) { + // Handle align + if (ty == 'align') { + dom.setStyle(img, 'float', ''); + dom.setStyle(img, 'vertical-align', ''); + + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') + dom.setStyle(img, 'float', v); + else + img.style.verticalAlign = v; + } + } + + // Handle border + if (ty == 'border') { + b = img.style.border ? img.style.border.split(' ') : []; + bStyle = dom.getStyle(img, 'border-style'); + bColor = dom.getStyle(img, 'border-color'); + + dom.setStyle(img, 'border', ''); + + v = f.border.value; + if (v || v == '0') { + if (v == '0') + img.style.border = isIE ? '0' : '0 none none'; + else { + if (b.length == 3 && b[isIE ? 2 : 1]) + bStyle = b[isIE ? 2 : 1]; + else if (!bStyle || bStyle == 'none') + bStyle = 'solid'; + if (b.length == 3 && b[isIE ? 0 : 2]) + bColor = b[isIE ? 0 : 2]; + else if (!bColor || bColor == 'none') + bColor = 'black'; + img.style.border = v + 'px ' + bStyle + ' ' + bColor; + } + } + } + + // Handle hspace + if (ty == 'hspace') { + dom.setStyle(img, 'marginLeft', ''); + dom.setStyle(img, 'marginRight', ''); + + v = f.hspace.value; + if (v) { + img.style.marginLeft = v + 'px'; + img.style.marginRight = v + 'px'; + } + } + + // Handle vspace + if (ty == 'vspace') { + dom.setStyle(img, 'marginTop', ''); + dom.setStyle(img, 'marginBottom', ''); + + v = f.vspace.value; + if (v) { + img.style.marginTop = v + 'px'; + img.style.marginBottom = v + 'px'; + } + } + + // Merge + dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText), 'img'); + } + }, + + changeMouseMove : function() { + }, + + showPreviewImage : function(u, st) { + if (!u) { + tinyMCEPopup.dom.setHTML('prev', ''); + return; + } + + if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true)) + this.resetImageData(); + + u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u); + + if (!st) + tinyMCEPopup.dom.setHTML('prev', ''); + else + tinyMCEPopup.dom.setHTML('prev', ''); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/js/tiny_mce/plugins/advimage/langs/en_dlg.js b/js/tiny_mce/plugins/advimage/langs/en_dlg.js index f493d196..5f122e2c 100644 --- a/js/tiny_mce/plugins/advimage/langs/en_dlg.js +++ b/js/tiny_mce/plugins/advimage/langs/en_dlg.js @@ -1,43 +1 @@ -tinyMCE.addI18n('en.advimage_dlg',{ -tab_general:"General", -tab_appearance:"Appearance", -tab_advanced:"Advanced", -general:"General", -title:"Title", -preview:"Preview", -constrain_proportions:"Constrain proportions", -langdir:"Language direction", -langcode:"Language code", -long_desc:"Long description link", -style:"Style", -classes:"Classes", -ltr:"Left to right", -rtl:"Right to left", -id:"Id", -map:"Image map", -swap_image:"Swap image", -alt_image:"Alternative image", -mouseover:"for mouse over", -mouseout:"for mouse out", -misc:"Miscellaneous", -example_img:"Appearance preview image", -missing_alt:"Are you sure you want to continue without including an Image Description? Without it the image may not be accessible to some users with disabilities, or to those using a text browser, or browsing the Web with images turned off.", -dialog_title:"Insert/edit image", -src:"Image URL", -alt:"Image description", -list:"Image list", -border:"Border", -dimensions:"Dimensions", -vspace:"Vertical space", -hspace:"Horizontal space", -align:"Alignment", -align_baseline:"Baseline", -align_top:"Top", -align_middle:"Middle", -align_bottom:"Bottom", -align_texttop:"Text top", -align_textbottom:"Text bottom", -align_left:"Left", -align_right:"Right", -image_list:"Image list" -}); \ No newline at end of file +tinyMCE.addI18n('en.advimage_dlg',{"image_list":"Image List","align_right":"Right","align_left":"Left","align_textbottom":"Text Bottom","align_texttop":"Text Top","align_bottom":"Bottom","align_middle":"Middle","align_top":"Top","align_baseline":"Baseline",align:"Alignment",hspace:"Horizontal Space",vspace:"Vertical Space",dimensions:"Dimensions",border:"Border",list:"Image List",alt:"Image Description",src:"Image URL","dialog_title":"Insert/Edit Image","missing_alt":"Are you sure you want to continue without including an Image Description? Without it the image may not be accessible to some users with disabilities, or to those using a text browser, or browsing the Web with images turned off.","example_img":"Appearance Preview Image",misc:"Miscellaneous",mouseout:"For Mouse Out",mouseover:"For Mouse Over","alt_image":"Alternative Image","swap_image":"Swap Image",map:"Image Map",id:"ID",rtl:"Right to Left",ltr:"Left to Right",classes:"Classes",style:"Style","long_desc":"Long Description Link",langcode:"Language Code",langdir:"Language Direction","constrain_proportions":"Constrain Proportions",preview:"Preview",title:"Title",general:"General","tab_advanced":"Advanced","tab_appearance":"Appearance","tab_general":"General",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advlink/js/advlink.js b/js/tiny_mce/plugins/advlink/js/advlink.js index b78e82f7..cd5cf414 100644 --- a/js/tiny_mce/plugins/advlink/js/advlink.js +++ b/js/tiny_mce/plugins/advlink/js/advlink.js @@ -1,528 +1,532 @@ -/* Functions for the advlink plugin popup */ - -tinyMCEPopup.requireLangPack(); - -var templates = { - "window.open" : "window.open('${url}','${target}','${options}')" -}; - -function preinit() { - var url; - - if (url = tinyMCEPopup.getParam("external_link_list_url")) - document.write(''); -} - -function changeClass() { - var f = document.forms[0]; - - f.classes.value = getSelectValue(f, 'classlist'); -} - -function init() { - tinyMCEPopup.resizeToInnerSize(); - - var formObj = document.forms[0]; - var inst = tinyMCEPopup.editor; - var elm = inst.selection.getNode(); - var action = "insert"; - var html; - - document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink'); - document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink'); - document.getElementById('linklisthrefcontainer').innerHTML = getLinkListHTML('linklisthref','href'); - document.getElementById('anchorlistcontainer').innerHTML = getAnchorListHTML('anchorlist','href'); - document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target'); - - // Link list - html = getLinkListHTML('linklisthref','href'); - if (html == "") - document.getElementById("linklisthrefrow").style.display = 'none'; - else - document.getElementById("linklisthrefcontainer").innerHTML = html; - - // Resize some elements - if (isVisible('hrefbrowser')) - document.getElementById('href').style.width = '260px'; - - if (isVisible('popupurlbrowser')) - document.getElementById('popupurl').style.width = '180px'; - - elm = inst.dom.getParent(elm, "A"); - if (elm != null && elm.nodeName == "A") - action = "update"; - - formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true); - - setPopupControlsDisabled(true); - - if (action == "update") { - var href = inst.dom.getAttrib(elm, 'href'); - var onclick = inst.dom.getAttrib(elm, 'onclick'); - - // Setup form data - setFormValue('href', href); - setFormValue('title', inst.dom.getAttrib(elm, 'title')); - setFormValue('id', inst.dom.getAttrib(elm, 'id')); - setFormValue('style', inst.dom.getAttrib(elm, "style")); - setFormValue('rel', inst.dom.getAttrib(elm, 'rel')); - setFormValue('rev', inst.dom.getAttrib(elm, 'rev')); - setFormValue('charset', inst.dom.getAttrib(elm, 'charset')); - setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang')); - setFormValue('dir', inst.dom.getAttrib(elm, 'dir')); - setFormValue('lang', inst.dom.getAttrib(elm, 'lang')); - setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); - setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); - setFormValue('type', inst.dom.getAttrib(elm, 'type')); - setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus')); - setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur')); - setFormValue('onclick', onclick); - setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick')); - setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown')); - setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup')); - setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover')); - setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove')); - setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout')); - setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress')); - setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown')); - setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup')); - setFormValue('target', inst.dom.getAttrib(elm, 'target')); - setFormValue('classes', inst.dom.getAttrib(elm, 'class')); - - // Parse onclick data - if (onclick != null && onclick.indexOf('window.open') != -1) - parseWindowOpen(onclick); - else - parseFunction(onclick); - - // Select by the values - selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir')); - selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel')); - selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev')); - selectByValue(formObj, 'linklisthref', href); - - if (href.charAt(0) == '#') - selectByValue(formObj, 'anchorlist', href); - - addClassesToList('classlist', 'advlink_styles'); - - selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true); - selectByValue(formObj, 'targetlist', inst.dom.getAttrib(elm, 'target'), true); - } else - addClassesToList('classlist', 'advlink_styles'); -} - -function checkPrefix(n) { - if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email'))) - n.value = 'mailto:' + n.value; - - if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external'))) - n.value = 'http://' + n.value; -} - -function setFormValue(name, value) { - document.forms[0].elements[name].value = value; -} - -function parseWindowOpen(onclick) { - var formObj = document.forms[0]; - - // Preprocess center code - if (onclick.indexOf('return false;') != -1) { - formObj.popupreturn.checked = true; - onclick = onclick.replace('return false;', ''); - } else - formObj.popupreturn.checked = false; - - var onClickData = parseLink(onclick); - - if (onClickData != null) { - formObj.ispopup.checked = true; - setPopupControlsDisabled(false); - - var onClickWindowOptions = parseOptions(onClickData['options']); - var url = onClickData['url']; - - formObj.popupname.value = onClickData['target']; - formObj.popupurl.value = url; - formObj.popupwidth.value = getOption(onClickWindowOptions, 'width'); - formObj.popupheight.value = getOption(onClickWindowOptions, 'height'); - - formObj.popupleft.value = getOption(onClickWindowOptions, 'left'); - formObj.popuptop.value = getOption(onClickWindowOptions, 'top'); - - if (formObj.popupleft.value.indexOf('screen') != -1) - formObj.popupleft.value = "c"; - - if (formObj.popuptop.value.indexOf('screen') != -1) - formObj.popuptop.value = "c"; - - formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes"; - formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes"; - formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes"; - formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes"; - formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes"; - formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes"; - formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes"; - - buildOnClick(); - } -} - -function parseFunction(onclick) { - var formObj = document.forms[0]; - var onClickData = parseLink(onclick); - - // TODO: Add stuff here -} - -function getOption(opts, name) { - return typeof(opts[name]) == "undefined" ? "" : opts[name]; -} - -function setPopupControlsDisabled(state) { - var formObj = document.forms[0]; - - formObj.popupname.disabled = state; - formObj.popupurl.disabled = state; - formObj.popupwidth.disabled = state; - formObj.popupheight.disabled = state; - formObj.popupleft.disabled = state; - formObj.popuptop.disabled = state; - formObj.popuplocation.disabled = state; - formObj.popupscrollbars.disabled = state; - formObj.popupmenubar.disabled = state; - formObj.popupresizable.disabled = state; - formObj.popuptoolbar.disabled = state; - formObj.popupstatus.disabled = state; - formObj.popupreturn.disabled = state; - formObj.popupdependent.disabled = state; - - setBrowserDisabled('popupurlbrowser', state); -} - -function parseLink(link) { - link = link.replace(new RegExp(''', 'g'), "'"); - - var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1"); - - // Is function name a template function - var template = templates[fnName]; - if (template) { - // Build regexp - var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi")); - var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\("; - var replaceStr = ""; - for (var i=0; i'); - for (var i=0; i'; - html += ''; - - for (i=0; i' + name + ''; - } - - html += ''; - - return html; -} - -function insertAction() { - var inst = tinyMCEPopup.editor; - var elm, elementArray, i; - - elm = inst.selection.getNode(); - checkPrefix(document.forms[0].href); - - elm = inst.dom.getParent(elm, "A"); - - // Remove element if there is no href - if (!document.forms[0].href.value) { - tinyMCEPopup.execCommand("mceBeginUndoLevel"); - i = inst.selection.getBookmark(); - inst.dom.remove(elm, 1); - inst.selection.moveToBookmark(i); - tinyMCEPopup.execCommand("mceEndUndoLevel"); - tinyMCEPopup.close(); - return; - } - - tinyMCEPopup.execCommand("mceBeginUndoLevel"); - - // Create new anchor elements - if (elm == null) { - inst.getDoc().execCommand("unlink", false, null); - tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1}); - - elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';}); - for (i=0; i' + tinyMCELinkList[i][0] + ''; - - html += ''; - - return html; - - // tinyMCE.debug('-- image list start --', html, '-- image list end --'); -} - -function getTargetListHTML(elm_id, target_form_element) { - var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';'); - var html = ''; - - html += ''; - - return html; -} - -// While loading -preinit(); -tinyMCEPopup.onInit.add(init); +/* Functions for the advlink plugin popup */ + +tinyMCEPopup.requireLangPack(); + +var templates = { + "window.open" : "window.open('${url}','${target}','${options}')" +}; + +function preinit() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); +} + +function changeClass() { + var f = document.forms[0]; + + f.classes.value = getSelectValue(f, 'classlist'); +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + var formObj = document.forms[0]; + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + var action = "insert"; + var html; + + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink'); + document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink'); + document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target'); + + // Link list + html = getLinkListHTML('linklisthref','href'); + if (html == "") + document.getElementById("linklisthrefrow").style.display = 'none'; + else + document.getElementById("linklisthrefcontainer").innerHTML = html; + + // Anchor list + html = getAnchorListHTML('anchorlist','href'); + if (html == "") + document.getElementById("anchorlistrow").style.display = 'none'; + else + document.getElementById("anchorlistcontainer").innerHTML = html; + + // Resize some elements + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '260px'; + + if (isVisible('popupurlbrowser')) + document.getElementById('popupurl').style.width = '180px'; + + elm = inst.dom.getParent(elm, "A"); + if (elm != null && elm.nodeName == "A") + action = "update"; + + formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true); + + setPopupControlsDisabled(true); + + if (action == "update") { + var href = inst.dom.getAttrib(elm, 'href'); + var onclick = inst.dom.getAttrib(elm, 'onclick'); + + // Setup form data + setFormValue('href', href); + setFormValue('title', inst.dom.getAttrib(elm, 'title')); + setFormValue('id', inst.dom.getAttrib(elm, 'id')); + setFormValue('style', inst.dom.getAttrib(elm, "style")); + setFormValue('rel', inst.dom.getAttrib(elm, 'rel')); + setFormValue('rev', inst.dom.getAttrib(elm, 'rev')); + setFormValue('charset', inst.dom.getAttrib(elm, 'charset')); + setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang')); + setFormValue('dir', inst.dom.getAttrib(elm, 'dir')); + setFormValue('lang', inst.dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('type', inst.dom.getAttrib(elm, 'type')); + setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup')); + setFormValue('target', inst.dom.getAttrib(elm, 'target')); + setFormValue('classes', inst.dom.getAttrib(elm, 'class')); + + // Parse onclick data + if (onclick != null && onclick.indexOf('window.open') != -1) + parseWindowOpen(onclick); + else + parseFunction(onclick); + + // Select by the values + selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir')); + selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel')); + selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev')); + selectByValue(formObj, 'linklisthref', href); + + if (href.charAt(0) == '#') + selectByValue(formObj, 'anchorlist', href); + + addClassesToList('classlist', 'advlink_styles'); + + selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true); + selectByValue(formObj, 'targetlist', inst.dom.getAttrib(elm, 'target'), true); + } else + addClassesToList('classlist', 'advlink_styles'); +} + +function checkPrefix(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external'))) + n.value = 'http://' + n.value; +} + +function setFormValue(name, value) { + document.forms[0].elements[name].value = value; +} + +function parseWindowOpen(onclick) { + var formObj = document.forms[0]; + + // Preprocess center code + if (onclick.indexOf('return false;') != -1) { + formObj.popupreturn.checked = true; + onclick = onclick.replace('return false;', ''); + } else + formObj.popupreturn.checked = false; + + var onClickData = parseLink(onclick); + + if (onClickData != null) { + formObj.ispopup.checked = true; + setPopupControlsDisabled(false); + + var onClickWindowOptions = parseOptions(onClickData['options']); + var url = onClickData['url']; + + formObj.popupname.value = onClickData['target']; + formObj.popupurl.value = url; + formObj.popupwidth.value = getOption(onClickWindowOptions, 'width'); + formObj.popupheight.value = getOption(onClickWindowOptions, 'height'); + + formObj.popupleft.value = getOption(onClickWindowOptions, 'left'); + formObj.popuptop.value = getOption(onClickWindowOptions, 'top'); + + if (formObj.popupleft.value.indexOf('screen') != -1) + formObj.popupleft.value = "c"; + + if (formObj.popuptop.value.indexOf('screen') != -1) + formObj.popuptop.value = "c"; + + formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes"; + formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes"; + formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes"; + formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes"; + formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes"; + formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes"; + formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes"; + + buildOnClick(); + } +} + +function parseFunction(onclick) { + var formObj = document.forms[0]; + var onClickData = parseLink(onclick); + + // TODO: Add stuff here +} + +function getOption(opts, name) { + return typeof(opts[name]) == "undefined" ? "" : opts[name]; +} + +function setPopupControlsDisabled(state) { + var formObj = document.forms[0]; + + formObj.popupname.disabled = state; + formObj.popupurl.disabled = state; + formObj.popupwidth.disabled = state; + formObj.popupheight.disabled = state; + formObj.popupleft.disabled = state; + formObj.popuptop.disabled = state; + formObj.popuplocation.disabled = state; + formObj.popupscrollbars.disabled = state; + formObj.popupmenubar.disabled = state; + formObj.popupresizable.disabled = state; + formObj.popuptoolbar.disabled = state; + formObj.popupstatus.disabled = state; + formObj.popupreturn.disabled = state; + formObj.popupdependent.disabled = state; + + setBrowserDisabled('popupurlbrowser', state); +} + +function parseLink(link) { + link = link.replace(new RegExp(''', 'g'), "'"); + + var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1"); + + // Is function name a template function + var template = templates[fnName]; + if (template) { + // Build regexp + var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi")); + var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\("; + var replaceStr = ""; + for (var i=0; i'); + for (var i=0; i' + name + ''; + } + + if (html == "") + return ""; + + html = ''; + + return html; +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm, elementArray, i; + + elm = inst.selection.getNode(); + checkPrefix(document.forms[0].href); + + elm = inst.dom.getParent(elm, "A"); + + // Remove element if there is no href + if (!document.forms[0].href.value) { + i = inst.selection.getBookmark(); + inst.dom.remove(elm, 1); + inst.selection.moveToBookmark(i); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + + // Create new anchor elements + if (elm == null) { + inst.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("mceInsertLink", false, "#mce_temp_url#", {skip_undo : 1}); + + elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';}); + for (i=0; i' + tinyMCELinkList[i][0] + ''; + + html += ''; + + return html; + + // tinyMCE.debug('-- image list start --', html, '-- image list end --'); +} + +function getTargetListHTML(elm_id, target_form_element) { + var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';'); + var html = ''; + + html += ''; + + return html; +} + +// While loading +preinit(); +tinyMCEPopup.onInit.add(init); diff --git a/js/tiny_mce/plugins/advlink/langs/en_dlg.js b/js/tiny_mce/plugins/advlink/langs/en_dlg.js index c71ffbd0..3169a565 100644 --- a/js/tiny_mce/plugins/advlink/langs/en_dlg.js +++ b/js/tiny_mce/plugins/advlink/langs/en_dlg.js @@ -1,52 +1 @@ -tinyMCE.addI18n('en.advlink_dlg',{ -title:"Insert/edit link", -url:"Link URL", -target:"Target", -titlefield:"Title", -is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?", -is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?", -list:"Link list", -general_tab:"General", -popup_tab:"Popup", -events_tab:"Events", -advanced_tab:"Advanced", -general_props:"General properties", -popup_props:"Popup properties", -event_props:"Events", -advanced_props:"Advanced properties", -popup_opts:"Options", -anchor_names:"Anchors", -target_same:"Open in this window / frame", -target_parent:"Open in parent window / frame", -target_top:"Open in top frame (replaces all frames)", -target_blank:"Open in new window", -popup:"Javascript popup", -popup_url:"Popup URL", -popup_name:"Window name", -popup_return:"Insert 'return false'", -popup_scrollbars:"Show scrollbars", -popup_statusbar:"Show status bar", -popup_toolbar:"Show toolbars", -popup_menubar:"Show menu bar", -popup_location:"Show location bar", -popup_resizable:"Make window resizable", -popup_dependent:"Dependent (Mozilla/Firefox only)", -popup_size:"Size", -popup_position:"Position (X/Y)", -id:"Id", -style:"Style", -classes:"Classes", -target_name:"Target name", -langdir:"Language direction", -target_langcode:"Target language", -langcode:"Language code", -encoding:"Target character encoding", -mime:"Target MIME type", -rel:"Relationship page to target", -rev:"Relationship target to page", -tabindex:"Tabindex", -accesskey:"Accesskey", -ltr:"Left to right", -rtl:"Right to left", -link_list:"Link list" -}); \ No newline at end of file +tinyMCE.addI18n('en.advlink_dlg',{"target_name":"Target Name",classes:"Classes",style:"Style",id:"ID","popup_position":"Position (X/Y)",langdir:"Language Direction","popup_size":"Size","popup_dependent":"Dependent (Mozilla/Firefox Only)","popup_resizable":"Make Window Resizable","popup_location":"Show Location Bar","popup_menubar":"Show Menu Bar","popup_toolbar":"Show Toolbars","popup_statusbar":"Show Status Bar","popup_scrollbars":"Show Scrollbars","popup_return":"Insert \'return false\'","popup_name":"Window Name","popup_url":"Popup URL",popup:"JavaScript Popup","target_blank":"Open in New Window","target_top":"Open in Top Frame (Replaces All Frames)","target_parent":"Open in Parent Window/Frame","target_same":"Open in This Window/Frame","anchor_names":"Anchors","popup_opts":"Options","advanced_props":"Advanced Properties","event_props":"Events","popup_props":"Popup Properties","general_props":"General Properties","advanced_tab":"Advanced","events_tab":"Events","popup_tab":"Popup","general_tab":"General",list:"Link List","is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",titlefield:"Title",target:"Target",url:"Link URL",title:"Insert/Edit Link","link_list":"Link List",rtl:"Right to Left",ltr:"Left to Right",accesskey:"AccessKey",tabindex:"TabIndex",rev:"Relationship Target to Page",rel:"Relationship Page to Target",mime:"Target MIME Type",encoding:"Target Character Encoding",langcode:"Language Code","target_langcode":"Target Language",width:"Width",height:"Height"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advlink/link.htm b/js/tiny_mce/plugins/advlink/link.htm index 876669c6..52623ab5 100644 --- a/js/tiny_mce/plugins/advlink/link.htm +++ b/js/tiny_mce/plugins/advlink/link.htm @@ -1,333 +1,338 @@ - - - - {#advlink_dlg.title} - - - - - - - - -
    - - -
    -
    -
    - {#advlink_dlg.general_props} - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
     
    - -
    -
    -
    - - - -
    -
    - {#advlink_dlg.advanced_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    - {#advlink_dlg.event_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    - -
    - - -
    -
    - - + + + + {#advlink_dlg.title} + + + + + + + + + +
    + + + + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/advlist/editor_plugin.js b/js/tiny_mce/plugins/advlist/editor_plugin.js index 02d16971..57ecce6e 100644 --- a/js/tiny_mce/plugins/advlist/editor_plugin.js +++ b/js/tiny_mce/plugins/advlist/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.AdvListPlugin",{init:function(b,c){var d=this;d.editor=b;function e(g){var f=[];a(g.split(/,/),function(h){f.push({title:"advlist."+(h=="default"?"def":h.replace(/-/g,"_")),styles:{listStyleType:h=="default"?"":h}})});return f}d.numlist=b.getParam("advlist_number_styles")||e("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");d.bullist=b.getParam("advlist_bullet_styles")||e("default,circle,disc,square")},createControl:function(d,b){var f=this,e,h;if(d=="numlist"||d=="bullist"){if(f[d][0].title=="advlist.def"){h=f[d][0]}function c(i,k){var j=true;a(k.styles,function(m,l){if(f.editor.dom.getStyle(i,l)!=m){j=false;return false}});return j}function g(){var k,i=f.editor,l=i.dom,j=i.selection;k=l.getParent(j.getNode(),"ol,ul");if(!k||k.nodeName==(d=="bullist"?"OL":"UL")||c(k,h)){i.execCommand(d=="bullist"?"InsertUnorderedList":"InsertOrderedList")}if(h){k=l.getParent(j.getNode(),"ol,ul");if(k){l.setStyles(k,h.styles);k.removeAttribute("_mce_style")}}}e=b.createSplitButton(d,{title:"advanced."+d+"_desc","class":"mce_"+d,onclick:function(){g()}});e.onRenderMenu.add(function(i,j){j.onShowMenu.add(function(){var m=f.editor.dom,l=m.getParent(f.editor.selection.getNode(),"ol,ul"),k;if(l||h){k=f[d];a(j.items,function(n){var o=true;n.setSelected(0);if(l&&!n.isDisabled()){a(k,function(p){if(p.id==n.id){if(!c(l,p)){o=false;return false}}});if(o){n.setSelected(1)}}});if(!l){j.items[h.id].setSelected(1)}}});j.add({id:f.editor.dom.uniqueId(),title:"advlist.types","class":"mceMenuItemTitle"}).setDisabled(1);a(f[d],function(k){k.id=f.editor.dom.uniqueId();j.add({id:k.id,title:k.title,onclick:function(){h=k;g()}})})});return e}},getInfo:function(){return{longname:"Advanced lists",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlist",tinymce.plugins.AdvListPlugin)})(); \ No newline at end of file +(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.AdvListPlugin",{init:function(b,c){var d=this;d.editor=b;function e(g){var f=[];a(g.split(/,/),function(h){f.push({title:"advlist."+(h=="default"?"def":h.replace(/-/g,"_")),styles:{listStyleType:h=="default"?"":h}})});return f}d.numlist=b.getParam("advlist_number_styles")||e("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman");d.bullist=b.getParam("advlist_bullet_styles")||e("default,circle,disc,square");if(tinymce.isIE&&/MSIE [2-7]/.test(navigator.userAgent)){d.isIE7=true}},createControl:function(d,b){var f=this,e,i,g=f.editor;if(d=="numlist"||d=="bullist"){if(f[d][0].title=="advlist.def"){i=f[d][0]}function c(j,l){var k=true;a(l.styles,function(n,m){if(g.dom.getStyle(j,m)!=n){k=false;return false}});return k}function h(){var k,l=g.dom,j=g.selection;k=l.getParent(j.getNode(),"ol,ul");if(!k||k.nodeName==(d=="bullist"?"OL":"UL")||c(k,i)){g.execCommand(d=="bullist"?"InsertUnorderedList":"InsertOrderedList")}if(i){k=l.getParent(j.getNode(),"ol,ul");if(k){l.setStyles(k,i.styles);k.removeAttribute("data-mce-style")}}g.focus()}e=b.createSplitButton(d,{title:"advanced."+d+"_desc","class":"mce_"+d,onclick:function(){h()}});e.onRenderMenu.add(function(j,k){k.onHideMenu.add(function(){if(f.bookmark){g.selection.moveToBookmark(f.bookmark);f.bookmark=0}});k.onShowMenu.add(function(){var n=g.dom,m=n.getParent(g.selection.getNode(),"ol,ul"),l;if(m||i){l=f[d];a(k.items,function(o){var p=true;o.setSelected(0);if(m&&!o.isDisabled()){a(l,function(q){if(q.id==o.id){if(!c(m,q)){p=false;return false}}});if(p){o.setSelected(1)}}});if(!m){k.items[i.id].setSelected(1)}}g.focus();if(tinymce.isIE){f.bookmark=g.selection.getBookmark(1)}});k.add({id:g.dom.uniqueId(),title:"advlist.types","class":"mceMenuItemTitle",titleItem:true}).setDisabled(1);a(f[d],function(l){if(f.isIE7&&l.styles.listStyleType=="lower-greek"){return}l.id=g.dom.uniqueId();k.add({id:l.id,title:l.title,onclick:function(){i=l;h()}})})});return e}},getInfo:function(){return{longname:"Advanced lists",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("advlist",tinymce.plugins.AdvListPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/advlist/editor_plugin_src.js b/js/tiny_mce/plugins/advlist/editor_plugin_src.js index a61887a9..4ee4d34c 100644 --- a/js/tiny_mce/plugins/advlist/editor_plugin_src.js +++ b/js/tiny_mce/plugins/advlist/editor_plugin_src.js @@ -1,154 +1,176 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var each = tinymce.each; - - tinymce.create('tinymce.plugins.AdvListPlugin', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - - function buildFormats(str) { - var formats = []; - - each(str.split(/,/), function(type) { - formats.push({ - title : 'advlist.' + (type == 'default' ? 'def' : type.replace(/-/g, '_')), - styles : { - listStyleType : type == 'default' ? '' : type - } - }); - }); - - return formats; - }; - - // Setup number formats from config or default - t.numlist = ed.getParam("advlist_number_styles") || buildFormats("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman"); - t.bullist = ed.getParam("advlist_bullet_styles") || buildFormats("default,circle,disc,square"); - }, - - createControl: function(name, cm) { - var t = this, btn, format; - - if (name == 'numlist' || name == 'bullist') { - // Default to first item if it's a default item - if (t[name][0].title == 'advlist.def') - format = t[name][0]; - - function hasFormat(node, format) { - var state = true; - - each(format.styles, function(value, name) { - // Format doesn't match - if (t.editor.dom.getStyle(node, name) != value) { - state = false; - return false; - } - }); - - return state; - }; - - function applyListFormat() { - var list, ed = t.editor, dom = ed.dom, sel = ed.selection; - - // Check for existing list element - list = dom.getParent(sel.getNode(), 'ol,ul'); - - // Switch/add list type if needed - if (!list || list.nodeName == (name == 'bullist' ? 'OL' : 'UL') || hasFormat(list, format)) - ed.execCommand(name == 'bullist' ? 'InsertUnorderedList' : 'InsertOrderedList'); - - // Append styles to new list element - if (format) { - list = dom.getParent(sel.getNode(), 'ol,ul'); - - if (list) { - dom.setStyles(list, format.styles); - list.removeAttribute('_mce_style'); - } - } - }; - - btn = cm.createSplitButton(name, { - title : 'advanced.' + name + '_desc', - 'class' : 'mce_' + name, - onclick : function() { - applyListFormat(); - } - }); - - btn.onRenderMenu.add(function(btn, menu) { - menu.onShowMenu.add(function() { - var dom = t.editor.dom, list = dom.getParent(t.editor.selection.getNode(), 'ol,ul'), fmtList; - - if (list || format) { - fmtList = t[name]; - - // Unselect existing items - each(menu.items, function(item) { - var state = true; - - item.setSelected(0); - - if (list && !item.isDisabled()) { - each(fmtList, function(fmt) { - if (fmt.id == item.id) { - if (!hasFormat(list, fmt)) { - state = false; - return false; - } - } - }); - - if (state) - item.setSelected(1); - } - }); - - // Select the current format - if (!list) - menu.items[format.id].setSelected(1); - } - }); - - menu.add({id : t.editor.dom.uniqueId(), title : 'advlist.types', 'class' : 'mceMenuItemTitle'}).setDisabled(1); - - each(t[name], function(item) { - item.id = t.editor.dom.uniqueId(); - - menu.add({id : item.id, title : item.title, onclick : function() { - format = item; - applyListFormat(); - }}); - }); - }); - - return btn; - } - }, - - getInfo : function() { - return { - longname : 'Advanced lists', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('advlist', tinymce.plugins.AdvListPlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each; + + tinymce.create('tinymce.plugins.AdvListPlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + function buildFormats(str) { + var formats = []; + + each(str.split(/,/), function(type) { + formats.push({ + title : 'advlist.' + (type == 'default' ? 'def' : type.replace(/-/g, '_')), + styles : { + listStyleType : type == 'default' ? '' : type + } + }); + }); + + return formats; + }; + + // Setup number formats from config or default + t.numlist = ed.getParam("advlist_number_styles") || buildFormats("default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman"); + t.bullist = ed.getParam("advlist_bullet_styles") || buildFormats("default,circle,disc,square"); + + if (tinymce.isIE && /MSIE [2-7]/.test(navigator.userAgent)) + t.isIE7 = true; + }, + + createControl: function(name, cm) { + var t = this, btn, format, editor = t.editor; + + if (name == 'numlist' || name == 'bullist') { + // Default to first item if it's a default item + if (t[name][0].title == 'advlist.def') + format = t[name][0]; + + function hasFormat(node, format) { + var state = true; + + each(format.styles, function(value, name) { + // Format doesn't match + if (editor.dom.getStyle(node, name) != value) { + state = false; + return false; + } + }); + + return state; + }; + + function applyListFormat() { + var list, dom = editor.dom, sel = editor.selection; + + // Check for existing list element + list = dom.getParent(sel.getNode(), 'ol,ul'); + + // Switch/add list type if needed + if (!list || list.nodeName == (name == 'bullist' ? 'OL' : 'UL') || hasFormat(list, format)) + editor.execCommand(name == 'bullist' ? 'InsertUnorderedList' : 'InsertOrderedList'); + + // Append styles to new list element + if (format) { + list = dom.getParent(sel.getNode(), 'ol,ul'); + if (list) { + dom.setStyles(list, format.styles); + list.removeAttribute('data-mce-style'); + } + } + + editor.focus(); + }; + + btn = cm.createSplitButton(name, { + title : 'advanced.' + name + '_desc', + 'class' : 'mce_' + name, + onclick : function() { + applyListFormat(); + } + }); + + btn.onRenderMenu.add(function(btn, menu) { + menu.onHideMenu.add(function() { + if (t.bookmark) { + editor.selection.moveToBookmark(t.bookmark); + t.bookmark = 0; + } + }); + + menu.onShowMenu.add(function() { + var dom = editor.dom, list = dom.getParent(editor.selection.getNode(), 'ol,ul'), fmtList; + + if (list || format) { + fmtList = t[name]; + + // Unselect existing items + each(menu.items, function(item) { + var state = true; + + item.setSelected(0); + + if (list && !item.isDisabled()) { + each(fmtList, function(fmt) { + if (fmt.id == item.id) { + if (!hasFormat(list, fmt)) { + state = false; + return false; + } + } + }); + + if (state) + item.setSelected(1); + } + }); + + // Select the current format + if (!list) + menu.items[format.id].setSelected(1); + } + + editor.focus(); + + // IE looses it's selection so store it away and restore it later + if (tinymce.isIE) { + t.bookmark = editor.selection.getBookmark(1); + } + }); + + menu.add({id : editor.dom.uniqueId(), title : 'advlist.types', 'class' : 'mceMenuItemTitle', titleItem: true}).setDisabled(1); + + each(t[name], function(item) { + // IE<8 doesn't support lower-greek, skip it + if (t.isIE7 && item.styles.listStyleType == 'lower-greek') + return; + + item.id = editor.dom.uniqueId(); + + menu.add({id : item.id, title : item.title, onclick : function() { + format = item; + applyListFormat(); + }}); + }); + }); + + return btn; + } + }, + + getInfo : function() { + return { + longname : 'Advanced lists', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('advlist', tinymce.plugins.AdvListPlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/autolink/editor_plugin.js b/js/tiny_mce/plugins/autolink/editor_plugin.js new file mode 100644 index 00000000..fd293dca --- /dev/null +++ b/js/tiny_mce/plugins/autolink/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.AutolinkPlugin",{init:function(a,b){var c=this;if(tinyMCE.isIE){return}a.onKeyDown.add(function(d,f){if(f.keyCode==13){return c.handleEnter(d)}});a.onKeyPress.add(function(d,f){if(f.which==41){return c.handleEclipse(d)}});a.onKeyUp.add(function(d,f){if(f.keyCode==32){return c.handleSpacebar(d)}})},handleEclipse:function(a){this.parseCurrentLine(a,-1,"(",true)},handleSpacebar:function(a){this.parseCurrentLine(a,0,"",true)},handleEnter:function(a){this.parseCurrentLine(a,-1,"",false)},parseCurrentLine:function(i,d,b,g){var a,f,c,n,k,m,h,e,j;a=i.selection.getRng().cloneRange();if(a.startOffset<5){e=a.endContainer.previousSibling;if(e==null){if(a.endContainer.firstChild==null||a.endContainer.firstChild.nextSibling==null){return}e=a.endContainer.firstChild.nextSibling}j=e.length;a.setStart(e,j);a.setEnd(e,j);if(a.endOffset<5){return}f=a.endOffset;n=e}else{n=a.endContainer;if(n.nodeType!=3&&n.firstChild){while(n.nodeType!=3&&n.firstChild){n=n.firstChild}a.setStart(n,0);a.setEnd(n,n.nodeValue.length)}if(a.endOffset==1){f=2}else{f=a.endOffset-1-d}}c=f;do{a.setStart(n,f-2);a.setEnd(n,f-1);f-=1}while(a.toString()!=" "&&a.toString()!=""&&a.toString().charCodeAt(0)!=160&&(f-2)>=0&&a.toString()!=b);if(a.toString()==b||a.toString().charCodeAt(0)==160){a.setStart(n,f);a.setEnd(n,c);f+=1}else{if(a.startOffset==0){a.setStart(n,0);a.setEnd(n,c)}else{a.setStart(n,f);a.setEnd(n,c)}}m=a.toString();h=m.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)(.+)$/i);if(h){if(h[1]=="www."){h[1]="http://www."}k=i.selection.getBookmark();i.selection.setRng(a);tinyMCE.execCommand("createlink",false,h[1]+h[2]);i.selection.moveToBookmark(k);if(tinyMCE.isWebKit){i.selection.collapse(false);var l=Math.min(n.length,c+1);a.setStart(n,l);a.setEnd(n,l);i.selection.setRng(a)}}},getInfo:function(){return{longname:"Autolink",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autolink",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autolink",tinymce.plugins.AutolinkPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/autolink/editor_plugin_src.js b/js/tiny_mce/plugins/autolink/editor_plugin_src.js new file mode 100644 index 00000000..604da8b4 --- /dev/null +++ b/js/tiny_mce/plugins/autolink/editor_plugin_src.js @@ -0,0 +1,172 @@ +/** + * editor_plugin_src.js + * + * Copyright 2011, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.AutolinkPlugin', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + + init : function(ed, url) { + var t = this; + + // Internet Explorer has built-in automatic linking + if (tinyMCE.isIE) + return; + + // Add a key down handler + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode == 13) + return t.handleEnter(ed); + }); + + ed.onKeyPress.add(function(ed, e) { + if (e.which == 41) + return t.handleEclipse(ed); + }); + + // Add a key up handler + ed.onKeyUp.add(function(ed, e) { + if (e.keyCode == 32) + return t.handleSpacebar(ed); + }); + }, + + handleEclipse : function(ed) { + this.parseCurrentLine(ed, -1, '(', true); + }, + + handleSpacebar : function(ed) { + this.parseCurrentLine(ed, 0, '', true); + }, + + handleEnter : function(ed) { + this.parseCurrentLine(ed, -1, '', false); + }, + + parseCurrentLine : function(ed, end_offset, delimiter, goback) { + var r, end, start, endContainer, bookmark, text, matches, prev, len; + + // We need at least five characters to form a URL, + // hence, at minimum, five characters from the beginning of the line. + r = ed.selection.getRng().cloneRange(); + if (r.startOffset < 5) { + // During testing, the caret is placed inbetween two text nodes. + // The previous text node contains the URL. + prev = r.endContainer.previousSibling; + if (prev == null) { + if (r.endContainer.firstChild == null || r.endContainer.firstChild.nextSibling == null) + return; + + prev = r.endContainer.firstChild.nextSibling; + } + len = prev.length; + r.setStart(prev, len); + r.setEnd(prev, len); + + if (r.endOffset < 5) + return; + + end = r.endOffset; + endContainer = prev; + } else { + endContainer = r.endContainer; + + // Get a text node + if (endContainer.nodeType != 3 && endContainer.firstChild) { + while (endContainer.nodeType != 3 && endContainer.firstChild) + endContainer = endContainer.firstChild; + + r.setStart(endContainer, 0); + r.setEnd(endContainer, endContainer.nodeValue.length); + } + + if (r.endOffset == 1) + end = 2; + else + end = r.endOffset - 1 - end_offset; + } + + start = end; + + do + { + // Move the selection one character backwards. + r.setStart(endContainer, end - 2); + r.setEnd(endContainer, end - 1); + end -= 1; + + // Loop until one of the following is found: a blank space,  , delimeter, (end-2) >= 0 + } while (r.toString() != ' ' && r.toString() != '' && r.toString().charCodeAt(0) != 160 && (end -2) >= 0 && r.toString() != delimiter); + + if (r.toString() == delimiter || r.toString().charCodeAt(0) == 160) { + r.setStart(endContainer, end); + r.setEnd(endContainer, start); + end += 1; + } else if (r.startOffset == 0) { + r.setStart(endContainer, 0); + r.setEnd(endContainer, start); + } + else { + r.setStart(endContainer, end); + r.setEnd(endContainer, start); + } + + text = r.toString(); + matches = text.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.)(.+)$/i); + + if (matches) { + if (matches[1] == 'www.') { + matches[1] = 'http://www.'; + } + + bookmark = ed.selection.getBookmark(); + + ed.selection.setRng(r); + tinyMCE.execCommand('createlink',false, matches[1] + matches[2]); + ed.selection.moveToBookmark(bookmark); + + // TODO: Determine if this is still needed. + if (tinyMCE.isWebKit) { + // move the caret to its original position + ed.selection.collapse(false); + var max = Math.min(endContainer.length, start + 1); + r.setStart(endContainer, max); + r.setEnd(endContainer, max); + ed.selection.setRng(r); + } + } + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Autolink', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autolink', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('autolink', tinymce.plugins.AutolinkPlugin); +})(); diff --git a/js/tiny_mce/plugins/autoresize/editor_plugin.js b/js/tiny_mce/plugins/autoresize/editor_plugin.js index 220b84ac..6c4ff0d5 100644 --- a/js/tiny_mce/plugins/autoresize/editor_plugin.js +++ b/js/tiny_mce/plugins/autoresize/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this;if(a.getParam("fullscreen_is_enabled")){return}function b(){var h=a.getDoc(),e=h.body,j=h.documentElement,g=tinymce.DOM,i=d.autoresize_min_height,f;f=tinymce.isIE?e.scrollHeight:j.offsetHeight;if(f>d.autoresize_min_height){i=f}g.setStyle(g.get(a.id+"_ifr"),"height",i+"px");if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetHeight;a.onInit.add(function(f,e){f.setProgressState(true);d.throbbing=true;f.getBody().style.overflowY="hidden"});a.onChange.add(b);a.onSetContent.add(b);a.onPaste.add(b);a.onKeyUp.add(b);a.onPostRender.add(b);a.onLoadContent.add(function(f,e){b();setTimeout(function(){b();f.setProgressState(false);d.throbbing=false},1250)});a.addCommand("mceAutoResize",b)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this,e=0;if(a.getParam("fullscreen_is_enabled")){return}function b(){var i=a.getDoc(),f=i.body,k=i.documentElement,h=tinymce.DOM,j=d.autoresize_min_height,g;g=tinymce.isIE?f.scrollHeight:i.body.offsetHeight;if(g>d.autoresize_min_height){j=g}if(d.autoresize_max_height&&g>d.autoresize_max_height){j=d.autoresize_max_height;a.getBody().style.overflowY="auto"}else{a.getBody().style.overflowY="hidden"}if(j!==e){h.setStyle(h.get(a.id+"_ifr"),"height",j+"px");e=j}if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=parseInt(a.getParam("autoresize_min_height",a.getElement().offsetHeight));d.autoresize_max_height=parseInt(a.getParam("autoresize_max_height",0));a.onInit.add(function(f){f.dom.setStyle(f.getBody(),"paddingBottom",f.getParam("autoresize_bottom_margin",50)+"px")});a.onChange.add(b);a.onSetContent.add(b);a.onPaste.add(b);a.onKeyUp.add(b);a.onPostRender.add(b);if(a.getParam("autoresize_on_init",true)){a.onInit.add(function(g,f){g.setProgressState(true);d.throbbing=true;g.getBody().style.overflowY="hidden"});a.onLoadContent.add(function(g,f){b();setTimeout(function(){b();g.setProgressState(false);d.throbbing=false},1250)})}a.addCommand("mceAutoResize",b)},getInfo:function(){return{longname:"Auto Resize",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("autoresize",tinymce.plugins.AutoResizePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/autoresize/editor_plugin_src.js b/js/tiny_mce/plugins/autoresize/editor_plugin_src.js index 37709f56..7d113419 100644 --- a/js/tiny_mce/plugins/autoresize/editor_plugin_src.js +++ b/js/tiny_mce/plugins/autoresize/editor_plugin_src.js @@ -26,7 +26,7 @@ * @param {string} url Absolute URL to where the plugin is located. */ init : function(ed, url) { - var t = this; + var t = this, oldSize = 0; if (ed.getParam('fullscreen_is_enabled')) return; @@ -38,14 +38,24 @@ var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight; // Get height differently depending on the browser used - myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight; + myHeight = tinymce.isIE ? b.scrollHeight : d.body.offsetHeight; // Don't make it smaller than the minimum height if (myHeight > t.autoresize_min_height) resizeHeight = myHeight; + // If a maximum height has been defined don't exceed this height + if (t.autoresize_max_height && myHeight > t.autoresize_max_height) { + resizeHeight = t.autoresize_max_height; + ed.getBody().style.overflowY = "auto"; + } else + ed.getBody().style.overflowY = "hidden"; + // Resize content element - DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px'); + if (resizeHeight !== oldSize) { + DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px'); + oldSize = resizeHeight; + } // if we're throbbing, we'll re-throb to match the new size if (t.throbbing) { @@ -57,16 +67,14 @@ t.editor = ed; // Define minimum height - t.autoresize_min_height = ed.getElement().offsetHeight; + t.autoresize_min_height = parseInt( ed.getParam('autoresize_min_height', ed.getElement().offsetHeight) ); - // Things to do when the editor is ready - ed.onInit.add(function(ed, l) { - // Show throbber until content area is resized properly - ed.setProgressState(true); - t.throbbing = true; + // Define maximum height + t.autoresize_max_height = parseInt( ed.getParam('autoresize_max_height', 0) ); - // Hide scrollbars - ed.getBody().style.overflowY = "hidden"; + // Add padding at the bottom for better UX + ed.onInit.add(function(ed){ + ed.dom.setStyle(ed.getBody(), 'paddingBottom', ed.getParam('autoresize_bottom_margin', 50) + 'px'); }); // Add appropriate listeners for resizing content area @@ -76,20 +84,32 @@ ed.onKeyUp.add(resize); ed.onPostRender.add(resize); - ed.onLoadContent.add(function(ed, l) { - resize(); + if (ed.getParam('autoresize_on_init', true)) { + // Things to do when the editor is ready + ed.onInit.add(function(ed, l) { + // Show throbber until content area is resized properly + ed.setProgressState(true); + t.throbbing = true; + + // Hide scrollbars + ed.getBody().style.overflowY = "hidden"; + }); - // Because the content area resizes when its content CSS loads, - // and we can't easily add a listener to its onload event, - // we'll just trigger a resize after a short loading period - setTimeout(function() { + ed.onLoadContent.add(function(ed, l) { resize(); - // Disable throbber - ed.setProgressState(false); - t.throbbing = false; - }, 1250); - }); + // Because the content area resizes when its content CSS loads, + // and we can't easily add a listener to its onload event, + // we'll just trigger a resize after a short loading period + setTimeout(function() { + resize(); + + // Disable throbber + ed.setProgressState(false); + t.throbbing = false; + }, 1250); + }); + } // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample'); ed.addCommand('mceAutoResize', resize); @@ -114,4 +134,4 @@ // Register plugin tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin); -})(); \ No newline at end of file +})(); diff --git a/js/tiny_mce/plugins/autosave/editor_plugin.js b/js/tiny_mce/plugins/autosave/editor_plugin.js index b33ebfb7..f7d05760 100644 --- a/js/tiny_mce/plugins/autosave/editor_plugin.js +++ b/js/tiny_mce/plugins/autosave/editor_plugin.js @@ -1 +1 @@ -(function(e){var c="autosave",g="restoredraft",b=true,f,d,a=e.util.Dispatcher;e.create("tinymce.plugins.AutoSave",{init:function(i,j){var h=this,l=i.settings;h.editor=i;function k(n){var m={s:1000,m:60000};n=/^(\d+)([ms]?)$/.exec(""+n);return(n[2]?m[n[2]]:1)*parseInt(n)}e.each({ask_before_unload:b,interval:"30s",retention:"20m",minlength:50},function(n,m){m=c+"_"+m;if(l[m]===f){l[m]=n}});l.autosave_interval=k(l.autosave_interval);l.autosave_retention=k(l.autosave_retention);i.addButton(g,{title:c+".restore_content",onclick:function(){if(i.getContent().replace(/\s| |<\/?p[^>]*>|]*>/gi,"").length>0){i.windowManager.confirm(c+".warning_message",function(m){if(m){h.restoreDraft()}})}else{h.restoreDraft()}}});i.onNodeChange.add(function(){var m=i.controlManager;if(m.get(g)){m.setDisabled(g,!h.hasDraft())}});i.onInit.add(function(){if(i.controlManager.get(g)){h.setupStorage(i);setInterval(function(){h.storeDraft();i.nodeChanged()},l.autosave_interval)}});h.onStoreDraft=new a(h);h.onRestoreDraft=new a(h);h.onRemoveDraft=new a(h);if(!d){window.onbeforeunload=e.plugins.AutoSave._beforeUnloadHandler;d=b}},getInfo:function(){return{longname:"Auto save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave",version:e.majorVersion+"."+e.minorVersion}},getExpDate:function(){return new Date(new Date().getTime()+this.editor.settings.autosave_retention).toUTCString()},setupStorage:function(i){var h=this,k=c+"_test",j="OK";h.key=c+i.id;e.each([function(){if(localStorage){localStorage.setItem(k,j);if(localStorage.getItem(k)===j){localStorage.removeItem(k);return localStorage}}},function(){if(sessionStorage){sessionStorage.setItem(k,j);if(sessionStorage.getItem(k)===j){sessionStorage.removeItem(k);return sessionStorage}}},function(){if(e.isIE){i.getElement().style.behavior="url('#default#userData')";return{autoExpires:b,setItem:function(l,n){var m=i.getElement();m.setAttribute(l,n);m.expires=h.getExpDate();m.save("TinyMCE")},getItem:function(l){var m=i.getElement();m.load("TinyMCE");return m.getAttribute(l)},removeItem:function(l){i.getElement().removeAttribute(l)}}}},],function(l){try{h.storage=l();if(h.storage){return false}}catch(m){}})},storeDraft:function(){var i=this,l=i.storage,j=i.editor,h,k;if(l){if(!l.getItem(i.key)&&!j.isDirty()){return}k=j.getContent();if(k.length>j.settings.autosave_minlength){h=i.getExpDate();if(!i.storage.autoExpires){i.storage.setItem(i.key+"_expires",h)}i.storage.setItem(i.key,k);i.onStoreDraft.dispatch(i,{expires:h,content:k})}}},restoreDraft:function(){var h=this,i=h.storage;if(i){content=i.getItem(h.key);if(content){h.editor.setContent(content);h.onRestoreDraft.dispatch(h,{content:content})}}},hasDraft:function(){var h=this,k=h.storage,i,j;if(k){j=!!k.getItem(h.key);if(j){if(!h.storage.autoExpires){i=new Date(k.getItem(h.key+"_expires"));if(new Date().getTime()]*>|]*>/gi,"").length>0){i.windowManager.confirm(c+".warning_message",function(m){if(m){h.restoreDraft()}})}else{h.restoreDraft()}}});i.onNodeChange.add(function(){var m=i.controlManager;if(m.get(g)){m.setDisabled(g,!h.hasDraft())}});i.onInit.add(function(){if(i.controlManager.get(g)){h.setupStorage(i);setInterval(function(){h.storeDraft();i.nodeChanged()},l.autosave_interval)}});h.onStoreDraft=new a(h);h.onRestoreDraft=new a(h);h.onRemoveDraft=new a(h);if(!d){window.onbeforeunload=e.plugins.AutoSave._beforeUnloadHandler;d=b}},getInfo:function(){return{longname:"Auto save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave",version:e.majorVersion+"."+e.minorVersion}},getExpDate:function(){return new Date(new Date().getTime()+this.editor.settings.autosave_retention).toUTCString()},setupStorage:function(i){var h=this,k=c+"_test",j="OK";h.key=c+i.id;e.each([function(){if(localStorage){localStorage.setItem(k,j);if(localStorage.getItem(k)===j){localStorage.removeItem(k);return localStorage}}},function(){if(sessionStorage){sessionStorage.setItem(k,j);if(sessionStorage.getItem(k)===j){sessionStorage.removeItem(k);return sessionStorage}}},function(){if(e.isIE){i.getElement().style.behavior="url('#default#userData')";return{autoExpires:b,setItem:function(l,n){var m=i.getElement();m.setAttribute(l,n);m.expires=h.getExpDate();try{m.save("TinyMCE")}catch(o){}},getItem:function(l){var m=i.getElement();try{m.load("TinyMCE");return m.getAttribute(l)}catch(n){return null}},removeItem:function(l){i.getElement().removeAttribute(l)}}}},],function(l){try{h.storage=l();if(h.storage){return false}}catch(m){}})},storeDraft:function(){var i=this,l=i.storage,j=i.editor,h,k;if(l){if(!l.getItem(i.key)&&!j.isDirty()){return}k=j.getContent({draft:true});if(k.length>j.settings.autosave_minlength){h=i.getExpDate();if(!i.storage.autoExpires){i.storage.setItem(i.key+"_expires",h)}i.storage.setItem(i.key,k);i.onStoreDraft.dispatch(i,{expires:h,content:k})}}},restoreDraft:function(){var h=this,j=h.storage,i;if(j){i=j.getItem(h.key);if(i){h.editor.setContent(i);h.onRestoreDraft.dispatch(h,{content:i})}}},hasDraft:function(){var h=this,k=h.storage,i,j;if(k){j=!!k.getItem(h.key);if(j){if(!h.storage.autoExpires){i=new Date(k.getItem(h.key+"_expires"));if(new Date().getTime()]*>|]*>/gi, "").length > 0) { - // Show confirm dialog if the editor isn't empty - ed.windowManager.confirm( - PLUGIN_NAME + ".warning_message", - function(ok) { - if (ok) - self.restoreDraft(); - } - ); - } else - self.restoreDraft(); - } - }); - - // Enable/disable restoredraft button depending on if there is a draft stored or not - ed.onNodeChange.add(function() { - var controlManager = ed.controlManager; - - if (controlManager.get(RESTORE_DRAFT)) - controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft()); - }); - - ed.onInit.add(function() { - // Check if the user added the restore button, then setup auto storage logic - if (ed.controlManager.get(RESTORE_DRAFT)) { - // Setup storage engine - self.setupStorage(ed); - - // Auto save contents each interval time - setInterval(function() { - self.storeDraft(); - ed.nodeChanged(); - }, settings.autosave_interval); - } - }); - - /** - * This event gets fired when a draft is stored to local storage. - * - * @event onStoreDraft - * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. - * @param {Object} draft Draft object containing the HTML contents of the editor. - */ - self.onStoreDraft = new Dispatcher(self); - - /** - * This event gets fired when a draft is restored from local storage. - * - * @event onStoreDraft - * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. - * @param {Object} draft Draft object containing the HTML contents of the editor. - */ - self.onRestoreDraft = new Dispatcher(self); - - /** - * This event gets fired when a draft removed/expired. - * - * @event onRemoveDraft - * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. - * @param {Object} draft Draft object containing the HTML contents of the editor. - */ - self.onRemoveDraft = new Dispatcher(self); - - // Add ask before unload dialog only add one unload handler - if (!unloadHandlerAdded) { - window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler; - unloadHandlerAdded = TRUE; - } - }, - - /** - * Returns information about the plugin as a name/value array. - * The current keys are longname, author, authorurl, infourl and version. - * - * @method getInfo - * @return {Object} Name/value array containing information about the plugin. - */ - getInfo : function() { - return { - longname : 'Auto save', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - /** - * Returns an expiration date UTC string. - * - * @method getExpDate - * @return {String} Expiration date UTC string. - */ - getExpDate : function() { - return new Date( - new Date().getTime() + this.editor.settings.autosave_retention - ).toUTCString(); - }, - - /** - * This method will setup the storage engine. If the browser has support for it. - * - * @method setupStorage - */ - setupStorage : function(ed) { - var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK"; - - self.key = PLUGIN_NAME + ed.id; - - // Loop though each storage engine type until we find one that works - tinymce.each([ - function() { - // Try HTML5 Local Storage - if (localStorage) { - localStorage.setItem(testKey, testVal); - - if (localStorage.getItem(testKey) === testVal) { - localStorage.removeItem(testKey); - - return localStorage; - } - } - }, - - function() { - // Try HTML5 Session Storage - if (sessionStorage) { - sessionStorage.setItem(testKey, testVal); - - if (sessionStorage.getItem(testKey) === testVal) { - sessionStorage.removeItem(testKey); - - return sessionStorage; - } - } - }, - - function() { - // Try IE userData - if (tinymce.isIE) { - ed.getElement().style.behavior = "url('#default#userData')"; - - // Fake localStorage on old IE - return { - autoExpires : TRUE, - - setItem : function(key, value) { - var userDataElement = ed.getElement(); - - userDataElement.setAttribute(key, value); - userDataElement.expires = self.getExpDate(); - userDataElement.save("TinyMCE"); - }, - - getItem : function(key) { - var userDataElement = ed.getElement(); - - userDataElement.load("TinyMCE"); - - return userDataElement.getAttribute(key); - }, - - removeItem : function(key) { - ed.getElement().removeAttribute(key); - } - }; - } - }, - ], function(setup) { - // Try executing each function to find a suitable storage engine - try { - self.storage = setup(); - - if (self.storage) - return false; - } catch (e) { - // Ignore - } - }); - }, - - /** - * This method will store the current contents in the the storage engine. - * - * @method storeDraft - */ - storeDraft : function() { - var self = this, storage = self.storage, editor = self.editor, expires, content; - - // Is the contents dirty - if (storage) { - // If there is no existing key and the contents hasn't been changed since - // it's original value then there is no point in saving a draft - if (!storage.getItem(self.key) && !editor.isDirty()) - return; - - // Store contents if the contents if longer than the minlength of characters - content = editor.getContent(); - if (content.length > editor.settings.autosave_minlength) { - expires = self.getExpDate(); - - // Store expiration date if needed IE userData has auto expire built in - if (!self.storage.autoExpires) - self.storage.setItem(self.key + "_expires", expires); - - self.storage.setItem(self.key, content); - self.onStoreDraft.dispatch(self, { - expires : expires, - content : content - }); - } - } - }, - - /** - * This method will restore the contents from the storage engine back to the editor. - * - * @method restoreDraft - */ - restoreDraft : function() { - var self = this, storage = self.storage; - - if (storage) { - content = storage.getItem(self.key); - - if (content) { - self.editor.setContent(content); - self.onRestoreDraft.dispatch(self, { - content : content - }); - } - } - }, - - /** - * This method will return true/false if there is a local storage draft available. - * - * @method hasDraft - * @return {boolean} true/false state if there is a local draft. - */ - hasDraft : function() { - var self = this, storage = self.storage, expDate, exists; - - if (storage) { - // Does the item exist at all - exists = !!storage.getItem(self.key); - if (exists) { - // Storage needs autoexpire - if (!self.storage.autoExpires) { - expDate = new Date(storage.getItem(self.key + "_expires")); - - // Contents hasn't expired - if (new Date().getTime() < expDate.getTime()) - return TRUE; - - // Remove it if it has - self.removeDraft(); - } else - return TRUE; - } - } - - return false; - }, - - /** - * Removes the currently stored draft. - * - * @method removeDraft - */ - removeDraft : function() { - var self = this, storage = self.storage, key = self.key, content; - - if (storage) { - // Get current contents and remove the existing draft - content = storage.getItem(key); - storage.removeItem(key); - storage.removeItem(key + "_expires"); - - // Dispatch remove event if we had any contents - if (content) { - self.onRemoveDraft.dispatch(self, { - content : content - }); - } - } - }, - - "static" : { - // Internal unload handler will be called before the page is unloaded - _beforeUnloadHandler : function(e) { - var msg; - - tinymce.each(tinyMCE.editors, function(ed) { - // Store a draft for each editor instance - if (ed.plugins.autosave) - ed.plugins.autosave.storeDraft(); - - // Never ask in fullscreen mode - if (ed.getParam("fullscreen_is_enabled")) - return; - - // Setup a return message if the editor is dirty - if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload")) - msg = ed.getLang("autosave.unload_msg"); - }); - - return msg; - } - } - }); - - tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave); -})(tinymce); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + * + * Adds auto-save capability to the TinyMCE text editor to rescue content + * inadvertently lost. This plugin was originally developed by Speednet + * and that project can be found here: http://code.google.com/p/tinyautosave/ + * + * TECHNOLOGY DISCUSSION: + * + * The plugin attempts to use the most advanced features available in the current browser to save + * as much content as possible. There are a total of four different methods used to autosave the + * content. In order of preference, they are: + * + * 1. localStorage - A new feature of HTML 5, localStorage can store megabytes of data per domain + * on the client computer. Data stored in the localStorage area has no expiration date, so we must + * manage expiring the data ourselves. localStorage is fully supported by IE8, and it is supposed + * to be working in Firefox 3 and Safari 3.2, but in reality is is flaky in those browsers. As + * HTML 5 gets wider support, the AutoSave plugin will use it automatically. In Windows Vista/7, + * localStorage is stored in the following folder: + * C:\Users\[username]\AppData\Local\Microsoft\Internet Explorer\DOMStore\[tempFolder] + * + * 2. sessionStorage - A new feature of HTML 5, sessionStorage works similarly to localStorage, + * except it is designed to expire after a certain amount of time. Because the specification + * around expiration date/time is very loosely-described, it is preferrable to use locaStorage and + * manage the expiration ourselves. sessionStorage has similar storage characteristics to + * localStorage, although it seems to have better support by Firefox 3 at the moment. (That will + * certainly change as Firefox continues getting better at HTML 5 adoption.) + * + * 3. UserData - A very under-exploited feature of Microsoft Internet Explorer, UserData is a + * way to store up to 128K of data per "document", or up to 1MB of data per domain, on the client + * computer. The feature is available for IE 5+, which makes it available for every version of IE + * supported by TinyMCE. The content is persistent across browser restarts and expires on the + * date/time specified, just like a cookie. However, the data is not cleared when the user clears + * cookies on the browser, which makes it well-suited for rescuing autosaved content. UserData, + * like other Microsoft IE browser technologies, is implemented as a behavior attached to a + * specific DOM object, so in this case we attach the behavior to the same DOM element that the + * TinyMCE editor instance is attached to. + */ + +(function(tinymce) { + // Setup constants to help the compressor to reduce script size + var PLUGIN_NAME = 'autosave', + RESTORE_DRAFT = 'restoredraft', + TRUE = true, + undefined, + unloadHandlerAdded, + Dispatcher = tinymce.util.Dispatcher; + + /** + * This plugin adds auto-save capability to the TinyMCE text editor to rescue content + * inadvertently lost. By using localStorage. + * + * @class tinymce.plugins.AutoSave + */ + tinymce.create('tinymce.plugins.AutoSave', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @method init + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed, url) { + var self = this, settings = ed.settings; + + self.editor = ed; + + // Parses the specified time string into a milisecond number 10m, 10s etc. + function parseTime(time) { + var multipels = { + s : 1000, + m : 60000 + }; + + time = /^(\d+)([ms]?)$/.exec('' + time); + + return (time[2] ? multipels[time[2]] : 1) * parseInt(time); + }; + + // Default config + tinymce.each({ + ask_before_unload : TRUE, + interval : '30s', + retention : '20m', + minlength : 50 + }, function(value, key) { + key = PLUGIN_NAME + '_' + key; + + if (settings[key] === undefined) + settings[key] = value; + }); + + // Parse times + settings.autosave_interval = parseTime(settings.autosave_interval); + settings.autosave_retention = parseTime(settings.autosave_retention); + + // Register restore button + ed.addButton(RESTORE_DRAFT, { + title : PLUGIN_NAME + ".restore_content", + onclick : function() { + if (ed.getContent({draft: true}).replace(/\s| |<\/?p[^>]*>|]*>/gi, "").length > 0) { + // Show confirm dialog if the editor isn't empty + ed.windowManager.confirm( + PLUGIN_NAME + ".warning_message", + function(ok) { + if (ok) + self.restoreDraft(); + } + ); + } else + self.restoreDraft(); + } + }); + + // Enable/disable restoredraft button depending on if there is a draft stored or not + ed.onNodeChange.add(function() { + var controlManager = ed.controlManager; + + if (controlManager.get(RESTORE_DRAFT)) + controlManager.setDisabled(RESTORE_DRAFT, !self.hasDraft()); + }); + + ed.onInit.add(function() { + // Check if the user added the restore button, then setup auto storage logic + if (ed.controlManager.get(RESTORE_DRAFT)) { + // Setup storage engine + self.setupStorage(ed); + + // Auto save contents each interval time + setInterval(function() { + self.storeDraft(); + ed.nodeChanged(); + }, settings.autosave_interval); + } + }); + + /** + * This event gets fired when a draft is stored to local storage. + * + * @event onStoreDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onStoreDraft = new Dispatcher(self); + + /** + * This event gets fired when a draft is restored from local storage. + * + * @event onStoreDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onRestoreDraft = new Dispatcher(self); + + /** + * This event gets fired when a draft removed/expired. + * + * @event onRemoveDraft + * @param {tinymce.plugins.AutoSave} sender Plugin instance sending the event. + * @param {Object} draft Draft object containing the HTML contents of the editor. + */ + self.onRemoveDraft = new Dispatcher(self); + + // Add ask before unload dialog only add one unload handler + if (!unloadHandlerAdded) { + window.onbeforeunload = tinymce.plugins.AutoSave._beforeUnloadHandler; + unloadHandlerAdded = TRUE; + } + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @method getInfo + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Auto save', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + /** + * Returns an expiration date UTC string. + * + * @method getExpDate + * @return {String} Expiration date UTC string. + */ + getExpDate : function() { + return new Date( + new Date().getTime() + this.editor.settings.autosave_retention + ).toUTCString(); + }, + + /** + * This method will setup the storage engine. If the browser has support for it. + * + * @method setupStorage + */ + setupStorage : function(ed) { + var self = this, testKey = PLUGIN_NAME + '_test', testVal = "OK"; + + self.key = PLUGIN_NAME + ed.id; + + // Loop though each storage engine type until we find one that works + tinymce.each([ + function() { + // Try HTML5 Local Storage + if (localStorage) { + localStorage.setItem(testKey, testVal); + + if (localStorage.getItem(testKey) === testVal) { + localStorage.removeItem(testKey); + + return localStorage; + } + } + }, + + function() { + // Try HTML5 Session Storage + if (sessionStorage) { + sessionStorage.setItem(testKey, testVal); + + if (sessionStorage.getItem(testKey) === testVal) { + sessionStorage.removeItem(testKey); + + return sessionStorage; + } + } + }, + + function() { + // Try IE userData + if (tinymce.isIE) { + ed.getElement().style.behavior = "url('#default#userData')"; + + // Fake localStorage on old IE + return { + autoExpires : TRUE, + + setItem : function(key, value) { + var userDataElement = ed.getElement(); + + userDataElement.setAttribute(key, value); + userDataElement.expires = self.getExpDate(); + + try { + userDataElement.save("TinyMCE"); + } catch (e) { + // Ignore, saving might fail if "Userdata Persistence" is disabled in IE + } + }, + + getItem : function(key) { + var userDataElement = ed.getElement(); + + try { + userDataElement.load("TinyMCE"); + return userDataElement.getAttribute(key); + } catch (e) { + // Ignore, loading might fail if "Userdata Persistence" is disabled in IE + return null; + } + }, + + removeItem : function(key) { + ed.getElement().removeAttribute(key); + } + }; + } + }, + ], function(setup) { + // Try executing each function to find a suitable storage engine + try { + self.storage = setup(); + + if (self.storage) + return false; + } catch (e) { + // Ignore + } + }); + }, + + /** + * This method will store the current contents in the the storage engine. + * + * @method storeDraft + */ + storeDraft : function() { + var self = this, storage = self.storage, editor = self.editor, expires, content; + + // Is the contents dirty + if (storage) { + // If there is no existing key and the contents hasn't been changed since + // it's original value then there is no point in saving a draft + if (!storage.getItem(self.key) && !editor.isDirty()) + return; + + // Store contents if the contents if longer than the minlength of characters + content = editor.getContent({draft: true}); + if (content.length > editor.settings.autosave_minlength) { + expires = self.getExpDate(); + + // Store expiration date if needed IE userData has auto expire built in + if (!self.storage.autoExpires) + self.storage.setItem(self.key + "_expires", expires); + + self.storage.setItem(self.key, content); + self.onStoreDraft.dispatch(self, { + expires : expires, + content : content + }); + } + } + }, + + /** + * This method will restore the contents from the storage engine back to the editor. + * + * @method restoreDraft + */ + restoreDraft : function() { + var self = this, storage = self.storage, content; + + if (storage) { + content = storage.getItem(self.key); + + if (content) { + self.editor.setContent(content); + self.onRestoreDraft.dispatch(self, { + content : content + }); + } + } + }, + + /** + * This method will return true/false if there is a local storage draft available. + * + * @method hasDraft + * @return {boolean} true/false state if there is a local draft. + */ + hasDraft : function() { + var self = this, storage = self.storage, expDate, exists; + + if (storage) { + // Does the item exist at all + exists = !!storage.getItem(self.key); + if (exists) { + // Storage needs autoexpire + if (!self.storage.autoExpires) { + expDate = new Date(storage.getItem(self.key + "_expires")); + + // Contents hasn't expired + if (new Date().getTime() < expDate.getTime()) + return TRUE; + + // Remove it if it has + self.removeDraft(); + } else + return TRUE; + } + } + + return false; + }, + + /** + * Removes the currently stored draft. + * + * @method removeDraft + */ + removeDraft : function() { + var self = this, storage = self.storage, key = self.key, content; + + if (storage) { + // Get current contents and remove the existing draft + content = storage.getItem(key); + storage.removeItem(key); + storage.removeItem(key + "_expires"); + + // Dispatch remove event if we had any contents + if (content) { + self.onRemoveDraft.dispatch(self, { + content : content + }); + } + } + }, + + "static" : { + // Internal unload handler will be called before the page is unloaded + _beforeUnloadHandler : function(e) { + var msg; + + tinymce.each(tinyMCE.editors, function(ed) { + // Store a draft for each editor instance + if (ed.plugins.autosave) + ed.plugins.autosave.storeDraft(); + + // Never ask in fullscreen mode + if (ed.getParam("fullscreen_is_enabled")) + return; + + // Setup a return message if the editor is dirty + if (!msg && ed.isDirty() && ed.getParam("autosave_ask_before_unload")) + msg = ed.getLang("autosave.unload_msg"); + }); + + return msg; + } + } + }); + + tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSave); +})(tinymce); diff --git a/js/tiny_mce/plugins/bbcode/editor_plugin.js b/js/tiny_mce/plugins/bbcode/editor_plugin.js index 930fdff0..8f8821fd 100644 --- a/js/tiny_mce/plugins/bbcode/editor_plugin.js +++ b/js/tiny_mce/plugins/bbcode/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a,b){var d=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.onBeforeSetContent.add(function(e,f){f.content=d["_"+c+"_bbcode2html"](f.content)});a.onPostProcess.add(function(e,f){if(f.set){f.content=d["_"+c+"_bbcode2html"](f.content)}if(f.get){f.content=d["_"+c+"_html2bbcode"](f.content)}})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_punbb_html2bbcode:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/(.*?)<\/a>/gi,"[url=$1]$2[/url]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/span>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/font>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/span>/gi,"[size=$1]$2[/size]");b(/(.*?)<\/font>/gi,"$1");b(//gi,"[img]$1[/img]");b(/(.*?)<\/span>/gi,"[code]$1[/code]");b(/(.*?)<\/span>/gi,"[quote]$1[/quote]");b(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");b(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");b(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");b(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");b(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");b(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");b(/<\/(strong|b)>/gi,"[/b]");b(/<(strong|b)>/gi,"[b]");b(/<\/(em|i)>/gi,"[/i]");b(/<(em|i)>/gi,"[i]");b(/<\/u>/gi,"[/u]");b(/(.*?)<\/span>/gi,"[u]$1[/u]");b(//gi,"[u]");b(/]*>/gi,"[quote]");b(/<\/blockquote>/gi,"[/quote]");b(/
    /gi,"\n");b(//gi,"\n");b(/
    /gi,"\n");b(/

    /gi,"");b(/<\/p>/gi,"\n");b(/ /gi," ");b(/"/gi,'"');b(/</gi,"<");b(/>/gi,">");b(/&/gi,"&");return a},_punbb_bbcode2html:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/\n/gi,"
    ");b(/\[b\]/gi,"");b(/\[\/b\]/gi,"");b(/\[i\]/gi,"");b(/\[\/i\]/gi,"");b(/\[u\]/gi,"");b(/\[\/u\]/gi,"");b(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'$2');b(/\[url\](.*?)\[\/url\]/gi,'$1');b(/\[img\](.*?)\[\/img\]/gi,'');b(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2');b(/\[code\](.*?)\[\/code\]/gi,'$1 ');b(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 ');return a}});tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a,b){var d=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.onBeforeSetContent.add(function(e,f){f.content=d["_"+c+"_bbcode2html"](f.content)});a.onPostProcess.add(function(e,f){if(f.set){f.content=d["_"+c+"_bbcode2html"](f.content)}if(f.get){f.content=d["_"+c+"_html2bbcode"](f.content)}})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_punbb_html2bbcode:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/(.*?)<\/a>/gi,"[url=$1]$2[/url]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");b(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");b(/(.*?)<\/span>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/font>/gi,"[color=$1]$2[/color]");b(/(.*?)<\/span>/gi,"[size=$1]$2[/size]");b(/(.*?)<\/font>/gi,"$1");b(//gi,"[img]$1[/img]");b(/(.*?)<\/span>/gi,"[code]$1[/code]");b(/(.*?)<\/span>/gi,"[quote]$1[/quote]");b(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");b(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");b(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");b(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");b(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");b(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");b(/<\/(strong|b)>/gi,"[/b]");b(/<(strong|b)>/gi,"[b]");b(/<\/(em|i)>/gi,"[/i]");b(/<(em|i)>/gi,"[i]");b(/<\/u>/gi,"[/u]");b(/(.*?)<\/span>/gi,"[u]$1[/u]");b(//gi,"[u]");b(/]*>/gi,"[quote]");b(/<\/blockquote>/gi,"[/quote]");b(/
    /gi,"\n");b(//gi,"\n");b(/
    /gi,"\n");b(/

    /gi,"");b(/<\/p>/gi,"\n");b(/ |\u00a0/gi," ");b(/"/gi,'"');b(/</gi,"<");b(/>/gi,">");b(/&/gi,"&");return a},_punbb_bbcode2html:function(a){a=tinymce.trim(a);function b(c,d){a=a.replace(c,d)}b(/\n/gi,"
    ");b(/\[b\]/gi,"");b(/\[\/b\]/gi,"");b(/\[i\]/gi,"");b(/\[\/i\]/gi,"");b(/\[u\]/gi,"");b(/\[\/u\]/gi,"");b(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'$2');b(/\[url\](.*?)\[\/url\]/gi,'$1');b(/\[img\](.*?)\[\/img\]/gi,'');b(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2');b(/\[code\](.*?)\[\/code\]/gi,'$1 ');b(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 ');return a}});tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/bbcode/editor_plugin_src.js b/js/tiny_mce/plugins/bbcode/editor_plugin_src.js index 5586637f..12cdacaa 100644 --- a/js/tiny_mce/plugins/bbcode/editor_plugin_src.js +++ b/js/tiny_mce/plugins/bbcode/editor_plugin_src.js @@ -1,120 +1,120 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.BBCodePlugin', { - init : function(ed, url) { - var t = this, dialect = ed.getParam('bbcode_dialect', 'punbb').toLowerCase(); - - ed.onBeforeSetContent.add(function(ed, o) { - o.content = t['_' + dialect + '_bbcode2html'](o.content); - }); - - ed.onPostProcess.add(function(ed, o) { - if (o.set) - o.content = t['_' + dialect + '_bbcode2html'](o.content); - - if (o.get) - o.content = t['_' + dialect + '_html2bbcode'](o.content); - }); - }, - - getInfo : function() { - return { - longname : 'BBCode Plugin', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - // Private methods - - // HTML -> BBCode in PunBB dialect - _punbb_html2bbcode : function(s) { - s = tinymce.trim(s); - - function rep(re, str) { - s = s.replace(re, str); - }; - - // example: to [b] - rep(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"); - rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); - rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); - rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); - rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); - rep(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"); - rep(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"); - rep(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"); - rep(/(.*?)<\/font>/gi,"$1"); - rep(//gi,"[img]$1[/img]"); - rep(/(.*?)<\/span>/gi,"[code]$1[/code]"); - rep(/(.*?)<\/span>/gi,"[quote]$1[/quote]"); - rep(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"); - rep(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"); - rep(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"); - rep(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"); - rep(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"); - rep(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"); - rep(/<\/(strong|b)>/gi,"[/b]"); - rep(/<(strong|b)>/gi,"[b]"); - rep(/<\/(em|i)>/gi,"[/i]"); - rep(/<(em|i)>/gi,"[i]"); - rep(/<\/u>/gi,"[/u]"); - rep(/(.*?)<\/span>/gi,"[u]$1[/u]"); - rep(//gi,"[u]"); - rep(/]*>/gi,"[quote]"); - rep(/<\/blockquote>/gi,"[/quote]"); - rep(/
    /gi,"\n"); - rep(//gi,"\n"); - rep(/
    /gi,"\n"); - rep(/

    /gi,""); - rep(/<\/p>/gi,"\n"); - rep(/ /gi," "); - rep(/"/gi,"\""); - rep(/</gi,"<"); - rep(/>/gi,">"); - rep(/&/gi,"&"); - - return s; - }, - - // BBCode -> HTML from PunBB dialect - _punbb_bbcode2html : function(s) { - s = tinymce.trim(s); - - function rep(re, str) { - s = s.replace(re, str); - }; - - // example: [b] to - rep(/\n/gi,"
    "); - rep(/\[b\]/gi,""); - rep(/\[\/b\]/gi,""); - rep(/\[i\]/gi,""); - rep(/\[\/i\]/gi,""); - rep(/\[u\]/gi,""); - rep(/\[\/u\]/gi,""); - rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"$2"); - rep(/\[url\](.*?)\[\/url\]/gi,"$1"); - rep(/\[img\](.*?)\[\/img\]/gi,""); - rep(/\[color=(.*?)\](.*?)\[\/color\]/gi,"$2"); - rep(/\[code\](.*?)\[\/code\]/gi,"$1 "); - rep(/\[quote.*?\](.*?)\[\/quote\]/gi,"$1 "); - - return s; - } - }); - - // Register plugin - tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.BBCodePlugin', { + init : function(ed, url) { + var t = this, dialect = ed.getParam('bbcode_dialect', 'punbb').toLowerCase(); + + ed.onBeforeSetContent.add(function(ed, o) { + o.content = t['_' + dialect + '_bbcode2html'](o.content); + }); + + ed.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t['_' + dialect + '_bbcode2html'](o.content); + + if (o.get) + o.content = t['_' + dialect + '_html2bbcode'](o.content); + }); + }, + + getInfo : function() { + return { + longname : 'BBCode Plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + // HTML -> BBCode in PunBB dialect + _punbb_html2bbcode : function(s) { + s = tinymce.trim(s); + + function rep(re, str) { + s = s.replace(re, str); + }; + + // example: to [b] + rep(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"); + rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); + rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); + rep(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"); + rep(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"); + rep(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"); + rep(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"); + rep(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"); + rep(/(.*?)<\/font>/gi,"$1"); + rep(//gi,"[img]$1[/img]"); + rep(/(.*?)<\/span>/gi,"[code]$1[/code]"); + rep(/(.*?)<\/span>/gi,"[quote]$1[/quote]"); + rep(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"); + rep(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"); + rep(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"); + rep(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"); + rep(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"); + rep(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"); + rep(/<\/(strong|b)>/gi,"[/b]"); + rep(/<(strong|b)>/gi,"[b]"); + rep(/<\/(em|i)>/gi,"[/i]"); + rep(/<(em|i)>/gi,"[i]"); + rep(/<\/u>/gi,"[/u]"); + rep(/(.*?)<\/span>/gi,"[u]$1[/u]"); + rep(//gi,"[u]"); + rep(/]*>/gi,"[quote]"); + rep(/<\/blockquote>/gi,"[/quote]"); + rep(/
    /gi,"\n"); + rep(//gi,"\n"); + rep(/
    /gi,"\n"); + rep(/

    /gi,""); + rep(/<\/p>/gi,"\n"); + rep(/ |\u00a0/gi," "); + rep(/"/gi,"\""); + rep(/</gi,"<"); + rep(/>/gi,">"); + rep(/&/gi,"&"); + + return s; + }, + + // BBCode -> HTML from PunBB dialect + _punbb_bbcode2html : function(s) { + s = tinymce.trim(s); + + function rep(re, str) { + s = s.replace(re, str); + }; + + // example: [b] to + rep(/\n/gi,"
    "); + rep(/\[b\]/gi,""); + rep(/\[\/b\]/gi,""); + rep(/\[i\]/gi,""); + rep(/\[\/i\]/gi,""); + rep(/\[u\]/gi,""); + rep(/\[\/u\]/gi,""); + rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"$2"); + rep(/\[url\](.*?)\[\/url\]/gi,"$1"); + rep(/\[img\](.*?)\[\/img\]/gi,""); + rep(/\[color=(.*?)\](.*?)\[\/color\]/gi,"$2"); + rep(/\[code\](.*?)\[\/code\]/gi,"$1 "); + rep(/\[quote.*?\](.*?)\[\/quote\]/gi,"$1 "); + + return s; + } + }); + + // Register plugin + tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/contextmenu/editor_plugin.js b/js/tiny_mce/plugins/contextmenu/editor_plugin.js index 24ee2eb4..af7ae544 100644 --- a/js/tiny_mce/plugins/contextmenu/editor_plugin.js +++ b/js/tiny_mce/plugins/contextmenu/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.dom.Event,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.ContextMenu",{init:function(d){var f=this;f.editor=d;f.onContextMenu=new tinymce.util.Dispatcher(this);d.onContextMenu.add(function(g,h){if(!h.ctrlKey){f._getMenu(g).showMenu(h.clientX,h.clientY);a.add(g.getDoc(),"click",e);a.cancel(h)}});function e(){if(f._menu){f._menu.removeAll();f._menu.destroy();a.remove(d.getDoc(),"click",e)}}d.onMouseDown.add(e);d.onKeyDown.add(e)},getInfo:function(){return{longname:"Contextmenu",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_getMenu:function(h){var l=this,f=l._menu,i=h.selection,e=i.isCollapsed(),d=i.getNode()||h.getBody(),g,k,j;if(f){f.removeAll();f.destroy()}k=b.getPos(h.getContentAreaContainer());j=b.getPos(h.getContainer());f=h.controlManager.createDropMenu("contextmenu",{offset_x:k.x+h.getParam("contextmenu_offset_x",0),offset_y:k.y+h.getParam("contextmenu_offset_y",0),constrain:1});l._menu=f;f.add({title:"advanced.cut_desc",icon:"cut",cmd:"Cut"}).setDisabled(e);f.add({title:"advanced.copy_desc",icon:"copy",cmd:"Copy"}).setDisabled(e);f.add({title:"advanced.paste_desc",icon:"paste",cmd:"Paste"});if((d.nodeName=="A"&&!h.dom.getAttrib(d,"name"))||!e){f.addSeparator();f.add({title:"advanced.link_desc",icon:"link",cmd:h.plugins.advlink?"mceAdvLink":"mceLink",ui:true});f.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"})}f.addSeparator();f.add({title:"advanced.image_desc",icon:"image",cmd:h.plugins.advimage?"mceAdvImage":"mceImage",ui:true});f.addSeparator();g=f.addMenu({title:"contextmenu.align"});g.add({title:"contextmenu.left",icon:"justifyleft",cmd:"JustifyLeft"});g.add({title:"contextmenu.center",icon:"justifycenter",cmd:"JustifyCenter"});g.add({title:"contextmenu.right",icon:"justifyright",cmd:"JustifyRight"});g.add({title:"contextmenu.full",icon:"justifyfull",cmd:"JustifyFull"});l.onContextMenu.dispatch(l,f,d,e);return f}});tinymce.PluginManager.add("contextmenu",tinymce.plugins.ContextMenu)})(); \ No newline at end of file +(function(){var a=tinymce.dom.Event,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.ContextMenu",{init:function(e){var h=this,f,d,i;h.editor=e;d=e.settings.contextmenu_never_use_native;h.onContextMenu=new tinymce.util.Dispatcher(this);f=e.onContextMenu.add(function(j,k){if((i!==0?i:k.ctrlKey)&&!d){return}a.cancel(k);if(k.target.nodeName=="IMG"){j.selection.select(k.target)}h._getMenu(j).showMenu(k.clientX||k.pageX,k.clientY||k.pageY);a.add(j.getDoc(),"click",function(l){g(j,l)});j.nodeChanged()});e.onRemove.add(function(){if(h._menu){h._menu.removeAll()}});function g(j,k){i=0;if(k&&k.button==2){i=k.ctrlKey;return}if(h._menu){h._menu.removeAll();h._menu.destroy();a.remove(j.getDoc(),"click",g)}}e.onMouseDown.add(g);e.onKeyDown.add(g);e.onKeyDown.add(function(j,k){if(k.shiftKey&&!k.ctrlKey&&!k.altKey&&k.keyCode===121){a.cancel(k);f(j,k)}})},getInfo:function(){return{longname:"Contextmenu",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_getMenu:function(e){var g=this,d=g._menu,j=e.selection,f=j.isCollapsed(),h=j.getNode()||e.getBody(),i,k;if(d){d.removeAll();d.destroy()}k=b.getPos(e.getContentAreaContainer());d=e.controlManager.createDropMenu("contextmenu",{offset_x:k.x+e.getParam("contextmenu_offset_x",0),offset_y:k.y+e.getParam("contextmenu_offset_y",0),constrain:1,keyboard_focus:true});g._menu=d;d.add({title:"advanced.cut_desc",icon:"cut",cmd:"Cut"}).setDisabled(f);d.add({title:"advanced.copy_desc",icon:"copy",cmd:"Copy"}).setDisabled(f);d.add({title:"advanced.paste_desc",icon:"paste",cmd:"Paste"});if((h.nodeName=="A"&&!e.dom.getAttrib(h,"name"))||!f){d.addSeparator();d.add({title:"advanced.link_desc",icon:"link",cmd:e.plugins.advlink?"mceAdvLink":"mceLink",ui:true});d.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"})}d.addSeparator();d.add({title:"advanced.image_desc",icon:"image",cmd:e.plugins.advimage?"mceAdvImage":"mceImage",ui:true});d.addSeparator();i=d.addMenu({title:"contextmenu.align"});i.add({title:"contextmenu.left",icon:"justifyleft",cmd:"JustifyLeft"});i.add({title:"contextmenu.center",icon:"justifycenter",cmd:"JustifyCenter"});i.add({title:"contextmenu.right",icon:"justifyright",cmd:"JustifyRight"});i.add({title:"contextmenu.full",icon:"justifyfull",cmd:"JustifyFull"});g.onContextMenu.dispatch(g,d,h,f);return d}});tinymce.PluginManager.add("contextmenu",tinymce.plugins.ContextMenu)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/contextmenu/editor_plugin_src.js b/js/tiny_mce/plugins/contextmenu/editor_plugin_src.js index 26e9ce2f..2a916a39 100644 --- a/js/tiny_mce/plugins/contextmenu/editor_plugin_src.js +++ b/js/tiny_mce/plugins/contextmenu/editor_plugin_src.js @@ -1,127 +1,160 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM; - - /** - * This plugin a context menu to TinyMCE editor instances. - * - * @class tinymce.plugins.ContextMenu - */ - tinymce.create('tinymce.plugins.ContextMenu', { - /** - * Initializes the plugin, this will be executed after the plugin has been created. - * This call is done before the editor instance has finished it's initialization so use the onInit event - * of the editor instance to intercept that event. - * - * @method init - * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. - * @param {string} url Absolute URL to where the plugin is located. - */ - init : function(ed) { - var t = this; - - t.editor = ed; - - /** - * This event gets fired when the context menu is shown. - * - * @event onContextMenu - * @param {tinymce.plugins.ContextMenu} sender Plugin instance sending the event. - * @param {tinymce.ui.DropMenu} menu Drop down menu to fill with more items if needed. - */ - t.onContextMenu = new tinymce.util.Dispatcher(this); - - ed.onContextMenu.add(function(ed, e) { - if (!e.ctrlKey) { - t._getMenu(ed).showMenu(e.clientX, e.clientY); - Event.add(ed.getDoc(), 'click', hide); - Event.cancel(e); - } - }); - - function hide() { - if (t._menu) { - t._menu.removeAll(); - t._menu.destroy(); - Event.remove(ed.getDoc(), 'click', hide); - } - }; - - ed.onMouseDown.add(hide); - ed.onKeyDown.add(hide); - }, - - /** - * Returns information about the plugin as a name/value array. - * The current keys are longname, author, authorurl, infourl and version. - * - * @method getInfo - * @return {Object} Name/value array containing information about the plugin. - */ - getInfo : function() { - return { - longname : 'Contextmenu', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - _getMenu : function(ed) { - var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p1, p2; - - if (m) { - m.removeAll(); - m.destroy(); - } - - p1 = DOM.getPos(ed.getContentAreaContainer()); - p2 = DOM.getPos(ed.getContainer()); - - m = ed.controlManager.createDropMenu('contextmenu', { - offset_x : p1.x + ed.getParam('contextmenu_offset_x', 0), - offset_y : p1.y + ed.getParam('contextmenu_offset_y', 0), - constrain : 1 - }); - - t._menu = m; - - m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col); - m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col); - m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'}); - - if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) { - m.addSeparator(); - m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); - m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); - } - - m.addSeparator(); - m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); - - m.addSeparator(); - am = m.addMenu({title : 'contextmenu.align'}); - am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'}); - am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'}); - am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'}); - am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'}); - - t.onContextMenu.dispatch(t, m, el, col); - - return m; - } - }); - - // Register plugin - tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu); -})(); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM; + + /** + * This plugin a context menu to TinyMCE editor instances. + * + * @class tinymce.plugins.ContextMenu + */ + tinymce.create('tinymce.plugins.ContextMenu', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @method init + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed) { + var t = this, showMenu, contextmenuNeverUseNative, realCtrlKey; + + t.editor = ed; + + contextmenuNeverUseNative = ed.settings.contextmenu_never_use_native; + + /** + * This event gets fired when the context menu is shown. + * + * @event onContextMenu + * @param {tinymce.plugins.ContextMenu} sender Plugin instance sending the event. + * @param {tinymce.ui.DropMenu} menu Drop down menu to fill with more items if needed. + */ + t.onContextMenu = new tinymce.util.Dispatcher(this); + + showMenu = ed.onContextMenu.add(function(ed, e) { + // Block TinyMCE menu on ctrlKey and work around Safari issue + if ((realCtrlKey !== 0 ? realCtrlKey : e.ctrlKey) && !contextmenuNeverUseNative) + return; + + Event.cancel(e); + + // Select the image if it's clicked. WebKit would other wise expand the selection + if (e.target.nodeName == 'IMG') + ed.selection.select(e.target); + + t._getMenu(ed).showMenu(e.clientX || e.pageX, e.clientY || e.pageY); + Event.add(ed.getDoc(), 'click', function(e) { + hide(ed, e); + }); + + ed.nodeChanged(); + }); + + ed.onRemove.add(function() { + if (t._menu) + t._menu.removeAll(); + }); + + function hide(ed, e) { + realCtrlKey = 0; + + // Since the contextmenu event moves + // the selection we need to store it away + if (e && e.button == 2) { + realCtrlKey = e.ctrlKey; + return; + } + + if (t._menu) { + t._menu.removeAll(); + t._menu.destroy(); + Event.remove(ed.getDoc(), 'click', hide); + } + }; + + ed.onMouseDown.add(hide); + ed.onKeyDown.add(hide); + ed.onKeyDown.add(function(ed, e) { + if (e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 121) { + Event.cancel(e); + showMenu(ed, e); + } + }); + }, + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @method getInfo + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Contextmenu', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _getMenu : function(ed) { + var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p; + + if (m) { + m.removeAll(); + m.destroy(); + } + + p = DOM.getPos(ed.getContentAreaContainer()); + + m = ed.controlManager.createDropMenu('contextmenu', { + offset_x : p.x + ed.getParam('contextmenu_offset_x', 0), + offset_y : p.y + ed.getParam('contextmenu_offset_y', 0), + constrain : 1, + keyboard_focus: true + }); + + t._menu = m; + + m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col); + m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col); + m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'}); + + if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) { + m.addSeparator(); + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + } + + m.addSeparator(); + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + + m.addSeparator(); + am = m.addMenu({title : 'contextmenu.align'}); + am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'}); + am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'}); + am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'}); + am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'}); + + t.onContextMenu.dispatch(t, m, el, col); + + return m; + } + }); + + // Register plugin + tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu); +})(); diff --git a/js/tiny_mce/plugins/emotions/emotions.htm b/js/tiny_mce/plugins/emotions/emotions.htm index 55a1d72f..7e73c3e0 100644 --- a/js/tiny_mce/plugins/emotions/emotions.htm +++ b/js/tiny_mce/plugins/emotions/emotions.htm @@ -1,40 +1,42 @@ - - - - {#emotions_dlg.title} - - - - -

    -
    {#emotions_dlg.title}:

    - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {#emotions_dlg.cool}{#emotions_dlg.cry}{#emotions_dlg.embarassed}{#emotions_dlg.foot_in_mouth}
    {#emotions_dlg.frown}{#emotions_dlg.innocent}{#emotions_dlg.kiss}{#emotions_dlg.laughing}
    {#emotions_dlg.money_mouth}{#emotions_dlg.sealed}{#emotions_dlg.smile}{#emotions_dlg.surprised}
    {#emotions_dlg.tongue-out}{#emotions_dlg.undecided}{#emotions_dlg.wink}{#emotions_dlg.yell}
    -
    - - + + + + {#emotions_dlg.title} + + + + + +
    +
    {#emotions_dlg.title}:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {#emotions_dlg.usage}
    +
    + + diff --git a/js/tiny_mce/plugins/emotions/img/smiley-foot-in-mouth.gif b/js/tiny_mce/plugins/emotions/img/smiley-foot-in-mouth.gif index 16f68cc1..c7cf1011 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-foot-in-mouth.gif and b/js/tiny_mce/plugins/emotions/img/smiley-foot-in-mouth.gif differ diff --git a/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif b/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif index 1606c119..82c5b182 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif and b/js/tiny_mce/plugins/emotions/img/smiley-laughing.gif differ diff --git a/js/tiny_mce/plugins/emotions/img/smiley-sealed.gif b/js/tiny_mce/plugins/emotions/img/smiley-sealed.gif index b33d3cca..fe66220c 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-sealed.gif and b/js/tiny_mce/plugins/emotions/img/smiley-sealed.gif differ diff --git a/js/tiny_mce/plugins/emotions/img/smiley-smile.gif b/js/tiny_mce/plugins/emotions/img/smiley-smile.gif index e6a9e60d..fd27edfa 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-smile.gif and b/js/tiny_mce/plugins/emotions/img/smiley-smile.gif differ diff --git a/js/tiny_mce/plugins/emotions/img/smiley-surprised.gif b/js/tiny_mce/plugins/emotions/img/smiley-surprised.gif index cb99cdd9..0cc9bb71 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-surprised.gif and b/js/tiny_mce/plugins/emotions/img/smiley-surprised.gif differ diff --git a/js/tiny_mce/plugins/emotions/img/smiley-wink.gif b/js/tiny_mce/plugins/emotions/img/smiley-wink.gif index 9faf1aff..0631c761 100644 Binary files a/js/tiny_mce/plugins/emotions/img/smiley-wink.gif and b/js/tiny_mce/plugins/emotions/img/smiley-wink.gif differ diff --git a/js/tiny_mce/plugins/emotions/js/emotions.js b/js/tiny_mce/plugins/emotions/js/emotions.js index c5493670..f73516c8 100644 --- a/js/tiny_mce/plugins/emotions/js/emotions.js +++ b/js/tiny_mce/plugins/emotions/js/emotions.js @@ -1,22 +1,43 @@ -tinyMCEPopup.requireLangPack(); - -var EmotionsDialog = { - init : function(ed) { - tinyMCEPopup.resizeToInnerSize(); - }, - - insert : function(file, title) { - var ed = tinyMCEPopup.editor, dom = ed.dom; - - tinyMCEPopup.execCommand('mceInsertContent', false, dom.createHTML('img', { - src : tinyMCEPopup.getWindowArg('plugin_url') + '/img/' + file, - alt : ed.getLang(title), - title : ed.getLang(title), - border : 0 - })); - - tinyMCEPopup.close(); - } -}; - -tinyMCEPopup.onInit.add(EmotionsDialog.init, EmotionsDialog); +tinyMCEPopup.requireLangPack(); + +var EmotionsDialog = { + addKeyboardNavigation: function(){ + var tableElm, cells, settings; + + cells = tinyMCEPopup.dom.select("a.emoticon_link", "emoticon_table"); + + settings ={ + root: "emoticon_table", + items: cells + }; + cells[0].tabindex=0; + tinyMCEPopup.dom.addClass(cells[0], "mceFocus"); + if (tinymce.isGecko) { + cells[0].focus(); + } else { + setTimeout(function(){ + cells[0].focus(); + }, 100); + } + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', settings, tinyMCEPopup.dom); + }, + init : function(ed) { + tinyMCEPopup.resizeToInnerSize(); + this.addKeyboardNavigation(); + }, + + insert : function(file, title) { + var ed = tinyMCEPopup.editor, dom = ed.dom; + + tinyMCEPopup.execCommand('mceInsertContent', false, dom.createHTML('img', { + src : tinyMCEPopup.getWindowArg('plugin_url') + '/img/' + file, + alt : ed.getLang(title), + title : ed.getLang(title), + border : 0 + })); + + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(EmotionsDialog.init, EmotionsDialog); diff --git a/js/tiny_mce/plugins/emotions/langs/en_dlg.js b/js/tiny_mce/plugins/emotions/langs/en_dlg.js index 3b57ad9e..037c4b58 100644 --- a/js/tiny_mce/plugins/emotions/langs/en_dlg.js +++ b/js/tiny_mce/plugins/emotions/langs/en_dlg.js @@ -1,20 +1 @@ -tinyMCE.addI18n('en.emotions_dlg',{ -title:"Insert emotion", -desc:"Emotions", -cool:"Cool", -cry:"Cry", -embarassed:"Embarassed", -foot_in_mouth:"Foot in mouth", -frown:"Frown", -innocent:"Innocent", -kiss:"Kiss", -laughing:"Laughing", -money_mouth:"Money mouth", -sealed:"Sealed", -smile:"Smile", -surprised:"Surprised", -tongue_out:"Tongue out", -undecided:"Undecided", -wink:"Wink", -yell:"Yell" -}); \ No newline at end of file +tinyMCE.addI18n('en.emotions_dlg',{cry:"Cry",cool:"Cool",desc:"Emotions",title:"Insert Emotion",usage:"Use left and right arrows to navigate.",yell:"Yell",wink:"Wink",undecided:"Undecided","tongue_out":"Tongue Out",surprised:"Surprised",smile:"Smile",sealed:"Sealed","money_mouth":"Money Mouth",laughing:"Laughing",kiss:"Kiss",innocent:"Innocent",frown:"Frown","foot_in_mouth":"Foot in Mouth",embarassed:"Embarassed"}); diff --git a/js/tiny_mce/plugins/example_dependency/editor_plugin.js b/js/tiny_mce/plugins/example_dependency/editor_plugin.js new file mode 100644 index 00000000..0a4551d3 --- /dev/null +++ b/js/tiny_mce/plugins/example_dependency/editor_plugin.js @@ -0,0 +1 @@ +(function(){tinymce.create("tinymce.plugins.ExampleDependencyPlugin",{init:function(a,b){},getInfo:function(){return{longname:"Example Dependency plugin",author:"Some author",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example_dependency",version:"1.0"}}});tinymce.PluginManager.add("example_dependency",tinymce.plugins.ExampleDependencyPlugin,["example"])})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/example_dependency/editor_plugin_src.js b/js/tiny_mce/plugins/example_dependency/editor_plugin_src.js new file mode 100644 index 00000000..e1c55e41 --- /dev/null +++ b/js/tiny_mce/plugins/example_dependency/editor_plugin_src.js @@ -0,0 +1,50 @@ +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + + tinymce.create('tinymce.plugins.ExampleDependencyPlugin', { + /** + * Initializes the plugin, this will be executed after the plugin has been created. + * This call is done before the editor instance has finished it's initialization so use the onInit event + * of the editor instance to intercept that event. + * + * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in. + * @param {string} url Absolute URL to where the plugin is located. + */ + init : function(ed, url) { + }, + + + /** + * Returns information about the plugin as a name/value array. + * The current keys are longname, author, authorurl, infourl and version. + * + * @return {Object} Name/value array containing information about the plugin. + */ + getInfo : function() { + return { + longname : 'Example Dependency plugin', + author : 'Some author', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example_dependency', + version : "1.0" + }; + } + }); + + /** + * Register the plugin, specifying the list of the plugins that this plugin depends on. They are specified in a list, with the list loaded in order. + * plugins in this list will be initialised when this plugin is initialized. (before the init method is called). + * plugins in a depends list should typically be specified using the short name). If neccesary this can be done + * with an object which has the url to the plugin and the shortname. + */ + tinymce.PluginManager.add('example_dependency', tinymce.plugins.ExampleDependencyPlugin, ['example']); +})(); diff --git a/js/tiny_mce/plugins/fullpage/css/fullpage.css b/js/tiny_mce/plugins/fullpage/css/fullpage.css index 7a3334f0..28b721f9 100644 --- a/js/tiny_mce/plugins/fullpage/css/fullpage.css +++ b/js/tiny_mce/plugins/fullpage/css/fullpage.css @@ -1,182 +1,143 @@ -/* Hide the advanced tab */ -#advanced_tab { - display: none; -} - -#metatitle, #metakeywords, #metadescription, #metaauthor, #metacopyright { - width: 280px; -} - -#doctype, #docencoding { - width: 200px; -} - -#langcode { - width: 30px; -} - -#bgimage { - width: 220px; -} - -#fontface { - width: 240px; -} - -#leftmargin, #rightmargin, #topmargin, #bottommargin { - width: 50px; -} - -.panel_wrapper div.current { - height: 400px; -} - -#stylesheet, #style { - width: 240px; -} - -/* Head list classes */ - -.headlistwrapper { - width: 100%; -} - -.addbutton, .removebutton, .moveupbutton, .movedownbutton { - border-top: 1px solid; - border-left: 1px solid; - border-bottom: 1px solid; - border-right: 1px solid; - border-color: #F0F0EE; - cursor: default; - display: block; - width: 20px; - height: 20px; -} - -#doctypes { - width: 200px; -} - -.addbutton:hover, .removebutton:hover, .moveupbutton:hover, .movedownbutton:hover { - border: 1px solid #0A246A; - background-color: #B6BDD2; -} - -.addbutton { - background-image: url('../images/add.gif'); - float: left; - margin-right: 3px; -} - -.removebutton { - background-image: url('../images/remove.gif'); - float: left; -} - -.moveupbutton { - background-image: url('../images/move_up.gif'); - float: left; - margin-right: 3px; -} - -.movedownbutton { - background-image: url('../images/move_down.gif'); - float: left; -} - -.selected { - border: 1px solid #0A246A; - background-color: #B6BDD2; -} - -.toolbar { - width: 100%; -} - -#headlist { - width: 100%; - margin-top: 3px; - font-size: 11px; -} - -#info, #title_element, #meta_element, #script_element, #style_element, #base_element, #link_element, #comment_element, #unknown_element { - display: none; -} - -#addmenu { - position: absolute; - border: 1px solid gray; - display: none; - z-index: 100; - background-color: white; -} - -#addmenu a { - display: block; - width: 100%; - line-height: 20px; - text-decoration: none; - background-color: white; -} - -#addmenu a:hover { - background-color: #B6BDD2; - color: black; -} - -#addmenu span { - padding-left: 10px; - padding-right: 10px; -} - -#updateElementPanel { - display: none; -} - -#script_element .panel_wrapper div.current { - height: 108px; -} - -#style_element .panel_wrapper div.current { - height: 108px; -} - -#link_element .panel_wrapper div.current { - height: 140px; -} - -#element_script_value { - width: 100%; - height: 100px; -} - -#element_comment_value { - width: 100%; - height: 120px; -} - -#element_style_value { - width: 100%; - height: 100px; -} - -#element_title, #element_script_src, #element_meta_name, #element_meta_content, #element_base_href, #element_link_href, #element_link_title { - width: 250px; -} - -.updateElementButton { - margin-top: 3px; -} - -/* MSIE specific styles */ - -* html .addbutton, * html .removebutton, * html .moveupbutton, * html .movedownbutton { - width: 22px; - height: 22px; -} - -textarea { - height: 55px; -} - +/* Hide the advanced tab */ +#advanced_tab { + display: none; +} + +#metatitle, #metakeywords, #metadescription, #metaauthor, #metacopyright { + width: 280px; +} + +#doctype, #docencoding { + width: 200px; +} + +#langcode { + width: 30px; +} + +#bgimage { + width: 220px; +} + +#fontface { + width: 240px; +} + +#leftmargin, #rightmargin, #topmargin, #bottommargin { + width: 50px; +} + +.panel_wrapper div.current { + height: 400px; +} + +#stylesheet, #style { + width: 240px; +} + +#doctypes { + width: 200px; +} + +/* Head list classes */ + +.headlistwrapper { + width: 100%; +} + +.selected { + border: 1px solid #0A246A; + background-color: #B6BDD2; +} + +.toolbar { + width: 100%; +} + +#headlist { + width: 100%; + margin-top: 3px; + font-size: 11px; +} + +#info, #title_element, #meta_element, #script_element, #style_element, #base_element, #link_element, #comment_element, #unknown_element { + display: none; +} + +#addmenu { + position: absolute; + border: 1px solid gray; + display: none; + z-index: 100; + background-color: white; +} + +#addmenu a { + display: block; + width: 100%; + line-height: 20px; + text-decoration: none; + background-color: white; +} + +#addmenu a:hover { + background-color: #B6BDD2; + color: black; +} + +#addmenu span { + padding-left: 10px; + padding-right: 10px; +} + +#updateElementPanel { + display: none; +} + +#script_element .panel_wrapper div.current { + height: 108px; +} + +#style_element .panel_wrapper div.current { + height: 108px; +} + +#link_element .panel_wrapper div.current { + height: 140px; +} + +#element_script_value { + width: 100%; + height: 100px; +} + +#element_comment_value { + width: 100%; + height: 120px; +} + +#element_style_value { + width: 100%; + height: 100px; +} + +#element_title, #element_script_src, #element_meta_name, #element_meta_content, #element_base_href, #element_link_href, #element_link_title { + width: 250px; +} + +.updateElementButton { + margin-top: 3px; +} + +/* MSIE specific styles */ + +* html .addbutton, * html .removebutton, * html .moveupbutton, * html .movedownbutton { + width: 22px; + height: 22px; +} + +textarea { + height: 55px; +} + .panel_wrapper div.current {height:420px;} \ No newline at end of file diff --git a/js/tiny_mce/plugins/fullpage/editor_plugin.js b/js/tiny_mce/plugins/fullpage/editor_plugin.js index 8e11bfc4..dcf76024 100644 --- a/js/tiny_mce/plugins/fullpage/editor_plugin.js +++ b/js/tiny_mce/plugins/fullpage/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.FullPagePlugin",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceFullPageProperties",function(){a.windowManager.open({file:b+"/fullpage.htm",width:430+parseInt(a.getLang("fullpage.delta_width",0)),height:495+parseInt(a.getLang("fullpage.delta_height",0)),inline:1},{plugin_url:b,head_html:c.head})});a.addButton("fullpage",{title:"fullpage.desc",cmd:"mceFullPageProperties"});a.onBeforeSetContent.add(c._setContent,c);a.onSetContent.add(c._setBodyAttribs,c);a.onGetContent.add(c._getContent,c)},getInfo:function(){return{longname:"Fullpage",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_setBodyAttribs:function(d,a){var l,c,e,g,b,h,j,f=this.head.match(/body(.*?)>/i);if(f&&f[1]){l=f[1].match(/\s*(\w+\s*=\s*".*?"|\w+\s*=\s*'.*?'|\w+\s*=\s*\w+|\w+)\s*/g);if(l){for(c=0,e=l.length;c",a);h.head=f.substring(0,a+1);j=f.indexOf("\n'}h.head+=d.getParam("fullpage_default_doctype",'');h.head+="\n\n\n"+d.getParam("fullpage_default_title","Untitled document")+"\n";if(g=d.getParam("fullpage_default_encoding")){h.head+='\n'}if(g=d.getParam("fullpage_default_font_family")){i+="font-family: "+g+";"}if(g=d.getParam("fullpage_default_font_size")){i+="font-size: "+g+";"}if(g=d.getParam("fullpage_default_text_color")){i+="color: "+g+";"}h.head+="\n\n";h.foot="\n\n"}},_getContent:function(a,c){var b=this;if(!c.source_view||!a.getParam("fullpage_hide_in_source_view")){c.content=tinymce.trim(b.head)+"\n"+tinymce.trim(c.content)+"\n"+tinymce.trim(b.foot)}}});tinymce.PluginManager.add("fullpage",tinymce.plugins.FullPagePlugin)})(); \ No newline at end of file +(function(){var b=tinymce.each,a=tinymce.html.Node;tinymce.create("tinymce.plugins.FullPagePlugin",{init:function(c,d){var e=this;e.editor=c;c.addCommand("mceFullPageProperties",function(){c.windowManager.open({file:d+"/fullpage.htm",width:430+parseInt(c.getLang("fullpage.delta_width",0)),height:495+parseInt(c.getLang("fullpage.delta_height",0)),inline:1},{plugin_url:d,data:e._htmlToData()})});c.addButton("fullpage",{title:"fullpage.desc",cmd:"mceFullPageProperties"});c.onBeforeSetContent.add(e._setContent,e);c.onGetContent.add(e._getContent,e)},getInfo:function(){return{longname:"Fullpage",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_htmlToData:function(){var f=this._parseHeader(),h={},c,i,g,e=this.editor;function d(l,j){var k=l.attr(j);return k||""}h.fontface=e.getParam("fullpage_default_fontface","");h.fontsize=e.getParam("fullpage_default_fontsize","");i=f.firstChild;if(i.type==7){h.xml_pi=true;g=/encoding="([^"]+)"/.exec(i.value);if(g){h.docencoding=g[1]}}i=f.getAll("#doctype")[0];if(i){h.doctype=""}i=f.getAll("title")[0];if(i&&i.firstChild){h.metatitle=i.firstChild.value}b(f.getAll("meta"),function(m){var k=m.attr("name"),j=m.attr("http-equiv"),l;if(k){h["meta"+k.toLowerCase()]=m.attr("content")}else{if(j=="Content-Type"){l=/charset\s*=\s*(.*)\s*/gi.exec(m.attr("content"));if(l){h.docencoding=l[1]}}}});i=f.getAll("html")[0];if(i){h.langcode=d(i,"lang")||d(i,"xml:lang")}i=f.getAll("link")[0];if(i&&i.attr("rel")=="stylesheet"){h.stylesheet=i.attr("href")}i=f.getAll("body")[0];if(i){h.langdir=d(i,"dir");h.style=d(i,"style");h.visited_color=d(i,"vlink");h.link_color=d(i,"link");h.active_color=d(i,"alink")}return h},_dataToHtml:function(g){var f,d,h,j,k,e=this.editor.dom;function c(n,l,m){n.attr(l,m?m:undefined)}function i(l){if(d.firstChild){d.insert(l,d.firstChild)}else{d.append(l)}}f=this._parseHeader();d=f.getAll("head")[0];if(!d){j=f.getAll("html")[0];d=new a("head",1);if(j.firstChild){j.insert(d,j.firstChild,true)}else{j.append(d)}}j=f.firstChild;if(g.xml_pi){k='version="1.0"';if(g.docencoding){k+=' encoding="'+g.docencoding+'"'}if(j.type!=7){j=new a("xml",7);f.insert(j,f.firstChild,true)}j.value=k}else{if(j&&j.type==7){j.remove()}}j=f.getAll("#doctype")[0];if(g.doctype){if(!j){j=new a("#doctype",10);if(g.xml_pi){f.insert(j,f.firstChild)}else{i(j)}}j.value=g.doctype.substring(9,g.doctype.length-1)}else{if(j){j.remove()}}j=f.getAll("title")[0];if(g.metatitle){if(!j){j=new a("title",1);j.append(new a("#text",3)).value=g.metatitle;i(j)}}if(g.docencoding){j=null;b(f.getAll("meta"),function(l){if(l.attr("http-equiv")=="Content-Type"){j=l}});if(!j){j=new a("meta",1);j.attr("http-equiv","Content-Type");j.shortEnded=true;i(j)}j.attr("content","text/html; charset="+g.docencoding)}b("keywords,description,author,copyright,robots".split(","),function(m){var l=f.getAll("meta"),n,p,o=g["meta"+m];for(n=0;n"))},_parseHeader:function(){return new tinymce.html.DomParser({validate:false,root_name:"#document"}).parse(this.head)},_setContent:function(g,d){var m=this,i,c,h=d.content,f,l="",e=m.editor.dom,j;function k(n){return n.replace(/<\/?[A-Z]+/g,function(o){return o.toLowerCase()})}if(d.format=="raw"&&m.head){return}if(d.source_view&&g.getParam("fullpage_hide_in_source_view")){return}h=h.replace(/<(\/?)BODY/gi,"<$1body");i=h.indexOf("",i);m.head=k(h.substring(0,i+1));c=h.indexOf("\n"}f=m._parseHeader();b(f.getAll("style"),function(n){if(n.firstChild){l+=n.firstChild.value}});j=f.getAll("body")[0];if(j){e.setAttribs(m.editor.getBody(),{style:j.attr("style")||"",dir:j.attr("dir")||"",vLink:j.attr("vlink")||"",link:j.attr("link")||"",aLink:j.attr("alink")||""})}e.remove("fullpage_styles");if(l){e.add(m.editor.getDoc().getElementsByTagName("head")[0],"style",{id:"fullpage_styles"},l);j=e.get("fullpage_styles");if(j.styleSheet){j.styleSheet.cssText=l}}},_getDefaultHeader:function(){var f="",c=this.editor,e,d="";if(c.getParam("fullpage_default_xml_pi")){f+='\n'}f+=c.getParam("fullpage_default_doctype",'');f+="\n\n\n";if(e=c.getParam("fullpage_default_title")){f+=""+e+"\n"}if(e=c.getParam("fullpage_default_encoding")){f+='\n'}if(e=c.getParam("fullpage_default_font_family")){d+="font-family: "+e+";"}if(e=c.getParam("fullpage_default_font_size")){d+="font-size: "+e+";"}if(e=c.getParam("fullpage_default_text_color")){d+="color: "+e+";"}f+="\n\n";return f},_getContent:function(d,e){var c=this;if(!e.source_view||!d.getParam("fullpage_hide_in_source_view")){e.content=tinymce.trim(c.head)+"\n"+tinymce.trim(e.content)+"\n"+tinymce.trim(c.foot)}}});tinymce.PluginManager.add("fullpage",tinymce.plugins.FullPagePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/fullpage/editor_plugin_src.js b/js/tiny_mce/plugins/fullpage/editor_plugin_src.js index b7d51d58..8b49c446 100644 --- a/js/tiny_mce/plugins/fullpage/editor_plugin_src.js +++ b/js/tiny_mce/plugins/fullpage/editor_plugin_src.js @@ -1,149 +1,405 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.FullPagePlugin', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - - // Register commands - ed.addCommand('mceFullPageProperties', function() { - ed.windowManager.open({ - file : url + '/fullpage.htm', - width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)), - height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)), - inline : 1 - }, { - plugin_url : url, - head_html : t.head - }); - }); - - // Register buttons - ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'}); - - ed.onBeforeSetContent.add(t._setContent, t); - ed.onSetContent.add(t._setBodyAttribs, t); - ed.onGetContent.add(t._getContent, t); - }, - - getInfo : function() { - return { - longname : 'Fullpage', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - // Private plugin internal methods - - _setBodyAttribs : function(ed, o) { - var bdattr, i, len, kv, k, v, t, attr = this.head.match(/body(.*?)>/i); - - if (attr && attr[1]) { - bdattr = attr[1].match(/\s*(\w+\s*=\s*".*?"|\w+\s*=\s*'.*?'|\w+\s*=\s*\w+|\w+)\s*/g); - - if (bdattr) { - for(i = 0, len = bdattr.length; i < len; i++) { - kv = bdattr[i].split('='); - k = kv[0].replace(/\s/,''); - v = kv[1]; - - if (v) { - v = v.replace(/^\s+/,'').replace(/\s+$/,''); - t = v.match(/^["'](.*)["']$/); - - if (t) - v = t[1]; - } else - v = k; - - ed.dom.setAttrib(ed.getBody(), 'style', v); - } - } - } - }, - - _createSerializer : function() { - return new tinymce.dom.Serializer({ - dom : this.editor.dom, - apply_source_formatting : true - }); - }, - - _setContent : function(ed, o) { - var t = this, sp, ep, c = o.content, v, st = ''; - - if (o.source_view && ed.getParam('fullpage_hide_in_source_view')) - return; - - // Parse out head, body and footer - c = c.replace(/<(\/?)BODY/gi, '<$1body'); - sp = c.indexOf('', sp); - t.head = c.substring(0, sp + 1); - - ep = c.indexOf('\n'; - - t.head += ed.getParam('fullpage_default_doctype', ''); - t.head += '\n\n\n' + ed.getParam('fullpage_default_title', 'Untitled document') + '\n'; - - if (v = ed.getParam('fullpage_default_encoding')) - t.head += '\n'; - - if (v = ed.getParam('fullpage_default_font_family')) - st += 'font-family: ' + v + ';'; - - if (v = ed.getParam('fullpage_default_font_size')) - st += 'font-size: ' + v + ';'; - - if (v = ed.getParam('fullpage_default_text_color')) - st += 'color: ' + v + ';'; - - t.head += '\n\n'; - t.foot = '\n\n'; - } - }, - - _getContent : function(ed, o) { - var t = this; - - if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view')) - o.content = tinymce.trim(t.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(t.foot); - } - }); - - // Register plugin - tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin); -})(); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, Node = tinymce.html.Node; + + tinymce.create('tinymce.plugins.FullPagePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceFullPageProperties', function() { + ed.windowManager.open({ + file : url + '/fullpage.htm', + width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)), + height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + data : t._htmlToData() + }); + }); + + // Register buttons + ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'}); + + ed.onBeforeSetContent.add(t._setContent, t); + ed.onGetContent.add(t._getContent, t); + }, + + getInfo : function() { + return { + longname : 'Fullpage', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private plugin internal methods + + _htmlToData : function() { + var headerFragment = this._parseHeader(), data = {}, nodes, elm, matches, editor = this.editor; + + function getAttr(elm, name) { + var value = elm.attr(name); + + return value || ''; + }; + + // Default some values + data.fontface = editor.getParam("fullpage_default_fontface", ""); + data.fontsize = editor.getParam("fullpage_default_fontsize", ""); + + // Parse XML PI + elm = headerFragment.firstChild; + if (elm.type == 7) { + data.xml_pi = true; + matches = /encoding="([^"]+)"/.exec(elm.value); + if (matches) + data.docencoding = matches[1]; + } + + // Parse doctype + elm = headerFragment.getAll('#doctype')[0]; + if (elm) + data.doctype = '"; + + // Parse title element + elm = headerFragment.getAll('title')[0]; + if (elm && elm.firstChild) { + data.metatitle = elm.firstChild.value; + } + + // Parse meta elements + each(headerFragment.getAll('meta'), function(meta) { + var name = meta.attr('name'), httpEquiv = meta.attr('http-equiv'), matches; + + if (name) + data['meta' + name.toLowerCase()] = meta.attr('content'); + else if (httpEquiv == "Content-Type") { + matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content')); + + if (matches) + data.docencoding = matches[1]; + } + }); + + // Parse html attribs + elm = headerFragment.getAll('html')[0]; + if (elm) + data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang'); + + // Parse stylesheet + elm = headerFragment.getAll('link')[0]; + if (elm && elm.attr('rel') == 'stylesheet') + data.stylesheet = elm.attr('href'); + + // Parse body parts + elm = headerFragment.getAll('body')[0]; + if (elm) { + data.langdir = getAttr(elm, 'dir'); + data.style = getAttr(elm, 'style'); + data.visited_color = getAttr(elm, 'vlink'); + data.link_color = getAttr(elm, 'link'); + data.active_color = getAttr(elm, 'alink'); + } + + return data; + }, + + _dataToHtml : function(data) { + var headerFragment, headElement, html, elm, value, dom = this.editor.dom; + + function setAttr(elm, name, value) { + elm.attr(name, value ? value : undefined); + }; + + function addHeadNode(node) { + if (headElement.firstChild) + headElement.insert(node, headElement.firstChild); + else + headElement.append(node); + }; + + headerFragment = this._parseHeader(); + headElement = headerFragment.getAll('head')[0]; + if (!headElement) { + elm = headerFragment.getAll('html')[0]; + headElement = new Node('head', 1); + + if (elm.firstChild) + elm.insert(headElement, elm.firstChild, true); + else + elm.append(headElement); + } + + // Add/update/remove XML-PI + elm = headerFragment.firstChild; + if (data.xml_pi) { + value = 'version="1.0"'; + + if (data.docencoding) + value += ' encoding="' + data.docencoding + '"'; + + if (elm.type != 7) { + elm = new Node('xml', 7); + headerFragment.insert(elm, headerFragment.firstChild, true); + } + + elm.value = value; + } else if (elm && elm.type == 7) + elm.remove(); + + // Add/update/remove doctype + elm = headerFragment.getAll('#doctype')[0]; + if (data.doctype) { + if (!elm) { + elm = new Node('#doctype', 10); + + if (data.xml_pi) + headerFragment.insert(elm, headerFragment.firstChild); + else + addHeadNode(elm); + } + + elm.value = data.doctype.substring(9, data.doctype.length - 1); + } else if (elm) + elm.remove(); + + // Add/update/remove title + elm = headerFragment.getAll('title')[0]; + if (data.metatitle) { + if (!elm) { + elm = new Node('title', 1); + elm.append(new Node('#text', 3)).value = data.metatitle; + addHeadNode(elm); + } + } + + // Add meta encoding + if (data.docencoding) { + elm = null; + each(headerFragment.getAll('meta'), function(meta) { + if (meta.attr('http-equiv') == 'Content-Type') + elm = meta; + }); + + if (!elm) { + elm = new Node('meta', 1); + elm.attr('http-equiv', 'Content-Type'); + elm.shortEnded = true; + addHeadNode(elm); + } + + elm.attr('content', 'text/html; charset=' + data.docencoding); + } + + // Add/update/remove meta + each('keywords,description,author,copyright,robots'.split(','), function(name) { + var nodes = headerFragment.getAll('meta'), i, meta, value = data['meta' + name]; + + for (i = 0; i < nodes.length; i++) { + meta = nodes[i]; + + if (meta.attr('name') == name) { + if (value) + meta.attr('content', value); + else + meta.remove(); + + return; + } + } + + if (value) { + elm = new Node('meta', 1); + elm.attr('name', name); + elm.attr('content', value); + elm.shortEnded = true; + + addHeadNode(elm); + } + }); + + // Add/update/delete link + elm = headerFragment.getAll('link')[0]; + if (elm && elm.attr('rel') == 'stylesheet') { + if (data.stylesheet) + elm.attr('href', data.stylesheet); + else + elm.remove(); + } else if (data.stylesheet) { + elm = new Node('link', 1); + elm.attr({ + rel : 'stylesheet', + text : 'text/css', + href : data.stylesheet + }); + elm.shortEnded = true; + + addHeadNode(elm); + } + + // Update body attributes + elm = headerFragment.getAll('body')[0]; + if (elm) { + setAttr(elm, 'dir', data.langdir); + setAttr(elm, 'style', data.style); + setAttr(elm, 'vlink', data.visited_color); + setAttr(elm, 'link', data.link_color); + setAttr(elm, 'alink', data.active_color); + + // Update iframe body as well + dom.setAttribs(this.editor.getBody(), { + style : data.style, + dir : data.dir, + vLink : data.visited_color, + link : data.link_color, + aLink : data.active_color + }); + } + + // Set html attributes + elm = headerFragment.getAll('html')[0]; + if (elm) { + setAttr(elm, 'lang', data.langcode); + setAttr(elm, 'xml:lang', data.langcode); + } + + // Serialize header fragment and crop away body part + html = new tinymce.html.Serializer({ + validate: false, + indent: true, + apply_source_formatting : true, + indent_before: 'head,html,body,meta,title,script,link,style', + indent_after: 'head,html,body,meta,title,script,link,style' + }).serialize(headerFragment); + + this.head = html.substring(0, html.indexOf('')); + }, + + _parseHeader : function() { + // Parse the contents with a DOM parser + return new tinymce.html.DomParser({ + validate: false, + root_name: '#document' + }).parse(this.head); + }, + + _setContent : function(ed, o) { + var self = this, startPos, endPos, content = o.content, headerFragment, styles = '', dom = self.editor.dom, elm; + + function low(s) { + return s.replace(/<\/?[A-Z]+/g, function(a) { + return a.toLowerCase(); + }) + }; + + // Ignore raw updated if we already have a head, this will fix issues with undo/redo keeping the head/foot separate + if (o.format == 'raw' && self.head) + return; + + if (o.source_view && ed.getParam('fullpage_hide_in_source_view')) + return; + + // Parse out head, body and footer + content = content.replace(/<(\/?)BODY/gi, '<$1body'); + startPos = content.indexOf('', startPos); + self.head = low(content.substring(0, startPos + 1)); + + endPos = content.indexOf('\n'; + + header += editor.getParam('fullpage_default_doctype', ''); + header += '\n\n\n'; + + if (value = editor.getParam('fullpage_default_title')) + header += '' + value + '\n'; + + if (value = editor.getParam('fullpage_default_encoding')) + header += '\n'; + + if (value = editor.getParam('fullpage_default_font_family')) + styles += 'font-family: ' + value + ';'; + + if (value = editor.getParam('fullpage_default_font_size')) + styles += 'font-size: ' + value + ';'; + + if (value = editor.getParam('fullpage_default_text_color')) + styles += 'color: ' + value + ';'; + + header += '\n\n'; + + return header; + }, + + _getContent : function(ed, o) { + var self = this; + + if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view')) + o.content = tinymce.trim(self.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(self.foot); + } + }); + + // Register plugin + tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin); +})(); diff --git a/js/tiny_mce/plugins/fullpage/fullpage.htm b/js/tiny_mce/plugins/fullpage/fullpage.htm index c32afaf2..200f2b8e 100644 --- a/js/tiny_mce/plugins/fullpage/fullpage.htm +++ b/js/tiny_mce/plugins/fullpage/fullpage.htm @@ -1,571 +1,259 @@ - - - - {#fullpage_dlg.title} - - - - - - - -
    - - -
    -
    -
    - {#fullpage_dlg.meta_props} - - - - - - - - - - - - - - - - - - - - - - - - - - -
     
     
     
     
     
      - -
    -
    - -
    - {#fullpage_dlg.langprops} - - - - - - - - - - - - - - - - - - - - - - -
    - -
      - -
     
    - -
     
    -
    -
    - -
    -
    - {#fullpage_dlg.appearance_textprops} - - - - - - - - - - - - - - - - -
    - -
    - -
    - - - - - -
     
    -
    -
    - -
    - {#fullpage_dlg.appearance_bgprops} - - - - - - - - - - -
    - - - - - -
     
    -
    - - - - - -
     
    -
    -
    - -
    - {#fullpage_dlg.appearance_marginprops} - - - - - - - - - - - - - - -
    -
    - -
    - {#fullpage_dlg.appearance_linkprops} - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    -
    - - - - - -
     
    -
    - - - - - -
     
    -
      
    -
    - -
    - {#fullpage_dlg.appearance_style} - - - - - - - - - - -
    - - - - -
     
    -
    -
    - -
    - - -
    - {#fullpage_dlg.head_elements} - -
    -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    - -
    - {#fullpage_dlg.meta_element} - - - - - - - - - - - - - - -
    - - -
    - -
    - {#fullpage_dlg.title_element} - - - - - - -
    - - -
    - -
    - {#fullpage_dlg.script_element} - - - -
    - -
    -
    - - - - - - - - - - - - - - - - - -
    - - - - -
     
    -
    - -
    - -
    -
    - - -
    - -
    - {#fullpage_dlg.style_element} - - - -
    - -
    -
    - - - - - - - - - -
    -
    - -
    - -
    -
    - - -
    - -
    - {#fullpage_dlg.base_element} - - - - - - - - - - -
    - - -
    - - - -
    - {#fullpage_dlg.comment_element} - - - - -
    -
    -
    - -
    - - -
    -
    - - + + + + {#fullpage_dlg.title} + + + + + + + +
    + + +
    +
    +
    + {#fullpage_dlg.meta_props} + + + + + + + + + + + + + + + + + + + + + + + + + + +
     
     
     
     
     
      + +
    +
    + +
    + {#fullpage_dlg.langprops} + + + + + + + + + + + + + + + + + + + + + + +
    + +
      + +
     
    + +
     
    +
    +
    + +
    +
    + {#fullpage_dlg.appearance_textprops} + + + + + + + + + + + + + + + + +
    + +
    + +
    + + + + + +
     
    +
    +
    + +
    + {#fullpage_dlg.appearance_bgprops} + + + + + + + + + + +
    + + + + + +
     
    +
    + + + + + +
     
    +
    +
    + +
    + {#fullpage_dlg.appearance_marginprops} + + + + + + + + + + + + + + +
    +
    + +
    + {#fullpage_dlg.appearance_linkprops} + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
     
    +
    + + + + + +
     
    +
      
    +
    + +
    + {#fullpage_dlg.appearance_style} + + + + + + + + + + +
    + + + + +
     
    +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/fullpage/js/fullpage.js b/js/tiny_mce/plugins/fullpage/js/fullpage.js index a1bb719a..66eec2d7 100644 --- a/js/tiny_mce/plugins/fullpage/js/fullpage.js +++ b/js/tiny_mce/plugins/fullpage/js/fullpage.js @@ -1,471 +1,232 @@ -/** - * fullpage.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -tinyMCEPopup.requireLangPack(); - -var doc; - -var defaultDocTypes = - 'XHTML 1.0 Transitional=,' + - 'XHTML 1.0 Frameset=,' + - 'XHTML 1.0 Strict=,' + - 'XHTML 1.1=,' + - 'HTML 4.01 Transitional=,' + - 'HTML 4.01 Strict=,' + - 'HTML 4.01 Frameset='; - -var defaultEncodings = - 'Western european (iso-8859-1)=iso-8859-1,' + - 'Central European (iso-8859-2)=iso-8859-2,' + - 'Unicode (UTF-8)=utf-8,' + - 'Chinese traditional (Big5)=big5,' + - 'Cyrillic (iso-8859-5)=iso-8859-5,' + - 'Japanese (iso-2022-jp)=iso-2022-jp,' + - 'Greek (iso-8859-7)=iso-8859-7,' + - 'Korean (iso-2022-kr)=iso-2022-kr,' + - 'ASCII (us-ascii)=us-ascii'; - -var defaultMediaTypes = - 'all=all,' + - 'screen=screen,' + - 'print=print,' + - 'tty=tty,' + - 'tv=tv,' + - 'projection=projection,' + - 'handheld=handheld,' + - 'braille=braille,' + - 'aural=aural'; - -var defaultFontNames = 'Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,times new roman,times,serif;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times,serif;Verdana=verdana,arial,helvetica,sans-serif;Impact=impact;WingDings=wingdings'; -var defaultFontSizes = '10px,11px,12px,13px,14px,15px,16px'; - -function init() { - var f = document.forms['fullpage'], el = f.elements, e, i, p, doctypes, encodings, mediaTypes, fonts, ed = tinyMCEPopup.editor, dom = tinyMCEPopup.dom, style; - - // Setup doctype select box - doctypes = ed.getParam("fullpage_doctypes", defaultDocTypes).split(','); - for (i=0; i 1) - addSelectValue(f, 'doctypes', p[0], p[1]); - } - - // Setup fonts select box - fonts = ed.getParam("fullpage_fonts", defaultFontNames).split(';'); - for (i=0; i 1) - addSelectValue(f, 'fontface', p[0], p[1]); - } - - // Setup fontsize select box - fonts = ed.getParam("fullpage_fontsizes", defaultFontSizes).split(','); - for (i=0; i 1) { - addSelectValue(f, 'element_style_media', p[0], p[1]); - addSelectValue(f, 'element_link_media', p[0], p[1]); - } - } - - // Setup encodings select box - encodings = ed.getParam("fullpage_encodings", defaultEncodings).split(','); - for (i=0; i 1) { - addSelectValue(f, 'docencoding', p[0], p[1]); - addSelectValue(f, 'element_script_charset', p[0], p[1]); - addSelectValue(f, 'element_link_charset', p[0], p[1]); - } - } - - document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); - document.getElementById('link_color_pickcontainer').innerHTML = getColorPickerHTML('link_color_pick','link_color'); - //document.getElementById('hover_color_pickcontainer').innerHTML = getColorPickerHTML('hover_color_pick','hover_color'); - document.getElementById('visited_color_pickcontainer').innerHTML = getColorPickerHTML('visited_color_pick','visited_color'); - document.getElementById('active_color_pickcontainer').innerHTML = getColorPickerHTML('active_color_pick','active_color'); - document.getElementById('textcolor_pickcontainer').innerHTML = getColorPickerHTML('textcolor_pick','textcolor'); - document.getElementById('stylesheet_browsercontainer').innerHTML = getBrowserHTML('stylesheetbrowser','stylesheet','file','fullpage'); - document.getElementById('link_href_pickcontainer').innerHTML = getBrowserHTML('link_href_browser','element_link_href','file','fullpage'); - document.getElementById('script_src_pickcontainer').innerHTML = getBrowserHTML('script_src_browser','element_script_src','file','fullpage'); - document.getElementById('bgimage_pickcontainer').innerHTML = getBrowserHTML('bgimage_browser','bgimage','image','fullpage'); - - // Resize some elements - if (isVisible('stylesheetbrowser')) - document.getElementById('stylesheet').style.width = '220px'; - - if (isVisible('link_href_browser')) - document.getElementById('element_link_href').style.width = '230px'; - - if (isVisible('bgimage_browser')) - document.getElementById('bgimage').style.width = '210px'; - - // Add iframe - dom.add(document.body, 'iframe', {id : 'documentIframe', src : 'javascript:""', style : {display : 'none'}}); - doc = dom.get('documentIframe').contentWindow.document; - h = tinyMCEPopup.getWindowArg('head_html'); - - // Preprocess the HTML disable scripts and urls - h = h.replace(/ - - - -
    - -
    - - - - - + + + + + + + + + +
    + +
    + + + + + diff --git a/js/tiny_mce/plugins/inlinepopups/editor_plugin.js b/js/tiny_mce/plugins/inlinepopups/editor_plugin.js index 07ea477b..8bb96f9c 100644 --- a/js/tiny_mce/plugins/inlinepopups/editor_plugin.js +++ b/js/tiny_mce/plugins/inlinepopups/editor_plugin.js @@ -1 +1 @@ -(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.create("tinymce.InlineWindowManager:tinymce.WindowManager",{InlineWindowManager:function(f){var g=this;g.parent(f);g.zIndex=300000;g.count=0;g.windows={}},open:function(r,j){var y=this,i,k="",q=y.editor,g=0,s=0,h,m,n,o,l,v,x;r=r||{};j=j||{};if(!r.inline){return y.parent(r,j)}if(!r.type){y.bookmark=q.selection.getBookmark(1)}i=d.uniqueId();h=d.getViewPort();r.width=parseInt(r.width||320);r.height=parseInt(r.height||240)+(tinymce.isIE?8:0);r.min_width=parseInt(r.min_width||150);r.min_height=parseInt(r.min_height||100);r.max_width=parseInt(r.max_width||2000);r.max_height=parseInt(r.max_height||2000);r.left=r.left||Math.round(Math.max(h.x,h.x+(h.w/2)-(r.width/2)));r.top=r.top||Math.round(Math.max(h.y,h.y+(h.h/2)-(r.height/2)));r.movable=r.resizable=true;j.mce_width=r.width;j.mce_height=r.height;j.mce_inline=true;j.mce_window_id=i;j.mce_auto_focus=r.auto_focus;y.features=r;y.params=j;y.onOpen.dispatch(y,r,j);if(r.type){k+=" mceModal";if(r.type){k+=" mce"+r.type.substring(0,1).toUpperCase()+r.type.substring(1)}r.resizable=false}if(r.statusbar){k+=" mceStatusbar"}if(r.resizable){k+=" mceResizable"}if(r.minimizable){k+=" mceMinimizable"}if(r.maximizable){k+=" mceMaximizable"}if(r.movable){k+=" mceMovable"}y._addAll(d.doc.body,["div",{id:i,"class":q.settings.inlinepopups_skin||"clearlooks2",style:"width:100px;height:100px"},["div",{id:i+"_wrapper","class":"mceWrapper"+k},["div",{id:i+"_top","class":"mceTop"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_title"},r.title||""]],["div",{id:i+"_middle","class":"mceMiddle"},["div",{id:i+"_left","class":"mceLeft"}],["span",{id:i+"_content"}],["div",{id:i+"_right","class":"mceRight"}]],["div",{id:i+"_bottom","class":"mceBottom"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_status"},"Content"]],["a",{"class":"mceMove",tabindex:"-1",href:"javascript:;"}],["a",{"class":"mceMin",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMax",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMed",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceClose",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{id:i+"_resize_n","class":"mceResize mceResizeN",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_s","class":"mceResize mceResizeS",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_w","class":"mceResize mceResizeW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_e","class":"mceResize mceResizeE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_nw","class":"mceResize mceResizeNW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_ne","class":"mceResize mceResizeNE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_sw","class":"mceResize mceResizeSW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_se","class":"mceResize mceResizeSE",tabindex:"-1",href:"javascript:;"}]]]);d.setStyles(i,{top:-10000,left:-10000});if(tinymce.isGecko){d.setStyle(i,"overflow","auto")}if(!r.type){g+=d.get(i+"_left").clientWidth;g+=d.get(i+"_right").clientWidth;s+=d.get(i+"_top").clientHeight;s+=d.get(i+"_bottom").clientHeight}d.setStyles(i,{top:r.top,left:r.left,width:r.width+g,height:r.height+s});x=r.url||r.file;if(x){if(tinymce.relaxedDomain){x+=(x.indexOf("?")==-1?"?":"&")+"mce_rdomain="+tinymce.relaxedDomain}x=tinymce._addVer(x)}if(!r.type){d.add(i+"_content","iframe",{id:i+"_ifr",src:'javascript:""',frameBorder:0,style:"border:0;width:10px;height:10px"});d.setStyles(i+"_ifr",{width:r.width,height:r.height});d.setAttrib(i+"_ifr","src",x)}else{d.add(i+"_wrapper","a",{id:i+"_ok","class":"mceButton mceOk",href:"javascript:;",onmousedown:"return false;"},"Ok");if(r.type=="confirm"){d.add(i+"_wrapper","a",{"class":"mceButton mceCancel",href:"javascript:;",onmousedown:"return false;"},"Cancel")}d.add(i+"_middle","div",{"class":"mceIcon"});d.setHTML(i+"_content",r.content.replace("\n","
    "))}n=a.add(i,"mousedown",function(t){var u=t.target,f,p;f=y.windows[i];y.focus(i);if(u.nodeName=="A"||u.nodeName=="a"){if(u.className=="mceMax"){f.oldPos=f.element.getXY();f.oldSize=f.element.getSize();p=d.getViewPort();p.w-=2;p.h-=2;f.element.moveTo(p.x,p.y);f.element.resizeTo(p.w,p.h);d.setStyles(i+"_ifr",{width:p.w-f.deltaWidth,height:p.h-f.deltaHeight});d.addClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMed"){f.element.moveTo(f.oldPos.x,f.oldPos.y);f.element.resizeTo(f.oldSize.w,f.oldSize.h);f.iframeElement.resizeTo(f.oldSize.w-f.deltaWidth,f.oldSize.h-f.deltaHeight);d.removeClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMove"){return y._startDrag(i,t,u.className)}else{if(d.hasClass(u,"mceResize")){return y._startDrag(i,t,u.className.substring(13))}}}}}});o=a.add(i,"click",function(f){var p=f.target;y.focus(i);if(p.nodeName=="A"||p.nodeName=="a"){switch(p.className){case"mceClose":y.close(null,i);return a.cancel(f);case"mceButton mceOk":case"mceButton mceCancel":r.button_func(p.className=="mceButton mceOk");return a.cancel(f)}}});v=y.windows[i]={id:i,mousedown_func:n,click_func:o,element:new b(i,{blocker:1,container:q.getContainer()}),iframeElement:new b(i+"_ifr"),features:r,deltaWidth:g,deltaHeight:s};v.iframeElement.on("focus",function(){y.focus(i)});if(y.count==0&&y.editor.getParam("dialog_type","modal")=="modal"){d.add(d.doc.body,"div",{id:"mceModalBlocker","class":(y.editor.settings.inlinepopups_skin||"clearlooks2")+"_modalBlocker",style:{zIndex:y.zIndex-1}});d.show("mceModalBlocker")}else{d.setStyle("mceModalBlocker","z-index",y.zIndex-1)}if(tinymce.isIE6||/Firefox\/2\./.test(navigator.userAgent)||(tinymce.isIE&&!d.boxModel)){d.setStyles("mceModalBlocker",{position:"absolute",left:h.x,top:h.y,width:h.w-2,height:h.h-2})}y.focus(i);y._fixIELayout(i,1);if(d.get(i+"_ok")){d.get(i+"_ok").focus()}y.count++;return v},focus:function(h){var g=this,f;if(f=g.windows[h]){f.zIndex=this.zIndex++;f.element.setStyle("zIndex",f.zIndex);f.element.update();h=h+"_wrapper";d.removeClass(g.lastId,"mceFocus");d.addClass(h,"mceFocus");g.lastId=h}},_addAll:function(k,h){var g,l,f=this,j=tinymce.DOM;if(c(h,"string")){k.appendChild(j.doc.createTextNode(h))}else{if(h.length){k=k.appendChild(j.create(h[0],h[1]));for(g=2;gf){i=m;f=m.zIndex}});if(i){h.focus(i.id)}}},setTitle:function(f,g){var h;f=this._findId(f);if(h=d.get(f+"_title")){h.innerHTML=d.encode(g)}},alert:function(g,f,j){var i=this,h;h=i.open({title:i,type:"alert",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},confirm:function(g,f,j){var i=this,h;h=i.open({title:i,type:"confirm",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},_findId:function(f){var g=this;if(typeof(f)=="string"){return f}e(g.windows,function(h){var i=d.get(h.id+"_ifr");if(i&&f==i.contentWindow){f=h.id;return false}});return f},_fixIELayout:function(i,h){var f,g;if(!tinymce.isIE6){return}e(["n","s","w","e","nw","ne","sw","se"],function(j){var k=d.get(i+"_resize_"+j);d.setStyles(k,{width:h?k.clientWidth:"",height:h?k.clientHeight:"",cursor:d.getStyle(k,"cursor",1)});d.setStyle(i+"_bottom","bottom","-1px");k=0});if(f=this.windows[i]){f.element.hide();f.element.show();e(d.select("div,a",i),function(k,j){if(k.currentStyle.backgroundImage!="none"){g=new Image();g.src=k.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/,"$1")}});d.get(i).style.filter=""}}});tinymce.PluginManager.add("inlinepopups",tinymce.plugins.InlinePopups)})(); \ No newline at end of file +(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.create("tinymce.InlineWindowManager:tinymce.WindowManager",{InlineWindowManager:function(f){var g=this;g.parent(f);g.zIndex=300000;g.count=0;g.windows={}},open:function(s,j){var z=this,i,k="",r=z.editor,g=0,v=0,h,m,o,q,l,x,y,n;s=s||{};j=j||{};if(!s.inline){return z.parent(s,j)}n=z._frontWindow();if(n&&d.get(n.id+"_ifr")){n.focussedElement=d.get(n.id+"_ifr").contentWindow.document.activeElement}if(!s.type){z.bookmark=r.selection.getBookmark(1)}i=d.uniqueId();h=d.getViewPort();s.width=parseInt(s.width||320);s.height=parseInt(s.height||240)+(tinymce.isIE?8:0);s.min_width=parseInt(s.min_width||150);s.min_height=parseInt(s.min_height||100);s.max_width=parseInt(s.max_width||2000);s.max_height=parseInt(s.max_height||2000);s.left=s.left||Math.round(Math.max(h.x,h.x+(h.w/2)-(s.width/2)));s.top=s.top||Math.round(Math.max(h.y,h.y+(h.h/2)-(s.height/2)));s.movable=s.resizable=true;j.mce_width=s.width;j.mce_height=s.height;j.mce_inline=true;j.mce_window_id=i;j.mce_auto_focus=s.auto_focus;z.features=s;z.params=j;z.onOpen.dispatch(z,s,j);if(s.type){k+=" mceModal";if(s.type){k+=" mce"+s.type.substring(0,1).toUpperCase()+s.type.substring(1)}s.resizable=false}if(s.statusbar){k+=" mceStatusbar"}if(s.resizable){k+=" mceResizable"}if(s.minimizable){k+=" mceMinimizable"}if(s.maximizable){k+=" mceMaximizable"}if(s.movable){k+=" mceMovable"}z._addAll(d.doc.body,["div",{id:i,role:"dialog","aria-labelledby":s.type?i+"_content":i+"_title","class":(r.settings.inlinepopups_skin||"clearlooks2")+(tinymce.isIE&&window.getSelection?" ie9":""),style:"width:100px;height:100px"},["div",{id:i+"_wrapper","class":"mceWrapper"+k},["div",{id:i+"_top","class":"mceTop"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_title"},s.title||""]],["div",{id:i+"_middle","class":"mceMiddle"},["div",{id:i+"_left","class":"mceLeft",tabindex:"0"}],["span",{id:i+"_content"}],["div",{id:i+"_right","class":"mceRight",tabindex:"0"}]],["div",{id:i+"_bottom","class":"mceBottom"},["div",{"class":"mceLeft"}],["div",{"class":"mceCenter"}],["div",{"class":"mceRight"}],["span",{id:i+"_status"},"Content"]],["a",{"class":"mceMove",tabindex:"-1",href:"javascript:;"}],["a",{"class":"mceMin",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMax",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceMed",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{"class":"mceClose",tabindex:"-1",href:"javascript:;",onmousedown:"return false;"}],["a",{id:i+"_resize_n","class":"mceResize mceResizeN",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_s","class":"mceResize mceResizeS",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_w","class":"mceResize mceResizeW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_e","class":"mceResize mceResizeE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_nw","class":"mceResize mceResizeNW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_ne","class":"mceResize mceResizeNE",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_sw","class":"mceResize mceResizeSW",tabindex:"-1",href:"javascript:;"}],["a",{id:i+"_resize_se","class":"mceResize mceResizeSE",tabindex:"-1",href:"javascript:;"}]]]);d.setStyles(i,{top:-10000,left:-10000});if(tinymce.isGecko){d.setStyle(i,"overflow","auto")}if(!s.type){g+=d.get(i+"_left").clientWidth;g+=d.get(i+"_right").clientWidth;v+=d.get(i+"_top").clientHeight;v+=d.get(i+"_bottom").clientHeight}d.setStyles(i,{top:s.top,left:s.left,width:s.width+g,height:s.height+v});y=s.url||s.file;if(y){if(tinymce.relaxedDomain){y+=(y.indexOf("?")==-1?"?":"&")+"mce_rdomain="+tinymce.relaxedDomain}y=tinymce._addVer(y)}if(!s.type){d.add(i+"_content","iframe",{id:i+"_ifr",src:'javascript:""',frameBorder:0,style:"border:0;width:10px;height:10px"});d.setStyles(i+"_ifr",{width:s.width,height:s.height});d.setAttrib(i+"_ifr","src",y)}else{d.add(i+"_wrapper","a",{id:i+"_ok","class":"mceButton mceOk",href:"javascript:;",onmousedown:"return false;"},"Ok");if(s.type=="confirm"){d.add(i+"_wrapper","a",{"class":"mceButton mceCancel",href:"javascript:;",onmousedown:"return false;"},"Cancel")}d.add(i+"_middle","div",{"class":"mceIcon"});d.setHTML(i+"_content",s.content.replace("\n","
    "));a.add(i,"keyup",function(f){var p=27;if(f.keyCode===p){s.button_func(false);return a.cancel(f)}});a.add(i,"keydown",function(f){var t,p=9;if(f.keyCode===p){t=d.select("a.mceCancel",i+"_wrapper")[0];if(t&&t!==f.target){t.focus()}else{d.get(i+"_ok").focus()}return a.cancel(f)}})}o=a.add(i,"mousedown",function(t){var u=t.target,f,p;f=z.windows[i];z.focus(i);if(u.nodeName=="A"||u.nodeName=="a"){if(u.className=="mceClose"){z.close(null,i);return a.cancel(t)}else{if(u.className=="mceMax"){f.oldPos=f.element.getXY();f.oldSize=f.element.getSize();p=d.getViewPort();p.w-=2;p.h-=2;f.element.moveTo(p.x,p.y);f.element.resizeTo(p.w,p.h);d.setStyles(i+"_ifr",{width:p.w-f.deltaWidth,height:p.h-f.deltaHeight});d.addClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMed"){f.element.moveTo(f.oldPos.x,f.oldPos.y);f.element.resizeTo(f.oldSize.w,f.oldSize.h);f.iframeElement.resizeTo(f.oldSize.w-f.deltaWidth,f.oldSize.h-f.deltaHeight);d.removeClass(i+"_wrapper","mceMaximized")}else{if(u.className=="mceMove"){return z._startDrag(i,t,u.className)}else{if(d.hasClass(u,"mceResize")){return z._startDrag(i,t,u.className.substring(13))}}}}}}});q=a.add(i,"click",function(f){var p=f.target;z.focus(i);if(p.nodeName=="A"||p.nodeName=="a"){switch(p.className){case"mceClose":z.close(null,i);return a.cancel(f);case"mceButton mceOk":case"mceButton mceCancel":s.button_func(p.className=="mceButton mceOk");return a.cancel(f)}}});a.add([i+"_left",i+"_right"],"focus",function(p){var t=d.get(i+"_ifr");if(t){var f=t.contentWindow.document.body;var u=d.select(":input:enabled,*[tabindex=0]",f);if(p.target.id===(i+"_left")){u[u.length-1].focus()}else{u[0].focus()}}else{d.get(i+"_ok").focus()}});x=z.windows[i]={id:i,mousedown_func:o,click_func:q,element:new b(i,{blocker:1,container:r.getContainer()}),iframeElement:new b(i+"_ifr"),features:s,deltaWidth:g,deltaHeight:v};x.iframeElement.on("focus",function(){z.focus(i)});if(z.count==0&&z.editor.getParam("dialog_type","modal")=="modal"){d.add(d.doc.body,"div",{id:"mceModalBlocker","class":(z.editor.settings.inlinepopups_skin||"clearlooks2")+"_modalBlocker",style:{zIndex:z.zIndex-1}});d.show("mceModalBlocker");d.setAttrib(d.doc.body,"aria-hidden","true")}else{d.setStyle("mceModalBlocker","z-index",z.zIndex-1)}if(tinymce.isIE6||/Firefox\/2\./.test(navigator.userAgent)||(tinymce.isIE&&!d.boxModel)){d.setStyles("mceModalBlocker",{position:"absolute",left:h.x,top:h.y,width:h.w-2,height:h.h-2})}d.setAttrib(i,"aria-hidden","false");z.focus(i);z._fixIELayout(i,1);if(d.get(i+"_ok")){d.get(i+"_ok").focus()}z.count++;return x},focus:function(h){var g=this,f;if(f=g.windows[h]){f.zIndex=this.zIndex++;f.element.setStyle("zIndex",f.zIndex);f.element.update();h=h+"_wrapper";d.removeClass(g.lastId,"mceFocus");d.addClass(h,"mceFocus");g.lastId=h;if(f.focussedElement){f.focussedElement.focus()}else{if(d.get(h+"_ok")){d.get(f.id+"_ok").focus()}else{if(d.get(f.id+"_ifr")){d.get(f.id+"_ifr").focus()}}}}},_addAll:function(k,h){var g,l,f=this,j=tinymce.DOM;if(c(h,"string")){k.appendChild(j.doc.createTextNode(h))}else{if(h.length){k=k.appendChild(j.create(h[0],h[1]));for(g=2;gf){g=h;f=h.zIndex}});return g},setTitle:function(f,g){var h;f=this._findId(f);if(h=d.get(f+"_title")){h.innerHTML=d.encode(g)}},alert:function(g,f,j){var i=this,h;h=i.open({title:i,type:"alert",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},confirm:function(g,f,j){var i=this,h;h=i.open({title:i,type:"confirm",button_func:function(k){if(f){f.call(k||i,k)}i.close(null,h.id)},content:d.encode(i.editor.getLang(g,g)),inline:1,width:400,height:130})},_findId:function(f){var g=this;if(typeof(f)=="string"){return f}e(g.windows,function(h){var i=d.get(h.id+"_ifr");if(i&&f==i.contentWindow){f=h.id;return false}});return f},_fixIELayout:function(i,h){var f,g;if(!tinymce.isIE6){return}e(["n","s","w","e","nw","ne","sw","se"],function(j){var k=d.get(i+"_resize_"+j);d.setStyles(k,{width:h?k.clientWidth:"",height:h?k.clientHeight:"",cursor:d.getStyle(k,"cursor",1)});d.setStyle(i+"_bottom","bottom","-1px");k=0});if(f=this.windows[i]){f.element.hide();f.element.show();e(d.select("div,a",i),function(k,j){if(k.currentStyle.backgroundImage!="none"){g=new Image();g.src=k.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/,"$1")}});d.get(i).style.filter=""}}});tinymce.PluginManager.add("inlinepopups",tinymce.plugins.InlinePopups)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/inlinepopups/editor_plugin_src.js b/js/tiny_mce/plugins/inlinepopups/editor_plugin_src.js index e991683d..2a6f3ad2 100644 --- a/js/tiny_mce/plugins/inlinepopups/editor_plugin_src.js +++ b/js/tiny_mce/plugins/inlinepopups/editor_plugin_src.js @@ -1,635 +1,699 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var DOM = tinymce.DOM, Element = tinymce.dom.Element, Event = tinymce.dom.Event, each = tinymce.each, is = tinymce.is; - - tinymce.create('tinymce.plugins.InlinePopups', { - init : function(ed, url) { - // Replace window manager - ed.onBeforeRenderUI.add(function() { - ed.windowManager = new tinymce.InlineWindowManager(ed); - DOM.loadCSS(url + '/skins/' + (ed.settings.inlinepopups_skin || 'clearlooks2') + "/window.css"); - }); - }, - - getInfo : function() { - return { - longname : 'InlinePopups', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - tinymce.create('tinymce.InlineWindowManager:tinymce.WindowManager', { - InlineWindowManager : function(ed) { - var t = this; - - t.parent(ed); - t.zIndex = 300000; - t.count = 0; - t.windows = {}; - }, - - open : function(f, p) { - var t = this, id, opt = '', ed = t.editor, dw = 0, dh = 0, vp, po, mdf, clf, we, w, u; - - f = f || {}; - p = p || {}; - - // Run native windows - if (!f.inline) - return t.parent(f, p); - - // Only store selection if the type is a normal window - if (!f.type) - t.bookmark = ed.selection.getBookmark(1); - - id = DOM.uniqueId(); - vp = DOM.getViewPort(); - f.width = parseInt(f.width || 320); - f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0); - f.min_width = parseInt(f.min_width || 150); - f.min_height = parseInt(f.min_height || 100); - f.max_width = parseInt(f.max_width || 2000); - f.max_height = parseInt(f.max_height || 2000); - f.left = f.left || Math.round(Math.max(vp.x, vp.x + (vp.w / 2.0) - (f.width / 2.0))); - f.top = f.top || Math.round(Math.max(vp.y, vp.y + (vp.h / 2.0) - (f.height / 2.0))); - f.movable = f.resizable = true; - p.mce_width = f.width; - p.mce_height = f.height; - p.mce_inline = true; - p.mce_window_id = id; - p.mce_auto_focus = f.auto_focus; - - // Transpose -// po = DOM.getPos(ed.getContainer()); -// f.left -= po.x; -// f.top -= po.y; - - t.features = f; - t.params = p; - t.onOpen.dispatch(t, f, p); - - if (f.type) { - opt += ' mceModal'; - - if (f.type) - opt += ' mce' + f.type.substring(0, 1).toUpperCase() + f.type.substring(1); - - f.resizable = false; - } - - if (f.statusbar) - opt += ' mceStatusbar'; - - if (f.resizable) - opt += ' mceResizable'; - - if (f.minimizable) - opt += ' mceMinimizable'; - - if (f.maximizable) - opt += ' mceMaximizable'; - - if (f.movable) - opt += ' mceMovable'; - - // Create DOM objects - t._addAll(DOM.doc.body, - ['div', {id : id, 'class' : ed.settings.inlinepopups_skin || 'clearlooks2', style : 'width:100px;height:100px'}, - ['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt}, - ['div', {id : id + '_top', 'class' : 'mceTop'}, - ['div', {'class' : 'mceLeft'}], - ['div', {'class' : 'mceCenter'}], - ['div', {'class' : 'mceRight'}], - ['span', {id : id + '_title'}, f.title || ''] - ], - - ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, - ['div', {id : id + '_left', 'class' : 'mceLeft'}], - ['span', {id : id + '_content'}], - ['div', {id : id + '_right', 'class' : 'mceRight'}] - ], - - ['div', {id : id + '_bottom', 'class' : 'mceBottom'}, - ['div', {'class' : 'mceLeft'}], - ['div', {'class' : 'mceCenter'}], - ['div', {'class' : 'mceRight'}], - ['span', {id : id + '_status'}, 'Content'] - ], - - ['a', {'class' : 'mceMove', tabindex : '-1', href : 'javascript:;'}], - ['a', {'class' : 'mceMin', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], - ['a', {'class' : 'mceMax', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], - ['a', {'class' : 'mceMed', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], - ['a', {'class' : 'mceClose', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], - ['a', {id : id + '_resize_n', 'class' : 'mceResize mceResizeN', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_s', 'class' : 'mceResize mceResizeS', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_w', 'class' : 'mceResize mceResizeW', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_e', 'class' : 'mceResize mceResizeE', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_nw', 'class' : 'mceResize mceResizeNW', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_ne', 'class' : 'mceResize mceResizeNE', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_sw', 'class' : 'mceResize mceResizeSW', tabindex : '-1', href : 'javascript:;'}], - ['a', {id : id + '_resize_se', 'class' : 'mceResize mceResizeSE', tabindex : '-1', href : 'javascript:;'}] - ] - ] - ); - - DOM.setStyles(id, {top : -10000, left : -10000}); - - // Fix gecko rendering bug, where the editors iframe messed with window contents - if (tinymce.isGecko) - DOM.setStyle(id, 'overflow', 'auto'); - - // Measure borders - if (!f.type) { - dw += DOM.get(id + '_left').clientWidth; - dw += DOM.get(id + '_right').clientWidth; - dh += DOM.get(id + '_top').clientHeight; - dh += DOM.get(id + '_bottom').clientHeight; - } - - // Resize window - DOM.setStyles(id, {top : f.top, left : f.left, width : f.width + dw, height : f.height + dh}); - - u = f.url || f.file; - if (u) { - if (tinymce.relaxedDomain) - u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain; - - u = tinymce._addVer(u); - } - - if (!f.type) { - DOM.add(id + '_content', 'iframe', {id : id + '_ifr', src : 'javascript:""', frameBorder : 0, style : 'border:0;width:10px;height:10px'}); - DOM.setStyles(id + '_ifr', {width : f.width, height : f.height}); - DOM.setAttrib(id + '_ifr', 'src', u); - } else { - DOM.add(id + '_wrapper', 'a', {id : id + '_ok', 'class' : 'mceButton mceOk', href : 'javascript:;', onmousedown : 'return false;'}, 'Ok'); - - if (f.type == 'confirm') - DOM.add(id + '_wrapper', 'a', {'class' : 'mceButton mceCancel', href : 'javascript:;', onmousedown : 'return false;'}, 'Cancel'); - - DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'}); - DOM.setHTML(id + '_content', f.content.replace('\n', '
    ')); - } - - // Register events - mdf = Event.add(id, 'mousedown', function(e) { - var n = e.target, w, vp; - - w = t.windows[id]; - t.focus(id); - - if (n.nodeName == 'A' || n.nodeName == 'a') { - if (n.className == 'mceMax') { - w.oldPos = w.element.getXY(); - w.oldSize = w.element.getSize(); - - vp = DOM.getViewPort(); - - // Reduce viewport size to avoid scrollbars - vp.w -= 2; - vp.h -= 2; - - w.element.moveTo(vp.x, vp.y); - w.element.resizeTo(vp.w, vp.h); - DOM.setStyles(id + '_ifr', {width : vp.w - w.deltaWidth, height : vp.h - w.deltaHeight}); - DOM.addClass(id + '_wrapper', 'mceMaximized'); - } else if (n.className == 'mceMed') { - // Reset to old size - w.element.moveTo(w.oldPos.x, w.oldPos.y); - w.element.resizeTo(w.oldSize.w, w.oldSize.h); - w.iframeElement.resizeTo(w.oldSize.w - w.deltaWidth, w.oldSize.h - w.deltaHeight); - - DOM.removeClass(id + '_wrapper', 'mceMaximized'); - } else if (n.className == 'mceMove') - return t._startDrag(id, e, n.className); - else if (DOM.hasClass(n, 'mceResize')) - return t._startDrag(id, e, n.className.substring(13)); - } - }); - - clf = Event.add(id, 'click', function(e) { - var n = e.target; - - t.focus(id); - - if (n.nodeName == 'A' || n.nodeName == 'a') { - switch (n.className) { - case 'mceClose': - t.close(null, id); - return Event.cancel(e); - - case 'mceButton mceOk': - case 'mceButton mceCancel': - f.button_func(n.className == 'mceButton mceOk'); - return Event.cancel(e); - } - } - }); - - // Add window - w = t.windows[id] = { - id : id, - mousedown_func : mdf, - click_func : clf, - element : new Element(id, {blocker : 1, container : ed.getContainer()}), - iframeElement : new Element(id + '_ifr'), - features : f, - deltaWidth : dw, - deltaHeight : dh - }; - - w.iframeElement.on('focus', function() { - t.focus(id); - }); - - // Setup blocker - if (t.count == 0 && t.editor.getParam('dialog_type', 'modal') == 'modal') { - DOM.add(DOM.doc.body, 'div', { - id : 'mceModalBlocker', - 'class' : (t.editor.settings.inlinepopups_skin || 'clearlooks2') + '_modalBlocker', - style : {zIndex : t.zIndex - 1} - }); - - DOM.show('mceModalBlocker'); // Reduces flicker in IE - } else - DOM.setStyle('mceModalBlocker', 'z-index', t.zIndex - 1); - - if (tinymce.isIE6 || /Firefox\/2\./.test(navigator.userAgent) || (tinymce.isIE && !DOM.boxModel)) - DOM.setStyles('mceModalBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2}); - - t.focus(id); - t._fixIELayout(id, 1); - - // Focus ok button - if (DOM.get(id + '_ok')) - DOM.get(id + '_ok').focus(); - - t.count++; - - return w; - }, - - focus : function(id) { - var t = this, w; - - if (w = t.windows[id]) { - w.zIndex = this.zIndex++; - w.element.setStyle('zIndex', w.zIndex); - w.element.update(); - - id = id + '_wrapper'; - DOM.removeClass(t.lastId, 'mceFocus'); - DOM.addClass(id, 'mceFocus'); - t.lastId = id; - } - }, - - _addAll : function(te, ne) { - var i, n, t = this, dom = tinymce.DOM; - - if (is(ne, 'string')) - te.appendChild(dom.doc.createTextNode(ne)); - else if (ne.length) { - te = te.appendChild(dom.create(ne[0], ne[1])); - - for (i=2; i ix) { - fw = w; - ix = w.zIndex; - } - }); - - if (fw) - t.focus(fw.id); - } - }, - - setTitle : function(w, ti) { - var e; - - w = this._findId(w); - - if (e = DOM.get(w + '_title')) - e.innerHTML = DOM.encode(ti); - }, - - alert : function(txt, cb, s) { - var t = this, w; - - w = t.open({ - title : t, - type : 'alert', - button_func : function(s) { - if (cb) - cb.call(s || t, s); - - t.close(null, w.id); - }, - content : DOM.encode(t.editor.getLang(txt, txt)), - inline : 1, - width : 400, - height : 130 - }); - }, - - confirm : function(txt, cb, s) { - var t = this, w; - - w = t.open({ - title : t, - type : 'confirm', - button_func : function(s) { - if (cb) - cb.call(s || t, s); - - t.close(null, w.id); - }, - content : DOM.encode(t.editor.getLang(txt, txt)), - inline : 1, - width : 400, - height : 130 - }); - }, - - // Internal functions - - _findId : function(w) { - var t = this; - - if (typeof(w) == 'string') - return w; - - each(t.windows, function(wo) { - var ifr = DOM.get(wo.id + '_ifr'); - - if (ifr && w == ifr.contentWindow) { - w = wo.id; - return false; - } - }); - - return w; - }, - - _fixIELayout : function(id, s) { - var w, img; - - if (!tinymce.isIE6) - return; - - // Fixes the bug where hover flickers and does odd things in IE6 - each(['n','s','w','e','nw','ne','sw','se'], function(v) { - var e = DOM.get(id + '_resize_' + v); - - DOM.setStyles(e, { - width : s ? e.clientWidth : '', - height : s ? e.clientHeight : '', - cursor : DOM.getStyle(e, 'cursor', 1) - }); - - DOM.setStyle(id + "_bottom", 'bottom', '-1px'); - - e = 0; - }); - - // Fixes graphics glitch - if (w = this.windows[id]) { - // Fixes rendering bug after resize - w.element.hide(); - w.element.show(); - - // Forced a repaint of the window - //DOM.get(id).style.filter = ''; - - // IE has a bug where images used in CSS won't get loaded - // sometimes when the cache in the browser is disabled - // This fix tries to solve it by loading the images using the image object - each(DOM.select('div,a', id), function(e, i) { - if (e.currentStyle.backgroundImage != 'none') { - img = new Image(); - img.src = e.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/, '$1'); - } - }); - - DOM.get(id).style.filter = ''; - } - } - }); - - // Register plugin - tinymce.PluginManager.add('inlinepopups', tinymce.plugins.InlinePopups); -})(); - +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Element = tinymce.dom.Element, Event = tinymce.dom.Event, each = tinymce.each, is = tinymce.is; + + tinymce.create('tinymce.plugins.InlinePopups', { + init : function(ed, url) { + // Replace window manager + ed.onBeforeRenderUI.add(function() { + ed.windowManager = new tinymce.InlineWindowManager(ed); + DOM.loadCSS(url + '/skins/' + (ed.settings.inlinepopups_skin || 'clearlooks2') + "/window.css"); + }); + }, + + getInfo : function() { + return { + longname : 'InlinePopups', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.create('tinymce.InlineWindowManager:tinymce.WindowManager', { + InlineWindowManager : function(ed) { + var t = this; + + t.parent(ed); + t.zIndex = 300000; + t.count = 0; + t.windows = {}; + }, + + open : function(f, p) { + var t = this, id, opt = '', ed = t.editor, dw = 0, dh = 0, vp, po, mdf, clf, we, w, u, parentWindow; + + f = f || {}; + p = p || {}; + + // Run native windows + if (!f.inline) + return t.parent(f, p); + + parentWindow = t._frontWindow(); + if (parentWindow && DOM.get(parentWindow.id + '_ifr')) { + parentWindow.focussedElement = DOM.get(parentWindow.id + '_ifr').contentWindow.document.activeElement; + } + + // Only store selection if the type is a normal window + if (!f.type) + t.bookmark = ed.selection.getBookmark(1); + + id = DOM.uniqueId(); + vp = DOM.getViewPort(); + f.width = parseInt(f.width || 320); + f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0); + f.min_width = parseInt(f.min_width || 150); + f.min_height = parseInt(f.min_height || 100); + f.max_width = parseInt(f.max_width || 2000); + f.max_height = parseInt(f.max_height || 2000); + f.left = f.left || Math.round(Math.max(vp.x, vp.x + (vp.w / 2.0) - (f.width / 2.0))); + f.top = f.top || Math.round(Math.max(vp.y, vp.y + (vp.h / 2.0) - (f.height / 2.0))); + f.movable = f.resizable = true; + p.mce_width = f.width; + p.mce_height = f.height; + p.mce_inline = true; + p.mce_window_id = id; + p.mce_auto_focus = f.auto_focus; + + // Transpose +// po = DOM.getPos(ed.getContainer()); +// f.left -= po.x; +// f.top -= po.y; + + t.features = f; + t.params = p; + t.onOpen.dispatch(t, f, p); + + if (f.type) { + opt += ' mceModal'; + + if (f.type) + opt += ' mce' + f.type.substring(0, 1).toUpperCase() + f.type.substring(1); + + f.resizable = false; + } + + if (f.statusbar) + opt += ' mceStatusbar'; + + if (f.resizable) + opt += ' mceResizable'; + + if (f.minimizable) + opt += ' mceMinimizable'; + + if (f.maximizable) + opt += ' mceMaximizable'; + + if (f.movable) + opt += ' mceMovable'; + + // Create DOM objects + t._addAll(DOM.doc.body, + ['div', {id : id, role : 'dialog', 'aria-labelledby': f.type ? id + '_content' : id + '_title', 'class' : (ed.settings.inlinepopups_skin || 'clearlooks2') + (tinymce.isIE && window.getSelection ? ' ie9' : ''), style : 'width:100px;height:100px'}, + ['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt}, + ['div', {id : id + '_top', 'class' : 'mceTop'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_title'}, f.title || ''] + ], + + ['div', {id : id + '_middle', 'class' : 'mceMiddle'}, + ['div', {id : id + '_left', 'class' : 'mceLeft', tabindex : '0'}], + ['span', {id : id + '_content'}], + ['div', {id : id + '_right', 'class' : 'mceRight', tabindex : '0'}] + ], + + ['div', {id : id + '_bottom', 'class' : 'mceBottom'}, + ['div', {'class' : 'mceLeft'}], + ['div', {'class' : 'mceCenter'}], + ['div', {'class' : 'mceRight'}], + ['span', {id : id + '_status'}, 'Content'] + ], + + ['a', {'class' : 'mceMove', tabindex : '-1', href : 'javascript:;'}], + ['a', {'class' : 'mceMin', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMax', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceMed', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {'class' : 'mceClose', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}], + ['a', {id : id + '_resize_n', 'class' : 'mceResize mceResizeN', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_s', 'class' : 'mceResize mceResizeS', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_w', 'class' : 'mceResize mceResizeW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_e', 'class' : 'mceResize mceResizeE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_nw', 'class' : 'mceResize mceResizeNW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_ne', 'class' : 'mceResize mceResizeNE', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_sw', 'class' : 'mceResize mceResizeSW', tabindex : '-1', href : 'javascript:;'}], + ['a', {id : id + '_resize_se', 'class' : 'mceResize mceResizeSE', tabindex : '-1', href : 'javascript:;'}] + ] + ] + ); + + DOM.setStyles(id, {top : -10000, left : -10000}); + + // Fix gecko rendering bug, where the editors iframe messed with window contents + if (tinymce.isGecko) + DOM.setStyle(id, 'overflow', 'auto'); + + // Measure borders + if (!f.type) { + dw += DOM.get(id + '_left').clientWidth; + dw += DOM.get(id + '_right').clientWidth; + dh += DOM.get(id + '_top').clientHeight; + dh += DOM.get(id + '_bottom').clientHeight; + } + + // Resize window + DOM.setStyles(id, {top : f.top, left : f.left, width : f.width + dw, height : f.height + dh}); + + u = f.url || f.file; + if (u) { + if (tinymce.relaxedDomain) + u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain; + + u = tinymce._addVer(u); + } + + if (!f.type) { + DOM.add(id + '_content', 'iframe', {id : id + '_ifr', src : 'javascript:""', frameBorder : 0, style : 'border:0;width:10px;height:10px'}); + DOM.setStyles(id + '_ifr', {width : f.width, height : f.height}); + DOM.setAttrib(id + '_ifr', 'src', u); + } else { + DOM.add(id + '_wrapper', 'a', {id : id + '_ok', 'class' : 'mceButton mceOk', href : 'javascript:;', onmousedown : 'return false;'}, 'Ok'); + + if (f.type == 'confirm') + DOM.add(id + '_wrapper', 'a', {'class' : 'mceButton mceCancel', href : 'javascript:;', onmousedown : 'return false;'}, 'Cancel'); + + DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'}); + DOM.setHTML(id + '_content', f.content.replace('\n', '
    ')); + + Event.add(id, 'keyup', function(evt) { + var VK_ESCAPE = 27; + if (evt.keyCode === VK_ESCAPE) { + f.button_func(false); + return Event.cancel(evt); + } + }); + + Event.add(id, 'keydown', function(evt) { + var cancelButton, VK_TAB = 9; + if (evt.keyCode === VK_TAB) { + cancelButton = DOM.select('a.mceCancel', id + '_wrapper')[0]; + if (cancelButton && cancelButton !== evt.target) { + cancelButton.focus(); + } else { + DOM.get(id + '_ok').focus(); + } + return Event.cancel(evt); + } + }); + } + + // Register events + mdf = Event.add(id, 'mousedown', function(e) { + var n = e.target, w, vp; + + w = t.windows[id]; + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + if (n.className == 'mceClose') { + t.close(null, id); + return Event.cancel(e); + } else if (n.className == 'mceMax') { + w.oldPos = w.element.getXY(); + w.oldSize = w.element.getSize(); + + vp = DOM.getViewPort(); + + // Reduce viewport size to avoid scrollbars + vp.w -= 2; + vp.h -= 2; + + w.element.moveTo(vp.x, vp.y); + w.element.resizeTo(vp.w, vp.h); + DOM.setStyles(id + '_ifr', {width : vp.w - w.deltaWidth, height : vp.h - w.deltaHeight}); + DOM.addClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMed') { + // Reset to old size + w.element.moveTo(w.oldPos.x, w.oldPos.y); + w.element.resizeTo(w.oldSize.w, w.oldSize.h); + w.iframeElement.resizeTo(w.oldSize.w - w.deltaWidth, w.oldSize.h - w.deltaHeight); + + DOM.removeClass(id + '_wrapper', 'mceMaximized'); + } else if (n.className == 'mceMove') + return t._startDrag(id, e, n.className); + else if (DOM.hasClass(n, 'mceResize')) + return t._startDrag(id, e, n.className.substring(13)); + } + }); + + clf = Event.add(id, 'click', function(e) { + var n = e.target; + + t.focus(id); + + if (n.nodeName == 'A' || n.nodeName == 'a') { + switch (n.className) { + case 'mceClose': + t.close(null, id); + return Event.cancel(e); + + case 'mceButton mceOk': + case 'mceButton mceCancel': + f.button_func(n.className == 'mceButton mceOk'); + return Event.cancel(e); + } + } + }); + + // Make sure the tab order loops within the dialog. + Event.add([id + '_left', id + '_right'], 'focus', function(evt) { + var iframe = DOM.get(id + '_ifr'); + if (iframe) { + var body = iframe.contentWindow.document.body; + var focusable = DOM.select(':input:enabled,*[tabindex=0]', body); + if (evt.target.id === (id + '_left')) { + focusable[focusable.length - 1].focus(); + } else { + focusable[0].focus(); + } + } else { + DOM.get(id + '_ok').focus(); + } + }); + + // Add window + w = t.windows[id] = { + id : id, + mousedown_func : mdf, + click_func : clf, + element : new Element(id, {blocker : 1, container : ed.getContainer()}), + iframeElement : new Element(id + '_ifr'), + features : f, + deltaWidth : dw, + deltaHeight : dh + }; + + w.iframeElement.on('focus', function() { + t.focus(id); + }); + + // Setup blocker + if (t.count == 0 && t.editor.getParam('dialog_type', 'modal') == 'modal') { + DOM.add(DOM.doc.body, 'div', { + id : 'mceModalBlocker', + 'class' : (t.editor.settings.inlinepopups_skin || 'clearlooks2') + '_modalBlocker', + style : {zIndex : t.zIndex - 1} + }); + + DOM.show('mceModalBlocker'); // Reduces flicker in IE + DOM.setAttrib(DOM.doc.body, 'aria-hidden', 'true'); + } else + DOM.setStyle('mceModalBlocker', 'z-index', t.zIndex - 1); + + if (tinymce.isIE6 || /Firefox\/2\./.test(navigator.userAgent) || (tinymce.isIE && !DOM.boxModel)) + DOM.setStyles('mceModalBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2}); + + DOM.setAttrib(id, 'aria-hidden', 'false'); + t.focus(id); + t._fixIELayout(id, 1); + + // Focus ok button + if (DOM.get(id + '_ok')) + DOM.get(id + '_ok').focus(); + t.count++; + + return w; + }, + + focus : function(id) { + var t = this, w; + + if (w = t.windows[id]) { + w.zIndex = this.zIndex++; + w.element.setStyle('zIndex', w.zIndex); + w.element.update(); + + id = id + '_wrapper'; + DOM.removeClass(t.lastId, 'mceFocus'); + DOM.addClass(id, 'mceFocus'); + t.lastId = id; + + if (w.focussedElement) { + w.focussedElement.focus(); + } else if (DOM.get(id + '_ok')) { + DOM.get(w.id + '_ok').focus(); + } else if (DOM.get(w.id + '_ifr')) { + DOM.get(w.id + '_ifr').focus(); + } + } + }, + + _addAll : function(te, ne) { + var i, n, t = this, dom = tinymce.DOM; + + if (is(ne, 'string')) + te.appendChild(dom.doc.createTextNode(ne)); + else if (ne.length) { + te = te.appendChild(dom.create(ne[0], ne[1])); + + for (i=2; i ix) { + fw = w; + ix = w.zIndex; + } + }); + return fw; + }, + + setTitle : function(w, ti) { + var e; + + w = this._findId(w); + + if (e = DOM.get(w + '_title')) + e.innerHTML = DOM.encode(ti); + }, + + alert : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'alert', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + confirm : function(txt, cb, s) { + var t = this, w; + + w = t.open({ + title : t, + type : 'confirm', + button_func : function(s) { + if (cb) + cb.call(s || t, s); + + t.close(null, w.id); + }, + content : DOM.encode(t.editor.getLang(txt, txt)), + inline : 1, + width : 400, + height : 130 + }); + }, + + // Internal functions + + _findId : function(w) { + var t = this; + + if (typeof(w) == 'string') + return w; + + each(t.windows, function(wo) { + var ifr = DOM.get(wo.id + '_ifr'); + + if (ifr && w == ifr.contentWindow) { + w = wo.id; + return false; + } + }); + + return w; + }, + + _fixIELayout : function(id, s) { + var w, img; + + if (!tinymce.isIE6) + return; + + // Fixes the bug where hover flickers and does odd things in IE6 + each(['n','s','w','e','nw','ne','sw','se'], function(v) { + var e = DOM.get(id + '_resize_' + v); + + DOM.setStyles(e, { + width : s ? e.clientWidth : '', + height : s ? e.clientHeight : '', + cursor : DOM.getStyle(e, 'cursor', 1) + }); + + DOM.setStyle(id + "_bottom", 'bottom', '-1px'); + + e = 0; + }); + + // Fixes graphics glitch + if (w = this.windows[id]) { + // Fixes rendering bug after resize + w.element.hide(); + w.element.show(); + + // Forced a repaint of the window + //DOM.get(id).style.filter = ''; + + // IE has a bug where images used in CSS won't get loaded + // sometimes when the cache in the browser is disabled + // This fix tries to solve it by loading the images using the image object + each(DOM.select('div,a', id), function(e, i) { + if (e.currentStyle.backgroundImage != 'none') { + img = new Image(); + img.src = e.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/, '$1'); + } + }); + + DOM.get(id).style.filter = ''; + } + } + }); + + // Register plugin + tinymce.PluginManager.add('inlinepopups', tinymce.plugins.InlinePopups); +})(); + diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif index 94abd087..21913985 100644 Binary files a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif and b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif differ diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/button.gif b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/button.gif index e671094c..f957e49a 100644 Binary files a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/button.gif and b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/button.gif differ diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif index 497307a8..20acbbf7 100644 Binary files a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif and b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif differ diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif index c894b2e8..d5de1cc2 100644 Binary files a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif and b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif differ diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif index 43a735f2..0b4cc368 100644 Binary files a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif and b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif differ diff --git a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/window.css b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/window.css index 5e6fd7d3..a50d4fc5 100644 --- a/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/window.css +++ b/js/tiny_mce/plugins/inlinepopups/skins/clearlooks2/window.css @@ -87,4 +87,4 @@ .clearlooks2 .mceAlert .mceIcon {background:url(img/alert.gif)} .clearlooks2 .mceConfirm .mceOk {left:50%; top:auto; margin-left: -90px} .clearlooks2 .mceConfirm .mceCancel {left:50%; top:auto} -.clearlooks2 .mceConfirm .mceIcon {background:url(img/confirm.gif)} \ No newline at end of file +.clearlooks2 .mceConfirm .mceIcon {background:url(img/confirm.gif)} diff --git a/js/tiny_mce/plugins/layer/editor_plugin.js b/js/tiny_mce/plugins/layer/editor_plugin.js index f88a6dd2..ca3857a7 100644 --- a/js/tiny_mce/plugins/layer/editor_plugin.js +++ b/js/tiny_mce/plugins/layer/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.Layer",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceInsertLayer",c._insertLayer,c);a.addCommand("mceMoveForward",function(){c._move(1)});a.addCommand("mceMoveBackward",function(){c._move(-1)});a.addCommand("mceMakeAbsolute",function(){c._toggleAbsolute()});a.addButton("moveforward",{title:"layer.forward_desc",cmd:"mceMoveForward"});a.addButton("movebackward",{title:"layer.backward_desc",cmd:"mceMoveBackward"});a.addButton("absolute",{title:"layer.absolute_desc",cmd:"mceMakeAbsolute"});a.addButton("insertlayer",{title:"layer.insertlayer_desc",cmd:"mceInsertLayer"});a.onInit.add(function(){if(tinymce.isIE){a.getDoc().execCommand("2D-Position",false,true)}});a.onNodeChange.add(c._nodeChange,c);a.onVisualAid.add(c._visualAid,c)},getInfo:function(){return{longname:"Layer",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_nodeChange:function(b,a,e){var c,d;c=this._getParentLayer(e);d=b.dom.getParent(e,"DIV,P,IMG");if(!d){a.setDisabled("absolute",1);a.setDisabled("moveforward",1);a.setDisabled("movebackward",1)}else{a.setDisabled("absolute",0);a.setDisabled("moveforward",!c);a.setDisabled("movebackward",!c);a.setActive("absolute",c&&c.style.position.toLowerCase()=="absolute")}},_visualAid:function(a,c,b){var d=a.dom;tinymce.each(d.select("div,p",c),function(f){if(/^(absolute|relative|static)$/i.test(f.style.position)){if(b){d.addClass(f,"mceItemVisualAid")}else{d.removeClass(f,"mceItemVisualAid")}}})},_move:function(h){var b=this.editor,f,g=[],e=this._getParentLayer(b.selection.getNode()),c=-1,j=-1,a;a=[];tinymce.walk(b.getBody(),function(d){if(d.nodeType==1&&/^(absolute|relative|static)$/i.test(d.style.position)){a.push(d)}},"childNodes");for(f=0;f-1){a[c].style.zIndex=g[j];a[j].style.zIndex=g[c]}else{if(g[c]>0){a[c].style.zIndex=g[c]-1}}}else{for(f=0;fg[c]){j=f;break}}if(j>-1){a[c].style.zIndex=g[j];a[j].style.zIndex=g[c]}else{a[c].style.zIndex=g[c]+1}}b.execCommand("mceRepaint")},_getParentLayer:function(a){return this.editor.dom.getParent(a,function(b){return b.nodeType==1&&/^(absolute|relative|static)$/i.test(b.style.position)})},_insertLayer:function(){var a=this.editor,b=a.dom.getPos(a.dom.getParent(a.selection.getNode(),"*"));a.dom.add(a.getBody(),"div",{style:{position:"absolute",left:b.x,top:(b.y>20?b.y:20),width:100,height:100},"class":"mceItemVisualAid"},a.selection.getContent()||a.getLang("layer.content"))},_toggleAbsolute:function(){var a=this.editor,b=this._getParentLayer(a.selection.getNode());if(!b){b=a.dom.getParent(a.selection.getNode(),"DIV,P,IMG")}if(b){if(b.style.position.toLowerCase()=="absolute"){a.dom.setStyles(b,{position:"",left:"",top:"",width:"",height:""});a.dom.removeClass(b,"mceItemVisualAid")}else{if(b.style.left==""){b.style.left=20+"px"}if(b.style.top==""){b.style.top=20+"px"}if(b.style.width==""){b.style.width=b.width?(b.width+"px"):"100px"}if(b.style.height==""){b.style.height=b.height?(b.height+"px"):"100px"}b.style.position="absolute";a.addVisual(a.getBody())}a.execCommand("mceRepaint");a.nodeChanged()}}});tinymce.PluginManager.add("layer",tinymce.plugins.Layer)})(); \ No newline at end of file +(function(){function a(b){do{if(b.className&&b.className.indexOf("mceItemLayer")!=-1){return b}}while(b=b.parentNode)}tinymce.create("tinymce.plugins.Layer",{init:function(b,c){var d=this;d.editor=b;b.addCommand("mceInsertLayer",d._insertLayer,d);b.addCommand("mceMoveForward",function(){d._move(1)});b.addCommand("mceMoveBackward",function(){d._move(-1)});b.addCommand("mceMakeAbsolute",function(){d._toggleAbsolute()});b.addButton("moveforward",{title:"layer.forward_desc",cmd:"mceMoveForward"});b.addButton("movebackward",{title:"layer.backward_desc",cmd:"mceMoveBackward"});b.addButton("absolute",{title:"layer.absolute_desc",cmd:"mceMakeAbsolute"});b.addButton("insertlayer",{title:"layer.insertlayer_desc",cmd:"mceInsertLayer"});b.onInit.add(function(){var e=b.dom;if(tinymce.isIE){b.getDoc().execCommand("2D-Position",false,true)}});b.onMouseUp.add(function(f,h){var g=a(h.target);if(g){f.dom.setAttrib(g,"data-mce-style","")}});b.onMouseDown.add(function(f,j){var h=j.target,i=f.getDoc(),g;if(tinymce.isGecko){if(a(h)){if(i.designMode!=="on"){i.designMode="on";h=i.body;g=h.parentNode;g.removeChild(h);g.appendChild(h)}}else{if(i.designMode=="on"){i.designMode="off"}}}});b.onNodeChange.add(d._nodeChange,d);b.onVisualAid.add(d._visualAid,d)},getInfo:function(){return{longname:"Layer",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_nodeChange:function(c,b,f){var d,e;d=this._getParentLayer(f);e=c.dom.getParent(f,"DIV,P,IMG");if(!e){b.setDisabled("absolute",1);b.setDisabled("moveforward",1);b.setDisabled("movebackward",1)}else{b.setDisabled("absolute",0);b.setDisabled("moveforward",!d);b.setDisabled("movebackward",!d);b.setActive("absolute",d&&d.style.position.toLowerCase()=="absolute")}},_visualAid:function(b,d,c){var f=b.dom;tinymce.each(f.select("div,p",d),function(g){if(/^(absolute|relative|fixed)$/i.test(g.style.position)){if(c){f.addClass(g,"mceItemVisualAid")}else{f.removeClass(g,"mceItemVisualAid")}f.addClass(g,"mceItemLayer")}})},_move:function(j){var c=this.editor,g,h=[],f=this._getParentLayer(c.selection.getNode()),e=-1,k=-1,b;b=[];tinymce.walk(c.getBody(),function(d){if(d.nodeType==1&&/^(absolute|relative|static)$/i.test(d.style.position)){b.push(d)}},"childNodes");for(g=0;g-1){b[e].style.zIndex=h[k];b[k].style.zIndex=h[e]}else{if(h[e]>0){b[e].style.zIndex=h[e]-1}}}else{for(g=0;gh[e]){k=g;break}}if(k>-1){b[e].style.zIndex=h[k];b[k].style.zIndex=h[e]}else{b[e].style.zIndex=h[e]+1}}c.execCommand("mceRepaint")},_getParentLayer:function(b){return this.editor.dom.getParent(b,function(c){return c.nodeType==1&&/^(absolute|relative|static)$/i.test(c.style.position)})},_insertLayer:function(){var c=this.editor,e=c.dom,d=e.getPos(e.getParent(c.selection.getNode(),"*")),b=c.getBody();c.dom.add(b,"div",{style:{position:"absolute",left:d.x,top:(d.y>20?d.y:20),width:100,height:100},"class":"mceItemVisualAid mceItemLayer"},c.selection.getContent()||c.getLang("layer.content"));if(tinymce.isIE){e.setHTML(b,b.innerHTML)}},_toggleAbsolute:function(){var b=this.editor,c=this._getParentLayer(b.selection.getNode());if(!c){c=b.dom.getParent(b.selection.getNode(),"DIV,P,IMG")}if(c){if(c.style.position.toLowerCase()=="absolute"){b.dom.setStyles(c,{position:"",left:"",top:"",width:"",height:""});b.dom.removeClass(c,"mceItemVisualAid");b.dom.removeClass(c,"mceItemLayer")}else{if(c.style.left==""){c.style.left=20+"px"}if(c.style.top==""){c.style.top=20+"px"}if(c.style.width==""){c.style.width=c.width?(c.width+"px"):"100px"}if(c.style.height==""){c.style.height=c.height?(c.height+"px"):"100px"}c.style.position="absolute";b.dom.setAttrib(c,"data-mce-style","");b.addVisual(b.getBody())}b.execCommand("mceRepaint");b.nodeChanged()}}});tinymce.PluginManager.add("layer",tinymce.plugins.Layer)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/layer/editor_plugin_src.js b/js/tiny_mce/plugins/layer/editor_plugin_src.js index d5aa8654..d31978bf 100644 --- a/js/tiny_mce/plugins/layer/editor_plugin_src.js +++ b/js/tiny_mce/plugins/layer/editor_plugin_src.js @@ -1,212 +1,262 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.Layer', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - - // Register commands - ed.addCommand('mceInsertLayer', t._insertLayer, t); - - ed.addCommand('mceMoveForward', function() { - t._move(1); - }); - - ed.addCommand('mceMoveBackward', function() { - t._move(-1); - }); - - ed.addCommand('mceMakeAbsolute', function() { - t._toggleAbsolute(); - }); - - // Register buttons - ed.addButton('moveforward', {title : 'layer.forward_desc', cmd : 'mceMoveForward'}); - ed.addButton('movebackward', {title : 'layer.backward_desc', cmd : 'mceMoveBackward'}); - ed.addButton('absolute', {title : 'layer.absolute_desc', cmd : 'mceMakeAbsolute'}); - ed.addButton('insertlayer', {title : 'layer.insertlayer_desc', cmd : 'mceInsertLayer'}); - - ed.onInit.add(function() { - if (tinymce.isIE) - ed.getDoc().execCommand('2D-Position', false, true); - }); - - ed.onNodeChange.add(t._nodeChange, t); - ed.onVisualAid.add(t._visualAid, t); - }, - - getInfo : function() { - return { - longname : 'Layer', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - // Private methods - - _nodeChange : function(ed, cm, n) { - var le, p; - - le = this._getParentLayer(n); - p = ed.dom.getParent(n, 'DIV,P,IMG'); - - if (!p) { - cm.setDisabled('absolute', 1); - cm.setDisabled('moveforward', 1); - cm.setDisabled('movebackward', 1); - } else { - cm.setDisabled('absolute', 0); - cm.setDisabled('moveforward', !le); - cm.setDisabled('movebackward', !le); - cm.setActive('absolute', le && le.style.position.toLowerCase() == "absolute"); - } - }, - - // Private methods - - _visualAid : function(ed, e, s) { - var dom = ed.dom; - - tinymce.each(dom.select('div,p', e), function(e) { - if (/^(absolute|relative|static)$/i.test(e.style.position)) { - if (s) - dom.addClass(e, 'mceItemVisualAid'); - else - dom.removeClass(e, 'mceItemVisualAid'); - } - }); - }, - - _move : function(d) { - var ed = this.editor, i, z = [], le = this._getParentLayer(ed.selection.getNode()), ci = -1, fi = -1, nl; - - nl = []; - tinymce.walk(ed.getBody(), function(n) { - if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position)) - nl.push(n); - }, 'childNodes'); - - // Find z-indexes - for (i=0; i -1) { - nl[ci].style.zIndex = z[fi]; - nl[fi].style.zIndex = z[ci]; - } else { - if (z[ci] > 0) - nl[ci].style.zIndex = z[ci] - 1; - } - } else { - // Move forward - - // Try find a higher one - for (i=0; i z[ci]) { - fi = i; - break; - } - } - - if (fi > -1) { - nl[ci].style.zIndex = z[fi]; - nl[fi].style.zIndex = z[ci]; - } else - nl[ci].style.zIndex = z[ci] + 1; - } - - ed.execCommand('mceRepaint'); - }, - - _getParentLayer : function(n) { - return this.editor.dom.getParent(n, function(n) { - return n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position); - }); - }, - - _insertLayer : function() { - var ed = this.editor, p = ed.dom.getPos(ed.dom.getParent(ed.selection.getNode(), '*')); - - ed.dom.add(ed.getBody(), 'div', { - style : { - position : 'absolute', - left : p.x, - top : (p.y > 20 ? p.y : 20), - width : 100, - height : 100 - }, - 'class' : 'mceItemVisualAid' - }, ed.selection.getContent() || ed.getLang('layer.content')); - }, - - _toggleAbsolute : function() { - var ed = this.editor, le = this._getParentLayer(ed.selection.getNode()); - - if (!le) - le = ed.dom.getParent(ed.selection.getNode(), 'DIV,P,IMG'); - - if (le) { - if (le.style.position.toLowerCase() == "absolute") { - ed.dom.setStyles(le, { - position : '', - left : '', - top : '', - width : '', - height : '' - }); - - ed.dom.removeClass(le, 'mceItemVisualAid'); - } else { - if (le.style.left == "") - le.style.left = 20 + 'px'; - - if (le.style.top == "") - le.style.top = 20 + 'px'; - - if (le.style.width == "") - le.style.width = le.width ? (le.width + 'px') : '100px'; - - if (le.style.height == "") - le.style.height = le.height ? (le.height + 'px') : '100px'; - - le.style.position = "absolute"; - ed.addVisual(ed.getBody()); - } - - ed.execCommand('mceRepaint'); - ed.nodeChanged(); - } - } - }); - - // Register plugin - tinymce.PluginManager.add('layer', tinymce.plugins.Layer); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + function findParentLayer(node) { + do { + if (node.className && node.className.indexOf('mceItemLayer') != -1) { + return node; + } + } while (node = node.parentNode); + }; + + tinymce.create('tinymce.plugins.Layer', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceInsertLayer', t._insertLayer, t); + + ed.addCommand('mceMoveForward', function() { + t._move(1); + }); + + ed.addCommand('mceMoveBackward', function() { + t._move(-1); + }); + + ed.addCommand('mceMakeAbsolute', function() { + t._toggleAbsolute(); + }); + + // Register buttons + ed.addButton('moveforward', {title : 'layer.forward_desc', cmd : 'mceMoveForward'}); + ed.addButton('movebackward', {title : 'layer.backward_desc', cmd : 'mceMoveBackward'}); + ed.addButton('absolute', {title : 'layer.absolute_desc', cmd : 'mceMakeAbsolute'}); + ed.addButton('insertlayer', {title : 'layer.insertlayer_desc', cmd : 'mceInsertLayer'}); + + ed.onInit.add(function() { + var dom = ed.dom; + + if (tinymce.isIE) + ed.getDoc().execCommand('2D-Position', false, true); + }); + + // Remove serialized styles when selecting a layer since it might be changed by a drag operation + ed.onMouseUp.add(function(ed, e) { + var layer = findParentLayer(e.target); + + if (layer) { + ed.dom.setAttrib(layer, 'data-mce-style', ''); + } + }); + + // Fixes edit focus issues with layers on Gecko + // This will enable designMode while inside a layer and disable it when outside + ed.onMouseDown.add(function(ed, e) { + var node = e.target, doc = ed.getDoc(), parent; + + if (tinymce.isGecko) { + if (findParentLayer(node)) { + if (doc.designMode !== 'on') { + doc.designMode = 'on'; + + // Repaint caret + node = doc.body; + parent = node.parentNode; + parent.removeChild(node); + parent.appendChild(node); + } + } else if (doc.designMode == 'on') { + doc.designMode = 'off'; + } + } + }); + + ed.onNodeChange.add(t._nodeChange, t); + ed.onVisualAid.add(t._visualAid, t); + }, + + getInfo : function() { + return { + longname : 'Layer', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + // Private methods + + _nodeChange : function(ed, cm, n) { + var le, p; + + le = this._getParentLayer(n); + p = ed.dom.getParent(n, 'DIV,P,IMG'); + + if (!p) { + cm.setDisabled('absolute', 1); + cm.setDisabled('moveforward', 1); + cm.setDisabled('movebackward', 1); + } else { + cm.setDisabled('absolute', 0); + cm.setDisabled('moveforward', !le); + cm.setDisabled('movebackward', !le); + cm.setActive('absolute', le && le.style.position.toLowerCase() == "absolute"); + } + }, + + // Private methods + + _visualAid : function(ed, e, s) { + var dom = ed.dom; + + tinymce.each(dom.select('div,p', e), function(e) { + if (/^(absolute|relative|fixed)$/i.test(e.style.position)) { + if (s) + dom.addClass(e, 'mceItemVisualAid'); + else + dom.removeClass(e, 'mceItemVisualAid'); + + dom.addClass(e, 'mceItemLayer'); + } + }); + }, + + _move : function(d) { + var ed = this.editor, i, z = [], le = this._getParentLayer(ed.selection.getNode()), ci = -1, fi = -1, nl; + + nl = []; + tinymce.walk(ed.getBody(), function(n) { + if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position)) + nl.push(n); + }, 'childNodes'); + + // Find z-indexes + for (i=0; i -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else { + if (z[ci] > 0) + nl[ci].style.zIndex = z[ci] - 1; + } + } else { + // Move forward + + // Try find a higher one + for (i=0; i z[ci]) { + fi = i; + break; + } + } + + if (fi > -1) { + nl[ci].style.zIndex = z[fi]; + nl[fi].style.zIndex = z[ci]; + } else + nl[ci].style.zIndex = z[ci] + 1; + } + + ed.execCommand('mceRepaint'); + }, + + _getParentLayer : function(n) { + return this.editor.dom.getParent(n, function(n) { + return n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position); + }); + }, + + _insertLayer : function() { + var ed = this.editor, dom = ed.dom, p = dom.getPos(dom.getParent(ed.selection.getNode(), '*')), body = ed.getBody(); + + ed.dom.add(body, 'div', { + style : { + position : 'absolute', + left : p.x, + top : (p.y > 20 ? p.y : 20), + width : 100, + height : 100 + }, + 'class' : 'mceItemVisualAid mceItemLayer' + }, ed.selection.getContent() || ed.getLang('layer.content')); + + // Workaround for IE where it messes up the JS engine if you insert a layer on IE 6,7 + if (tinymce.isIE) + dom.setHTML(body, body.innerHTML); + }, + + _toggleAbsolute : function() { + var ed = this.editor, le = this._getParentLayer(ed.selection.getNode()); + + if (!le) + le = ed.dom.getParent(ed.selection.getNode(), 'DIV,P,IMG'); + + if (le) { + if (le.style.position.toLowerCase() == "absolute") { + ed.dom.setStyles(le, { + position : '', + left : '', + top : '', + width : '', + height : '' + }); + + ed.dom.removeClass(le, 'mceItemVisualAid'); + ed.dom.removeClass(le, 'mceItemLayer'); + } else { + if (le.style.left == "") + le.style.left = 20 + 'px'; + + if (le.style.top == "") + le.style.top = 20 + 'px'; + + if (le.style.width == "") + le.style.width = le.width ? (le.width + 'px') : '100px'; + + if (le.style.height == "") + le.style.height = le.height ? (le.height + 'px') : '100px'; + + le.style.position = "absolute"; + + ed.dom.setAttrib(le, 'data-mce-style', ''); + ed.addVisual(ed.getBody()); + } + + ed.execCommand('mceRepaint'); + ed.nodeChanged(); + } + } + }); + + // Register plugin + tinymce.PluginManager.add('layer', tinymce.plugins.Layer); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/legacyoutput/editor_plugin.js b/js/tiny_mce/plugins/legacyoutput/editor_plugin.js index 33618e84..b3a4ce31 100644 --- a/js/tiny_mce/plugins/legacyoutput/editor_plugin.js +++ b/js/tiny_mce/plugins/legacyoutput/editor_plugin.js @@ -1 +1 @@ -(function(a){a.onAddEditor.addToTop(function(c,b){b.settings.inline_styles=false});a.create("tinymce.plugins.LegacyOutput",{init:function(b){b.onInit.add(function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",e=a.explode(b.settings.font_size_style_values),d=b.serializer;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignfull:{selector:c,attributes:{align:"full"}},bold:{inline:"b"},italic:{inline:"i"},underline:{inline:"u"},strikethrough:{inline:"strike"},fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(f){return a.inArray(e,f.value)+1}}},forecolor:{inline:"font",styles:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}},});d._setup();a.each("b,i,u,strike".split(","),function(f){var g=d.rules[f];if(!g){d.addRules(f)}});if(!d.rules.font){d.addRules("font[face|size|color|style]")}a.each(c.split(","),function(f){var h=d.rules[f],g;if(h){a.each(h.attribs,function(j,i){if(i.name=="align"){g=true;return false}});if(!g){h.attribs.push({name:"align"})}}});b.onNodeChange.add(function(g,k){var j,f,h,i;f=g.dom.getParent(g.selection.getNode(),"font");if(f){h=f.face;i=f.size}if(j=k.get("fontselect")){j.select(function(l){return l==h})}if(j=k.get("fontsizeselect")){j.select(function(m){var l=a.inArray(e,m.fontSize);return l+1==i})}})})},getInfo:function(){return{longname:"LegacyOutput",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput",version:a.majorVersion+"."+a.minorVersion}}});a.PluginManager.add("legacyoutput",a.plugins.LegacyOutput)})(tinymce); \ No newline at end of file +(function(a){a.onAddEditor.addToTop(function(c,b){b.settings.inline_styles=false});a.create("tinymce.plugins.LegacyOutput",{init:function(b){b.onInit.add(function(){var c="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",e=a.explode(b.settings.font_size_style_values),d=b.schema;b.formatter.register({alignleft:{selector:c,attributes:{align:"left"}},aligncenter:{selector:c,attributes:{align:"center"}},alignright:{selector:c,attributes:{align:"right"}},alignfull:{selector:c,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:true}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:true}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(f){return a.inArray(e,f.value)+1}}},forecolor:{inline:"font",styles:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}});a.each("b,i,u,strike".split(","),function(f){d.addValidElements(f+"[*]")});if(!d.getElementRule("font")){d.addValidElements("font[face|size|color|style]")}a.each(c.split(","),function(f){var h=d.getElementRule(f),g;if(h){if(!h.attributes.align){h.attributes.align={};h.attributesOrder.push("align")}}});b.onNodeChange.add(function(g,k){var j,f,h,i;f=g.dom.getParent(g.selection.getNode(),"font");if(f){h=f.face;i=f.size}if(j=k.get("fontselect")){j.select(function(l){return l==h})}if(j=k.get("fontsizeselect")){j.select(function(m){var l=a.inArray(e,m.fontSize);return l+1==i})}})})},getInfo:function(){return{longname:"LegacyOutput",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput",version:a.majorVersion+"."+a.minorVersion}}});a.PluginManager.add("legacyoutput",a.plugins.LegacyOutput)})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/plugins/legacyoutput/editor_plugin_src.js b/js/tiny_mce/plugins/legacyoutput/editor_plugin_src.js index 3673a4dd..835a45c3 100644 --- a/js/tiny_mce/plugins/legacyoutput/editor_plugin_src.js +++ b/js/tiny_mce/plugins/legacyoutput/editor_plugin_src.js @@ -1,136 +1,139 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - * - * This plugin will force TinyMCE to produce deprecated legacy output such as font elements, u elements, align - * attributes and so forth. There are a few cases where these old items might be needed for example in email applications or with Flash - * - * However you should NOT use this plugin if you are building some system that produces web contents such as a CMS. All these elements are - * not apart of the newer specifications for HTML and XHTML. - */ - -(function(tinymce) { - // Override inline_styles setting to force TinyMCE to produce deprecated contents - tinymce.onAddEditor.addToTop(function(tinymce, editor) { - editor.settings.inline_styles = false; - }); - - // Create the legacy ouput plugin - tinymce.create('tinymce.plugins.LegacyOutput', { - init : function(editor) { - editor.onInit.add(function() { - var alignElements = 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', - fontSizes = tinymce.explode(editor.settings.font_size_style_values), - serializer = editor.serializer; - - // Override some internal formats to produce legacy elements and attributes - editor.formatter.register({ - // Change alignment formats to use the deprecated align attribute - alignleft : {selector : alignElements, attributes : {align : 'left'}}, - aligncenter : {selector : alignElements, attributes : {align : 'center'}}, - alignright : {selector : alignElements, attributes : {align : 'right'}}, - alignfull : {selector : alignElements, attributes : {align : 'full'}}, - - // Change the basic formatting elements to use deprecated element types - bold : {inline : 'b'}, - italic : {inline : 'i'}, - underline : {inline : 'u'}, - strikethrough : {inline : 'strike'}, - - // Change font size and font family to use the deprecated font element - fontname : {inline : 'font', attributes : {face : '%value'}}, - fontsize : { - inline : 'font', - attributes : { - size : function(vars) { - return tinymce.inArray(fontSizes, vars.value) + 1; - } - } - }, - - // Setup font elements for colors as well - forecolor : {inline : 'font', styles : {color : '%value'}}, - hilitecolor : {inline : 'font', styles : {backgroundColor : '%value'}}, - }); - - // Force parsing of the serializer rules - serializer._setup(); - - // Check that deprecated elements are allowed if not add them - tinymce.each('b,i,u,strike'.split(','), function(name) { - var rule = serializer.rules[name]; - - if (!rule) - serializer.addRules(name); - }); - - // Add font element if it's missing - if (!serializer.rules["font"]) - serializer.addRules("font[face|size|color|style]"); - - // Add the missing and depreacted align attribute for the serialization engine - tinymce.each(alignElements.split(','), function(name) { - var rule = serializer.rules[name], found; - - if (rule) { - tinymce.each(rule.attribs, function(name, attr) { - if (attr.name == 'align') { - found = true; - return false; - } - }); - - if (!found) - rule.attribs.push({name : 'align'}); - } - }); - - // Listen for the onNodeChange event so that we can do special logic for the font size and font name drop boxes - editor.onNodeChange.add(function(editor, control_manager) { - var control, fontElm, fontName, fontSize; - - // Find font element get it's name and size - fontElm = editor.dom.getParent(editor.selection.getNode(), 'font'); - if (fontElm) { - fontName = fontElm.face; - fontSize = fontElm.size; - } - - // Select/unselect the font name in droplist - if (control = control_manager.get('fontselect')) { - control.select(function(value) { - return value == fontName; - }); - } - - // Select/unselect the font size in droplist - if (control = control_manager.get('fontsizeselect')) { - control.select(function(value) { - var index = tinymce.inArray(fontSizes, value.fontSize); - - return index + 1 == fontSize; - }); - } - }); - }); - }, - - getInfo : function() { - return { - longname : 'LegacyOutput', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('legacyoutput', tinymce.plugins.LegacyOutput); -})(tinymce); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + * + * This plugin will force TinyMCE to produce deprecated legacy output such as font elements, u elements, align + * attributes and so forth. There are a few cases where these old items might be needed for example in email applications or with Flash + * + * However you should NOT use this plugin if you are building some system that produces web contents such as a CMS. All these elements are + * not apart of the newer specifications for HTML and XHTML. + */ + +(function(tinymce) { + // Override inline_styles setting to force TinyMCE to produce deprecated contents + tinymce.onAddEditor.addToTop(function(tinymce, editor) { + editor.settings.inline_styles = false; + }); + + // Create the legacy ouput plugin + tinymce.create('tinymce.plugins.LegacyOutput', { + init : function(editor) { + editor.onInit.add(function() { + var alignElements = 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', + fontSizes = tinymce.explode(editor.settings.font_size_style_values), + schema = editor.schema; + + // Override some internal formats to produce legacy elements and attributes + editor.formatter.register({ + // Change alignment formats to use the deprecated align attribute + alignleft : {selector : alignElements, attributes : {align : 'left'}}, + aligncenter : {selector : alignElements, attributes : {align : 'center'}}, + alignright : {selector : alignElements, attributes : {align : 'right'}}, + alignfull : {selector : alignElements, attributes : {align : 'justify'}}, + + // Change the basic formatting elements to use deprecated element types + bold : [ + {inline : 'b', remove : 'all'}, + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}} + ], + italic : [ + {inline : 'i', remove : 'all'}, + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}} + ], + underline : [ + {inline : 'u', remove : 'all'}, + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true} + ], + strikethrough : [ + {inline : 'strike', remove : 'all'}, + {inline : 'span', styles : {textDecoration: 'line-through'}, exact : true} + ], + + // Change font size and font family to use the deprecated font element + fontname : {inline : 'font', attributes : {face : '%value'}}, + fontsize : { + inline : 'font', + attributes : { + size : function(vars) { + return tinymce.inArray(fontSizes, vars.value) + 1; + } + } + }, + + // Setup font elements for colors as well + forecolor : {inline : 'font', styles : {color : '%value'}}, + hilitecolor : {inline : 'font', styles : {backgroundColor : '%value'}} + }); + + // Check that deprecated elements are allowed if not add them + tinymce.each('b,i,u,strike'.split(','), function(name) { + schema.addValidElements(name + '[*]'); + }); + + // Add font element if it's missing + if (!schema.getElementRule("font")) + schema.addValidElements("font[face|size|color|style]"); + + // Add the missing and depreacted align attribute for the serialization engine + tinymce.each(alignElements.split(','), function(name) { + var rule = schema.getElementRule(name), found; + + if (rule) { + if (!rule.attributes.align) { + rule.attributes.align = {}; + rule.attributesOrder.push('align'); + } + } + }); + + // Listen for the onNodeChange event so that we can do special logic for the font size and font name drop boxes + editor.onNodeChange.add(function(editor, control_manager) { + var control, fontElm, fontName, fontSize; + + // Find font element get it's name and size + fontElm = editor.dom.getParent(editor.selection.getNode(), 'font'); + if (fontElm) { + fontName = fontElm.face; + fontSize = fontElm.size; + } + + // Select/unselect the font name in droplist + if (control = control_manager.get('fontselect')) { + control.select(function(value) { + return value == fontName; + }); + } + + // Select/unselect the font size in droplist + if (control = control_manager.get('fontsizeselect')) { + control.select(function(value) { + var index = tinymce.inArray(fontSizes, value.fontSize); + + return index + 1 == fontSize; + }); + } + }); + }); + }, + + getInfo : function() { + return { + longname : 'LegacyOutput', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/legacyoutput', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('legacyoutput', tinymce.plugins.LegacyOutput); +})(tinymce); diff --git a/js/tiny_mce/plugins/lists/editor_plugin.js b/js/tiny_mce/plugins/lists/editor_plugin.js new file mode 100644 index 00000000..86b8b1a4 --- /dev/null +++ b/js/tiny_mce/plugins/lists/editor_plugin.js @@ -0,0 +1 @@ +(function(){var e=tinymce.each,r=tinymce.dom.Event,g;function p(t,s){while(t&&(t.nodeType===8||(t.nodeType===3&&/^[ \t\n\r]*$/.test(t.nodeValue)))){t=s(t)}return t}function b(s){return p(s,function(t){return t.previousSibling})}function i(s){return p(s,function(t){return t.nextSibling})}function d(s,u,t){return s.dom.getParent(u,function(v){return tinymce.inArray(t,v)!==-1})}function n(s){return s&&(s.tagName==="OL"||s.tagName==="UL")}function c(u,v){var t,w,s;t=b(u.lastChild);while(n(t)){w=t;t=b(w.previousSibling)}if(w){s=v.create("li",{style:"list-style-type: none;"});v.split(u,w);v.insertAfter(s,w);s.appendChild(w);s.appendChild(w);u=s.previousSibling}return u}function m(t,s,u){t=a(t,s,u);return o(t,s,u)}function a(u,s,v){var t=b(u.previousSibling);if(t){return h(t,u,s?t:false,v)}else{return u}}function o(u,t,v){var s=i(u.nextSibling);if(s){return h(u,s,t?s:false,v)}else{return u}}function h(u,s,t,v){if(l(u,s,!!t,v)){return f(u,s,t)}else{if(u&&u.tagName==="LI"&&n(s)){u.appendChild(s)}}return s}function l(u,t,s,v){if(!u||!t){return false}else{if(u.tagName==="LI"&&t.tagName==="LI"){return t.style.listStyleType==="none"||j(t)}else{if(n(u)){return(u.tagName===t.tagName&&(s||u.style.listStyleType===t.style.listStyleType))||q(t)}else{return v&&u.tagName==="P"&&t.tagName==="P"}}}}function q(t){var s=i(t.firstChild),u=b(t.lastChild);return s&&u&&n(t)&&s===u&&(n(s)||s.style.listStyleType==="none"||j(s))}function j(u){var t=i(u.firstChild),s=b(u.lastChild);return t&&s&&t===s&&n(t)}function f(w,v,s){var u=b(w.lastChild),t=i(v.firstChild);if(w.tagName==="P"){w.appendChild(w.ownerDocument.createElement("br"))}while(v.firstChild){w.appendChild(v.firstChild)}if(s){w.style.listStyleType=s.style.listStyleType}v.parentNode.removeChild(v);h(u,t,false);return w}function k(t,u){var s;if(!u.is(t,"li,ol,ul")){s=u.getParent(t,"li");if(s){t=s}}return t}tinymce.create("tinymce.plugins.Lists",{init:function(y){var v="TABBING";var s="EMPTY";var I="ESCAPE";var z="PARAGRAPH";var M="UNKNOWN";var x=M;function E(T){return T.keyCode===tinymce.VK.TAB&&!(T.altKey||T.ctrlKey)&&(y.queryCommandState("InsertUnorderedList")||y.queryCommandState("InsertOrderedList"))}function D(){var T=y.selection.getRng();var U=T.startContainer;if(U.nodeType==3){return(T.endOffset==U.nodeValue.length)}else{if(U.nodeType==1){return T.endOffset==U.childNodes.length}}return false}function N(){var U=y.selection.getNode();var T=U.tagName==="P"&&U.parentNode.tagName==="LI"&&U.parentNode.lastChild===U;return y.selection.isCollapsed()&&T&&D()}function w(){var T=B();var V=T.parentNode.parentNode;var U=T.parentNode.lastChild===T;return U&&!t(V)&&O(T)}function t(T){if(n(T)){return T.parentNode&&T.parentNode.tagName==="LI"}else{return T.tagName==="LI"}}function F(){return y.selection.isCollapsed()&&O(B())}function B(){var T=y.selection.getStart();return((T.tagName=="BR"||T.tagName=="")&&T.parentNode.tagName=="LI")?T.parentNode:T}function O(T){var U=T.childNodes.length;if(T.tagName==="LI"){return U==0?true:U==1&&(T.firstChild.tagName==""||T.firstChild.tagName=="BR"||H(T))}return false}function H(T){var U=tinymce.grep(T.parentNode.childNodes,function(X){return X.tagName=="LI"});var V=T==U[U.length-1];var W=T.firstChild;return tinymce.isIE9&&V&&(W.nodeValue==String.fromCharCode(160)||W.nodeValue==String.fromCharCode(32))}function S(T){return T.keyCode===tinymce.VK.ENTER}function A(T){return S(T)&&!T.shiftKey}function L(T){if(E(T)){return v}else{if(A(T)&&w()){return I}else{if(A(T)&&F()){return s}else{if(A(T)&&N()){return z}else{return M}}}}}function C(T,U){if(x==v||x==s||tinymce.isGecko&&x==I){r.cancel(U)}}function J(V,X){if(x==z){var W=V.selection.getNode();var U=V.dom.create("li");var T=V.dom.getParent(W,"li");V.dom.insertAfter(U,T);if(tinyMCE.isIE8){U.appendChild(V.dom.create(" "));V.selection.setCursorLocation(U,1)}else{if(tinyMCE.isGecko){setTimeout(function(){var Y=V.getDoc().createTextNode("\uFEFF");U.appendChild(Y);V.selection.setCursorLocation(U,0)},0)}else{V.selection.setCursorLocation(U,0)}}r.cancel(X)}}function u(W,Y){var ab;if(!tinymce.isGecko){return}var U=W.selection.getStart();if(Y.keyCode!=tinymce.VK.BACKSPACE||U.tagName!=="IMG"){return}function V(af){var ag=af.firstChild;var ae=null;do{if(!ag){break}if(ag.tagName==="LI"){ae=ag}}while(ag=ag.nextSibling);return ae}function ad(af,ae){while(af.childNodes.length>0){ae.appendChild(af.childNodes[0])}}ab=U.parentNode.previousSibling;if(!ab){return}var Z;if(ab.tagName==="UL"||ab.tagName==="OL"){Z=ab}else{if(ab.previousSibling&&(ab.previousSibling.tagName==="UL"||ab.previousSibling.tagName==="OL")){Z=ab.previousSibling}else{return}}var ac=V(Z);var T=W.dom.createRng();T.setStart(ac,1);T.setEnd(ac,1);W.selection.setRng(T);W.selection.collapse(true);var X=W.selection.getBookmark();var aa=U.parentNode.cloneNode(true);if(aa.tagName==="P"||aa.tagName==="DIV"){ad(aa,ac)}else{ac.appendChild(aa)}U.parentNode.parentNode.removeChild(U.parentNode);W.selection.moveToBookmark(X)}function G(T){var U=y.dom.getParent(T,"ol,ul");if(U!=null){var V=U.lastChild;V.appendChild(y.getDoc().createElement(""));y.selection.setCursorLocation(V,0)}}this.ed=y;y.addCommand("Indent",this.indent,this);y.addCommand("Outdent",this.outdent,this);y.addCommand("InsertUnorderedList",function(){this.applyList("UL","OL")},this);y.addCommand("InsertOrderedList",function(){this.applyList("OL","UL")},this);y.onInit.add(function(){y.editorCommands.addCommands({outdent:function(){var U=y.selection,V=y.dom;function T(W){W=V.getParent(W,V.isBlock);return W&&(parseInt(y.dom.getStyle(W,"margin-left")||0,10)+parseInt(y.dom.getStyle(W,"padding-left")||0,10))>0}return T(U.getStart())||T(U.getEnd())||y.queryCommandState("InsertOrderedList")||y.queryCommandState("InsertUnorderedList")}},"state")});y.onKeyUp.add(function(U,V){if(x==v){U.execCommand(V.shiftKey?"Outdent":"Indent",true,null);x=M;return r.cancel(V)}else{if(x==s){var T=B();var X=U.settings.list_outdent_on_enter===true||V.shiftKey;U.execCommand(X?"Outdent":"Indent",true,null);if(tinymce.isIE){G(T)}return r.cancel(V)}else{if(x==I){if(tinymce.isIE8){var W=U.getDoc().createTextNode("\uFEFF");U.selection.getNode().appendChild(W)}else{if(tinymce.isIE9||tinymce.isGecko){U.execCommand("Outdent");return r.cancel(V)}}}}}});function K(U,T){var V=y.getDoc().createTextNode("\uFEFF");U.insertBefore(V,T);y.selection.setCursorLocation(V,0);y.execCommand("mceRepaint")}function Q(U,W){if(S(W)){var T=B();if(T){var V=T.parentNode;var X=V&&V.parentNode;if(X&&X.nodeName=="LI"&&X.firstChild==V&&T==V.firstChild){K(X,V)}}}}function R(U,W){if(S(W)){var T=B();if(U.dom.select("ul li",T).length===1){var V=T.firstChild;K(T,V)}}}function P(U,Y){function V(ac,Z){var ab=[];var ad=new tinymce.dom.TreeWalker(Z,ac);for(var aa=ad.current();aa;aa=ad.next()){if(U.dom.is(aa,"ol,ul,li")){ab.push(aa)}}return ab}if(Y.keyCode==tinymce.VK.BACKSPACE){var T=B();if(T){var X=U.dom.getParent(T,"ol,ul");if(X&&X.firstChild===T){var W=V(X,T);U.execCommand("Outdent",false,W);U.undoManager.add();return r.cancel(Y)}}}}y.onKeyDown.add(function(T,U){x=L(U)});y.onKeyDown.add(C);y.onKeyDown.add(u);y.onKeyDown.add(J);if(tinymce.isGecko){y.onKeyUp.add(Q)}if(tinymce.isIE8){y.onKeyUp.add(R)}if(tinymce.isGecko||tinymce.isWebKit){y.onKeyDown.add(P)}},applyList:function(y,v){var C=this,z=C.ed,I=z.dom,s=[],H=false,u=false,w=false,B,G=z.selection.getSelectedBlocks();function E(t){if(t&&t.tagName==="BR"){I.remove(t)}}function F(M){var N=I.create(y),t;function L(O){if(O.style.marginLeft||O.style.paddingLeft){C.adjustPaddingFunction(false)(O)}}if(M.tagName==="LI"){}else{if(M.tagName==="P"||M.tagName==="DIV"||M.tagName==="BODY"){K(M,function(P,O){J(P,O,M.tagName==="BODY"?null:P.parentNode);t=P.parentNode;L(t);E(O)});if(t){if(t.tagName==="LI"&&(M.tagName==="P"||G.length>1)){I.split(t.parentNode.parentNode,t.parentNode)}m(t.parentNode,true)}return}else{t=I.create("li");I.insertAfter(t,M);t.appendChild(M);L(M);M=t}}I.insertAfter(N,M);N.appendChild(M);m(N,true);s.push(M)}function J(P,L,N){var t,O=P,M;while(!I.isBlock(P.parentNode)&&P.parentNode!==I.getRoot()){P=I.split(P.parentNode,P.previousSibling);P=P.nextSibling;O=P}if(N){t=N.cloneNode(true);P.parentNode.insertBefore(t,P);while(t.firstChild){I.remove(t.firstChild)}t=I.rename(t,"li")}else{t=I.create("li");P.parentNode.insertBefore(t,P)}while(O&&O!=L){M=O.nextSibling;t.appendChild(O);O=M}if(t.childNodes.length===0){t.innerHTML='
    '}F(t)}function K(Q,T){var N,R,O=3,L=1,t="br,ul,ol,p,div,h1,h2,h3,h4,h5,h6,table,blockquote,address,pre,form,center,dl";function P(X,U){var V=I.createRng(),W;g.keep=true;z.selection.moveToBookmark(g);g.keep=false;W=z.selection.getRng(true);if(!U){U=X.parentNode.lastChild}V.setStartBefore(X);V.setEndAfter(U);return !(V.compareBoundaryPoints(O,W)>0||V.compareBoundaryPoints(L,W)<=0)}function S(U){if(U.nextSibling){return U.nextSibling}if(!I.isBlock(U.parentNode)&&U.parentNode!==I.getRoot()){return S(U.parentNode)}}N=Q.firstChild;var M=false;e(I.select(t,Q),function(U){if(U.hasAttribute&&U.hasAttribute("_mce_bogus")){return true}if(P(N,U)){I.addClass(U,"_mce_tagged_br");N=S(U)}});M=(N&&P(N,undefined));N=Q.firstChild;e(I.select(t,Q),function(V){var U=S(V);if(V.hasAttribute&&V.hasAttribute("_mce_bogus")){return true}if(I.hasClass(V,"_mce_tagged_br")){T(N,V,R);R=null}else{R=V}N=U});if(M){T(N,undefined,R)}}function D(t){K(t,function(M,L,N){J(M,L);E(L);E(N)})}function A(t){if(tinymce.inArray(s,t)!==-1){return}if(t.parentNode.tagName===v){I.split(t.parentNode,t);F(t);o(t.parentNode,false)}s.push(t)}function x(M){var O,N,L,t;if(tinymce.inArray(s,M)!==-1){return}M=c(M,I);while(I.is(M.parentNode,"ol,ul,li")){I.split(M.parentNode,M)}s.push(M);M=I.rename(M,"p");L=m(M,false,z.settings.force_br_newlines);if(L===M){O=M.firstChild;while(O){if(I.isBlock(O)){O=I.split(O.parentNode,O);t=true;N=O.nextSibling&&O.nextSibling.firstChild}else{N=O.nextSibling;if(t&&O.tagName==="BR"){I.remove(O)}t=false}O=N}}}e(G,function(t){t=k(t,I);if(t.tagName===v||(t.tagName==="LI"&&t.parentNode.tagName===v)){u=true}else{if(t.tagName===y||(t.tagName==="LI"&&t.parentNode.tagName===y)){H=true}else{w=true}}});if(w&&!H||u||G.length===0){B={LI:A,H1:F,H2:F,H3:F,H4:F,H5:F,H6:F,P:F,BODY:F,DIV:G.length>1?F:D,defaultAction:D,elements:this.selectedBlocks()}}else{B={defaultAction:x,elements:this.selectedBlocks()}}this.process(B)},indent:function(){var u=this.ed,w=u.dom,x=[];function s(z){var y=w.create("li",{style:"list-style-type: none;"});w.insertAfter(y,z);return y}function t(B){var y=s(B),D=w.getParent(B,"ol,ul"),C=D.tagName,E=w.getStyle(D,"list-style-type"),A={},z;if(E!==""){A.style="list-style-type: "+E+";"}z=w.create(C,A);y.appendChild(z);return z}function v(z){if(!d(u,z,x)){z=c(z,w);var y=t(z);y.appendChild(z);m(y.parentNode,false);m(y,false);x.push(z)}}this.process({LI:v,defaultAction:this.adjustPaddingFunction(true),elements:this.selectedBlocks()})},outdent:function(y,x){var w=this,u=w.ed,z=u.dom,s=[];function A(t){var C,B,D;if(!d(u,t,s)){if(z.getStyle(t,"margin-left")!==""||z.getStyle(t,"padding-left")!==""){return w.adjustPaddingFunction(false)(t)}D=z.getStyle(t,"text-align",true);if(D==="center"||D==="right"){z.setStyle(t,"text-align","left");return}t=c(t,z);C=t.parentNode;B=t.parentNode.parentNode;if(B.tagName==="P"){z.split(B,t.parentNode)}else{z.split(C,t);if(B.tagName==="LI"){z.split(B,t)}else{if(!z.is(B,"ol,ul")){z.rename(t,"p")}}}s.push(t)}}var v=x&&tinymce.is(x,"array")?x:this.selectedBlocks();this.process({LI:A,defaultAction:this.adjustPaddingFunction(false),elements:v});e(s,m)},process:function(y){var F=this,w=F.ed.selection,z=F.ed.dom,E,u;function B(t){var s=tinymce.grep(t.childNodes,function(H){return !(H.nodeName==="BR"||H.nodeName==="SPAN"&&z.getAttrib(H,"data-mce-type")=="bookmark"||H.nodeType==3&&(H.nodeValue==String.fromCharCode(160)||H.nodeValue==""))});return s.length===0}function x(s){z.removeClass(s,"_mce_act_on");if(!s||s.nodeType!==1||E.length>1&&B(s)){return}s=k(s,z);var t=y[s.tagName];if(!t){t=y.defaultAction}t(s)}function v(s){F.splitSafeEach(s.childNodes,x)}function C(s,t){return t>=0&&s.hasChildNodes()&&t0){t=s.shift();w.removeClass(t,"_mce_act_on");u(t);s=w.select("._mce_act_on")}},adjustPaddingFunction:function(u){var s,v,t=this.ed;s=t.settings.indentation;v=/[a-z%]+/i.exec(s);s=parseInt(s,10);return function(w){var y,x;y=parseInt(t.dom.getStyle(w,"margin-left")||0,10)+parseInt(t.dom.getStyle(w,"padding-left")||0,10);if(u){x=y+s}else{x=y-s}t.dom.setStyle(w,"padding-left","");t.dom.setStyle(w,"margin-left",x>0?x+v:"")}},selectedBlocks:function(){var s=this.ed;var t=s.selection.getSelectedBlocks();return t.length==0?[s.dom.getRoot()]:t},getInfo:function(){return{longname:"Lists",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/lists",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("lists",tinymce.plugins.Lists)}()); \ No newline at end of file diff --git a/js/tiny_mce/plugins/lists/editor_plugin_src.js b/js/tiny_mce/plugins/lists/editor_plugin_src.js new file mode 100644 index 00000000..a3bd16ca --- /dev/null +++ b/js/tiny_mce/plugins/lists/editor_plugin_src.js @@ -0,0 +1,925 @@ +/** + * editor_plugin_src.js + * + * Copyright 2011, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, Event = tinymce.dom.Event, bookmark; + + // Skips text nodes that only contain whitespace since they aren't semantically important. + function skipWhitespaceNodes(e, next) { + while (e && (e.nodeType === 8 || (e.nodeType === 3 && /^[ \t\n\r]*$/.test(e.nodeValue)))) { + e = next(e); + } + return e; + } + + function skipWhitespaceNodesBackwards(e) { + return skipWhitespaceNodes(e, function(e) { + return e.previousSibling; + }); + } + + function skipWhitespaceNodesForwards(e) { + return skipWhitespaceNodes(e, function(e) { + return e.nextSibling; + }); + } + + function hasParentInList(ed, e, list) { + return ed.dom.getParent(e, function(p) { + return tinymce.inArray(list, p) !== -1; + }); + } + + function isList(e) { + return e && (e.tagName === 'OL' || e.tagName === 'UL'); + } + + function splitNestedLists(element, dom) { + var tmp, nested, wrapItem; + tmp = skipWhitespaceNodesBackwards(element.lastChild); + while (isList(tmp)) { + nested = tmp; + tmp = skipWhitespaceNodesBackwards(nested.previousSibling); + } + if (nested) { + wrapItem = dom.create('li', { style: 'list-style-type: none;'}); + dom.split(element, nested); + dom.insertAfter(wrapItem, nested); + wrapItem.appendChild(nested); + wrapItem.appendChild(nested); + element = wrapItem.previousSibling; + } + return element; + } + + function attemptMergeWithAdjacent(e, allowDifferentListStyles, mergeParagraphs) { + e = attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs); + return attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs); + } + + function attemptMergeWithPrevious(e, allowDifferentListStyles, mergeParagraphs) { + var prev = skipWhitespaceNodesBackwards(e.previousSibling); + if (prev) { + return attemptMerge(prev, e, allowDifferentListStyles ? prev : false, mergeParagraphs); + } else { + return e; + } + } + + function attemptMergeWithNext(e, allowDifferentListStyles, mergeParagraphs) { + var next = skipWhitespaceNodesForwards(e.nextSibling); + if (next) { + return attemptMerge(e, next, allowDifferentListStyles ? next : false, mergeParagraphs); + } else { + return e; + } + } + + function attemptMerge(e1, e2, differentStylesMasterElement, mergeParagraphs) { + if (canMerge(e1, e2, !!differentStylesMasterElement, mergeParagraphs)) { + return merge(e1, e2, differentStylesMasterElement); + } else if (e1 && e1.tagName === 'LI' && isList(e2)) { + // Fix invalidly nested lists. + e1.appendChild(e2); + } + return e2; + } + + function canMerge(e1, e2, allowDifferentListStyles, mergeParagraphs) { + if (!e1 || !e2) { + return false; + } else if (e1.tagName === 'LI' && e2.tagName === 'LI') { + return e2.style.listStyleType === 'none' || containsOnlyAList(e2); + } else if (isList(e1)) { + return (e1.tagName === e2.tagName && (allowDifferentListStyles || e1.style.listStyleType === e2.style.listStyleType)) || isListForIndent(e2); + } else return mergeParagraphs && e1.tagName === 'P' && e2.tagName === 'P'; + } + + function isListForIndent(e) { + var firstLI = skipWhitespaceNodesForwards(e.firstChild), lastLI = skipWhitespaceNodesBackwards(e.lastChild); + return firstLI && lastLI && isList(e) && firstLI === lastLI && (isList(firstLI) || firstLI.style.listStyleType === 'none' || containsOnlyAList(firstLI)); + } + + function containsOnlyAList(e) { + var firstChild = skipWhitespaceNodesForwards(e.firstChild), lastChild = skipWhitespaceNodesBackwards(e.lastChild); + return firstChild && lastChild && firstChild === lastChild && isList(firstChild); + } + + function merge(e1, e2, masterElement) { + var lastOriginal = skipWhitespaceNodesBackwards(e1.lastChild), firstNew = skipWhitespaceNodesForwards(e2.firstChild); + if (e1.tagName === 'P') { + e1.appendChild(e1.ownerDocument.createElement('br')); + } + while (e2.firstChild) { + e1.appendChild(e2.firstChild); + } + if (masterElement) { + e1.style.listStyleType = masterElement.style.listStyleType; + } + e2.parentNode.removeChild(e2); + attemptMerge(lastOriginal, firstNew, false); + return e1; + } + + function findItemToOperateOn(e, dom) { + var item; + if (!dom.is(e, 'li,ol,ul')) { + item = dom.getParent(e, 'li'); + if (item) { + e = item; + } + } + return e; + } + + tinymce.create('tinymce.plugins.Lists', { + init: function(ed) { + var LIST_TABBING = 'TABBING'; + var LIST_EMPTY_ITEM = 'EMPTY'; + var LIST_ESCAPE = 'ESCAPE'; + var LIST_PARAGRAPH = 'PARAGRAPH'; + var LIST_UNKNOWN = 'UNKNOWN'; + var state = LIST_UNKNOWN; + + function isTabInList(e) { + // Don't indent on Ctrl+Tab or Alt+Tab + return e.keyCode === tinymce.VK.TAB && !(e.altKey || e.ctrlKey) && + (ed.queryCommandState('InsertUnorderedList') || ed.queryCommandState('InsertOrderedList')); + } + + function isCursorAtEndOfContainer() { + var range = ed.selection.getRng(); + var startContainer = range.startContainer; + if (startContainer.nodeType == 3) { + return (range.endOffset == startContainer.nodeValue.length); + } else if (startContainer.nodeType == 1) { + return range.endOffset == startContainer.childNodes.length; + } + return false; + } + + // If we are at the end of a paragraph in a list item, pressing enter should create a new list item instead of a new paragraph. + function isEndOfParagraph() { + var node = ed.selection.getNode(); + var isLastParagraphOfLi = node.tagName === 'P' && node.parentNode.tagName === 'LI' && node.parentNode.lastChild === node; + return ed.selection.isCollapsed() && isLastParagraphOfLi && isCursorAtEndOfContainer(); + } + + function isOnLastListItem() { + var li = getLi(); + var grandParent = li.parentNode.parentNode; + var isLastItem = li.parentNode.lastChild === li; + return isLastItem && !isNestedList(grandParent) && isEmptyListItem(li); + } + + function isNestedList(grandParent) { + if (isList(grandParent)) { + return grandParent.parentNode && grandParent.parentNode.tagName === 'LI'; + } else { + return grandParent.tagName === 'LI'; + } + } + + function isInEmptyListItem() { + return ed.selection.isCollapsed() && isEmptyListItem(getLi()); + } + + function getLi() { + var n = ed.selection.getStart(); + // Get start will return BR if the LI only contains a BR or an empty element as we use these to fix caret position + return ((n.tagName == 'BR' || n.tagName == '') && n.parentNode.tagName == 'LI') ? n.parentNode : n; + } + + function isEmptyListItem(li) { + var numChildren = li.childNodes.length; + if (li.tagName === 'LI') { + return numChildren == 0 ? true : numChildren == 1 && (li.firstChild.tagName == '' || li.firstChild.tagName == 'BR' || isEmptyIE9Li(li)); + } + return false; + } + + function isEmptyIE9Li(li) { + // only consider this to be last item if there is no list item content or that content is nbsp or space since IE9 creates these + var lis = tinymce.grep(li.parentNode.childNodes, function(n) {return n.tagName == 'LI'}); + var isLastLi = li == lis[lis.length - 1]; + var child = li.firstChild; + return tinymce.isIE9 && isLastLi && (child.nodeValue == String.fromCharCode(160) || child.nodeValue == String.fromCharCode(32)); + } + + function isEnter(e) { + return e.keyCode === tinymce.VK.ENTER; + } + + function isEnterWithoutShift(e) { + return isEnter(e) && !e.shiftKey; + } + + function getListKeyState(e) { + if (isTabInList(e)) { + return LIST_TABBING; + } else if (isEnterWithoutShift(e) && isOnLastListItem()) { + return LIST_ESCAPE; + } else if (isEnterWithoutShift(e) && isInEmptyListItem()) { + return LIST_EMPTY_ITEM; + } else if (isEnterWithoutShift(e) && isEndOfParagraph()) { + return LIST_PARAGRAPH; + } else { + return LIST_UNKNOWN; + } + } + + function cancelDefaultEvents(ed, e) { + // list escape is done manually using outdent as it does not create paragraphs correctly in td's + if (state == LIST_TABBING || state == LIST_EMPTY_ITEM || tinymce.isGecko && state == LIST_ESCAPE) { + Event.cancel(e); + } + } + + // Creates a new list item after the current selection's list item parent + function createNewLi(ed, e) { + if (state == LIST_PARAGRAPH) { + var node = ed.selection.getNode(); + var li = ed.dom.create("li"); + var parentLi = ed.dom.getParent(node, 'li'); + ed.dom.insertAfter(li, parentLi); + + // Move caret to new list element. + if (tinyMCE.isIE8) { + li.appendChild(ed.dom.create(" ")); // IE needs an element within the bullet point + ed.selection.setCursorLocation(li, 1); + } else if (tinyMCE.isGecko) { + // This setTimeout is a hack as FF behaves badly if there is no content after the bullet point + setTimeout(function () { + var n = ed.getDoc().createTextNode('\uFEFF'); + li.appendChild(n); + ed.selection.setCursorLocation(li, 0); + }, 0); + } else { + ed.selection.setCursorLocation(li, 0); + } + Event.cancel(e); + } + } + + function imageJoiningListItem(ed, e) { + var prevSibling; + + if (!tinymce.isGecko) + return; + + var n = ed.selection.getStart(); + if (e.keyCode != tinymce.VK.BACKSPACE || n.tagName !== 'IMG') + return; + + function lastLI(node) { + var child = node.firstChild; + var li = null; + do { + if (!child) + break; + + if (child.tagName === 'LI') + li = child; + } while (child = child.nextSibling); + + return li; + } + + function addChildren(parentNode, destination) { + while (parentNode.childNodes.length > 0) + destination.appendChild(parentNode.childNodes[0]); + } + + // Check if there is a previous sibling + prevSibling = n.parentNode.previousSibling; + if (!prevSibling) + return; + + var ul; + if (prevSibling.tagName === 'UL' || prevSibling.tagName === 'OL') + ul = prevSibling; + else if (prevSibling.previousSibling && (prevSibling.previousSibling.tagName === 'UL' || prevSibling.previousSibling.tagName === 'OL')) + ul = prevSibling.previousSibling; + else + return; + + var li = lastLI(ul); + + // move the caret to the end of the list item + var rng = ed.dom.createRng(); + rng.setStart(li, 1); + rng.setEnd(li, 1); + ed.selection.setRng(rng); + ed.selection.collapse(true); + + // save a bookmark at the end of the list item + var bookmark = ed.selection.getBookmark(); + + // copy the image an its text to the list item + var clone = n.parentNode.cloneNode(true); + if (clone.tagName === 'P' || clone.tagName === 'DIV') + addChildren(clone, li); + else + li.appendChild(clone); + + // remove the old copy of the image + n.parentNode.parentNode.removeChild(n.parentNode); + + // move the caret where we saved the bookmark + ed.selection.moveToBookmark(bookmark); + } + + // fix the cursor position to ensure it is correct in IE + function setCursorPositionToOriginalLi(li) { + var list = ed.dom.getParent(li, 'ol,ul'); + if (list != null) { + var lastLi = list.lastChild; + lastLi.appendChild(ed.getDoc().createElement('')); + ed.selection.setCursorLocation(lastLi, 0); + } + } + + this.ed = ed; + ed.addCommand('Indent', this.indent, this); + ed.addCommand('Outdent', this.outdent, this); + ed.addCommand('InsertUnorderedList', function() { + this.applyList('UL', 'OL'); + }, this); + ed.addCommand('InsertOrderedList', function() { + this.applyList('OL', 'UL'); + }, this); + + ed.onInit.add(function() { + ed.editorCommands.addCommands({ + 'outdent': function() { + var sel = ed.selection, dom = ed.dom; + + function hasStyleIndent(n) { + n = dom.getParent(n, dom.isBlock); + return n && (parseInt(ed.dom.getStyle(n, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(n, 'padding-left') || 0, 10)) > 0; + } + + return hasStyleIndent(sel.getStart()) || hasStyleIndent(sel.getEnd()) || ed.queryCommandState('InsertOrderedList') || ed.queryCommandState('InsertUnorderedList'); + } + }, 'state'); + }); + + ed.onKeyUp.add(function(ed, e) { + if (state == LIST_TABBING) { + ed.execCommand(e.shiftKey ? 'Outdent' : 'Indent', true, null); + state = LIST_UNKNOWN; + return Event.cancel(e); + } else if (state == LIST_EMPTY_ITEM) { + var li = getLi(); + var shouldOutdent = ed.settings.list_outdent_on_enter === true || e.shiftKey; + ed.execCommand(shouldOutdent ? 'Outdent' : 'Indent', true, null); + if (tinymce.isIE) { + setCursorPositionToOriginalLi(li); + } + + return Event.cancel(e); + } else if (state == LIST_ESCAPE) { + if (tinymce.isIE8) { + // append a zero sized nbsp so that caret is positioned correctly in IE8 after escaping and applying formatting. + // if there is no text then applying formatting for e.g a H1 to the P tag immediately following list after + // escaping from it will cause the caret to be positioned on the last li instead of staying the in P tag. + var n = ed.getDoc().createTextNode('\uFEFF'); + ed.selection.getNode().appendChild(n); + } else if (tinymce.isIE9 || tinymce.isGecko) { + // IE9 does not escape the list so we use outdent to do this and cancel the default behaviour + // Gecko does not create a paragraph outdenting inside a TD so default behaviour is cancelled and we outdent ourselves + ed.execCommand('Outdent'); + return Event.cancel(e); + } + } + }); + + function fixListItem(parent, reference) { + // a zero-sized non-breaking space is placed in the empty list item so that the nested list is + // displayed on the below line instead of next to it + var n = ed.getDoc().createTextNode('\uFEFF'); + parent.insertBefore(n, reference); + ed.selection.setCursorLocation(n, 0); + // repaint to remove rendering artifact. only visible when creating new list + ed.execCommand('mceRepaint'); + } + + function fixIndentedListItemForGecko(ed, e) { + if (isEnter(e)) { + var li = getLi(); + if (li) { + var parent = li.parentNode; + var grandParent = parent && parent.parentNode; + if (grandParent && grandParent.nodeName == 'LI' && grandParent.firstChild == parent && li == parent.firstChild) { + fixListItem(grandParent, parent); + } + } + } + } + + function fixIndentedListItemForIE8(ed, e) { + if (isEnter(e)) { + var li = getLi(); + if (ed.dom.select('ul li', li).length === 1) { + var list = li.firstChild; + fixListItem(li, list); + } + } + } + + function fixDeletingFirstCharOfList(ed, e) { + function listElements(list, li) { + var elements = []; + var walker = new tinymce.dom.TreeWalker(li, list); + for (var node = walker.current(); node; node = walker.next()) { + if (ed.dom.is(node, 'ol,ul,li')) { + elements.push(node); + } + } + return elements; + } + + if (e.keyCode == tinymce.VK.BACKSPACE) { + var li = getLi(); + if (li) { + var list = ed.dom.getParent(li, 'ol,ul'); + if (list && list.firstChild === li) { + var elements = listElements(list, li); + ed.execCommand("Outdent", false, elements); + ed.undoManager.add(); + return Event.cancel(e); + } + } + } + } + + ed.onKeyDown.add(function(_, e) { state = getListKeyState(e); }); + ed.onKeyDown.add(cancelDefaultEvents); + ed.onKeyDown.add(imageJoiningListItem); + ed.onKeyDown.add(createNewLi); + + if (tinymce.isGecko) { + ed.onKeyUp.add(fixIndentedListItemForGecko); + } + if (tinymce.isIE8) { + ed.onKeyUp.add(fixIndentedListItemForIE8); + } + if (tinymce.isGecko || tinymce.isWebKit) { + ed.onKeyDown.add(fixDeletingFirstCharOfList); + } + }, + + applyList: function(targetListType, oppositeListType) { + var t = this, ed = t.ed, dom = ed.dom, applied = [], hasSameType = false, hasOppositeType = false, hasNonList = false, actions, + selectedBlocks = ed.selection.getSelectedBlocks(); + + function cleanupBr(e) { + if (e && e.tagName === 'BR') { + dom.remove(e); + } + } + + function makeList(element) { + var list = dom.create(targetListType), li; + + function adjustIndentForNewList(element) { + // If there's a margin-left, outdent one level to account for the extra list margin. + if (element.style.marginLeft || element.style.paddingLeft) { + t.adjustPaddingFunction(false)(element); + } + } + + if (element.tagName === 'LI') { + // No change required. + } else if (element.tagName === 'P' || element.tagName === 'DIV' || element.tagName === 'BODY') { + processBrs(element, function(startSection, br) { + doWrapList(startSection, br, element.tagName === 'BODY' ? null : startSection.parentNode); + li = startSection.parentNode; + adjustIndentForNewList(li); + cleanupBr(br); + }); + if (li) { + if (li.tagName === 'LI' && (element.tagName === 'P' || selectedBlocks.length > 1)) { + dom.split(li.parentNode.parentNode, li.parentNode); + } + attemptMergeWithAdjacent(li.parentNode, true); + } + return; + } else { + // Put the list around the element. + li = dom.create('li'); + dom.insertAfter(li, element); + li.appendChild(element); + adjustIndentForNewList(element); + element = li; + } + dom.insertAfter(list, element); + list.appendChild(element); + attemptMergeWithAdjacent(list, true); + applied.push(element); + } + + function doWrapList(start, end, template) { + var li, n = start, tmp; + while (!dom.isBlock(start.parentNode) && start.parentNode !== dom.getRoot()) { + start = dom.split(start.parentNode, start.previousSibling); + start = start.nextSibling; + n = start; + } + if (template) { + li = template.cloneNode(true); + start.parentNode.insertBefore(li, start); + while (li.firstChild) dom.remove(li.firstChild); + li = dom.rename(li, 'li'); + } else { + li = dom.create('li'); + start.parentNode.insertBefore(li, start); + } + while (n && n != end) { + tmp = n.nextSibling; + li.appendChild(n); + n = tmp; + } + if (li.childNodes.length === 0) { + li.innerHTML = '
    '; + } + makeList(li); + } + + function processBrs(element, callback) { + var startSection, previousBR, END_TO_START = 3, START_TO_END = 1, + breakElements = 'br,ul,ol,p,div,h1,h2,h3,h4,h5,h6,table,blockquote,address,pre,form,center,dl'; + + function isAnyPartSelected(start, end) { + var r = dom.createRng(), sel; + bookmark.keep = true; + ed.selection.moveToBookmark(bookmark); + bookmark.keep = false; + sel = ed.selection.getRng(true); + if (!end) { + end = start.parentNode.lastChild; + } + r.setStartBefore(start); + r.setEndAfter(end); + return !(r.compareBoundaryPoints(END_TO_START, sel) > 0 || r.compareBoundaryPoints(START_TO_END, sel) <= 0); + } + + function nextLeaf(br) { + if (br.nextSibling) + return br.nextSibling; + if (!dom.isBlock(br.parentNode) && br.parentNode !== dom.getRoot()) + return nextLeaf(br.parentNode); + } + + // Split on BRs within the range and process those. + startSection = element.firstChild; + // First mark the BRs that have any part of the previous section selected. + var trailingContentSelected = false; + each(dom.select(breakElements, element), function(br) { + if (br.hasAttribute && br.hasAttribute('_mce_bogus')) { + return true; // Skip the bogus Brs that are put in to appease Firefox and Safari. + } + if (isAnyPartSelected(startSection, br)) { + dom.addClass(br, '_mce_tagged_br'); + startSection = nextLeaf(br); + } + }); + trailingContentSelected = (startSection && isAnyPartSelected(startSection, undefined)); + startSection = element.firstChild; + each(dom.select(breakElements, element), function(br) { + // Got a section from start to br. + var tmp = nextLeaf(br); + if (br.hasAttribute && br.hasAttribute('_mce_bogus')) { + return true; // Skip the bogus Brs that are put in to appease Firefox and Safari. + } + if (dom.hasClass(br, '_mce_tagged_br')) { + callback(startSection, br, previousBR); + previousBR = null; + } else { + previousBR = br; + } + startSection = tmp; + }); + if (trailingContentSelected) { + callback(startSection, undefined, previousBR); + } + } + + function wrapList(element) { + processBrs(element, function(startSection, br, previousBR) { + // Need to indent this part + doWrapList(startSection, br); + cleanupBr(br); + cleanupBr(previousBR); + }); + } + + function changeList(element) { + if (tinymce.inArray(applied, element) !== -1) { + return; + } + if (element.parentNode.tagName === oppositeListType) { + dom.split(element.parentNode, element); + makeList(element); + attemptMergeWithNext(element.parentNode, false); + } + applied.push(element); + } + + function convertListItemToParagraph(element) { + var child, nextChild, mergedElement, splitLast; + if (tinymce.inArray(applied, element) !== -1) { + return; + } + element = splitNestedLists(element, dom); + while (dom.is(element.parentNode, 'ol,ul,li')) { + dom.split(element.parentNode, element); + } + // Push the original element we have from the selection, not the renamed one. + applied.push(element); + element = dom.rename(element, 'p'); + mergedElement = attemptMergeWithAdjacent(element, false, ed.settings.force_br_newlines); + if (mergedElement === element) { + // Now split out any block elements that can't be contained within a P. + // Manually iterate to ensure we handle modifications correctly (doesn't work with tinymce.each) + child = element.firstChild; + while (child) { + if (dom.isBlock(child)) { + child = dom.split(child.parentNode, child); + splitLast = true; + nextChild = child.nextSibling && child.nextSibling.firstChild; + } else { + nextChild = child.nextSibling; + if (splitLast && child.tagName === 'BR') { + dom.remove(child); + } + splitLast = false; + } + child = nextChild; + } + } + } + + each(selectedBlocks, function(e) { + e = findItemToOperateOn(e, dom); + if (e.tagName === oppositeListType || (e.tagName === 'LI' && e.parentNode.tagName === oppositeListType)) { + hasOppositeType = true; + } else if (e.tagName === targetListType || (e.tagName === 'LI' && e.parentNode.tagName === targetListType)) { + hasSameType = true; + } else { + hasNonList = true; + } + }); + + if (hasNonList &&!hasSameType || hasOppositeType || selectedBlocks.length === 0) { + actions = { + 'LI': changeList, + 'H1': makeList, + 'H2': makeList, + 'H3': makeList, + 'H4': makeList, + 'H5': makeList, + 'H6': makeList, + 'P': makeList, + 'BODY': makeList, + 'DIV': selectedBlocks.length > 1 ? makeList : wrapList, + defaultAction: wrapList, + elements: this.selectedBlocks() + }; + } else { + actions = { + defaultAction: convertListItemToParagraph, + elements: this.selectedBlocks() + }; + } + this.process(actions); + }, + + indent: function() { + var ed = this.ed, dom = ed.dom, indented = []; + + function createWrapItem(element) { + var wrapItem = dom.create('li', { style: 'list-style-type: none;'}); + dom.insertAfter(wrapItem, element); + return wrapItem; + } + + function createWrapList(element) { + var wrapItem = createWrapItem(element), + list = dom.getParent(element, 'ol,ul'), + listType = list.tagName, + listStyle = dom.getStyle(list, 'list-style-type'), + attrs = {}, + wrapList; + if (listStyle !== '') { + attrs.style = 'list-style-type: ' + listStyle + ';'; + } + wrapList = dom.create(listType, attrs); + wrapItem.appendChild(wrapList); + return wrapList; + } + + function indentLI(element) { + if (!hasParentInList(ed, element, indented)) { + element = splitNestedLists(element, dom); + var wrapList = createWrapList(element); + wrapList.appendChild(element); + attemptMergeWithAdjacent(wrapList.parentNode, false); + attemptMergeWithAdjacent(wrapList, false); + indented.push(element); + } + } + + this.process({ + 'LI': indentLI, + defaultAction: this.adjustPaddingFunction(true), + elements: this.selectedBlocks() + }); + + }, + + outdent: function(ui, elements) { + var t = this, ed = t.ed, dom = ed.dom, outdented = []; + + function outdentLI(element) { + var listElement, targetParent, align; + if (!hasParentInList(ed, element, outdented)) { + if (dom.getStyle(element, 'margin-left') !== '' || dom.getStyle(element, 'padding-left') !== '') { + return t.adjustPaddingFunction(false)(element); + } + align = dom.getStyle(element, 'text-align', true); + if (align === 'center' || align === 'right') { + dom.setStyle(element, 'text-align', 'left'); + return; + } + element = splitNestedLists(element, dom); + listElement = element.parentNode; + targetParent = element.parentNode.parentNode; + if (targetParent.tagName === 'P') { + dom.split(targetParent, element.parentNode); + } else { + dom.split(listElement, element); + if (targetParent.tagName === 'LI') { + // Nested list, need to split the LI and go back out to the OL/UL element. + dom.split(targetParent, element); + } else if (!dom.is(targetParent, 'ol,ul')) { + dom.rename(element, 'p'); + } + } + outdented.push(element); + } + } + + var listElements = elements && tinymce.is(elements, 'array') ? elements : this.selectedBlocks(); + this.process({ + 'LI': outdentLI, + defaultAction: this.adjustPaddingFunction(false), + elements: listElements + }); + + each(outdented, attemptMergeWithAdjacent); + }, + + process: function(actions) { + var t = this, sel = t.ed.selection, dom = t.ed.dom, selectedBlocks, r; + + function isEmptyElement(element) { + var excludeBrsAndBookmarks = tinymce.grep(element.childNodes, function(n) { + return !(n.nodeName === 'BR' || n.nodeName === 'SPAN' && dom.getAttrib(n, 'data-mce-type') == 'bookmark' + || n.nodeType == 3 && (n.nodeValue == String.fromCharCode(160) || n.nodeValue == '')); + }); + return excludeBrsAndBookmarks.length === 0; + } + + function processElement(element) { + dom.removeClass(element, '_mce_act_on'); + if (!element || element.nodeType !== 1 || selectedBlocks.length > 1 && isEmptyElement(element)) { + return; + } + element = findItemToOperateOn(element, dom); + var action = actions[element.tagName]; + if (!action) { + action = actions.defaultAction; + } + action(element); + } + + function recurse(element) { + t.splitSafeEach(element.childNodes, processElement); + } + + function brAtEdgeOfSelection(container, offset) { + return offset >= 0 && container.hasChildNodes() && offset < container.childNodes.length && + container.childNodes[offset].tagName === 'BR'; + } + + function isInTable() { + var n = sel.getNode(); + var p = dom.getParent(n, 'td'); + return p !== null; + } + + selectedBlocks = actions.elements; + + r = sel.getRng(true); + if (!r.collapsed) { + if (brAtEdgeOfSelection(r.endContainer, r.endOffset - 1)) { + r.setEnd(r.endContainer, r.endOffset - 1); + sel.setRng(r); + } + if (brAtEdgeOfSelection(r.startContainer, r.startOffset)) { + r.setStart(r.startContainer, r.startOffset + 1); + sel.setRng(r); + } + } + + + if (tinymce.isIE8) { + // append a zero sized nbsp so that caret is restored correctly using bookmark + var s = t.ed.selection.getNode(); + if (s.tagName === 'LI' && !(s.parentNode.lastChild === s)) { + var i = t.ed.getDoc().createTextNode('\uFEFF'); + s.appendChild(i); + } + } + + bookmark = sel.getBookmark(); + actions.OL = actions.UL = recurse; + t.splitSafeEach(selectedBlocks, processElement); + sel.moveToBookmark(bookmark); + bookmark = null; + + // we avoid doing repaint in a table as this will move the caret out of the table in Firefox 3.6 + if (!isInTable()) { + // Avoids table or image handles being left behind in Firefox. + t.ed.execCommand('mceRepaint'); + } + }, + + splitSafeEach: function(elements, f) { + if (tinymce.isGecko && (/Firefox\/[12]\.[0-9]/.test(navigator.userAgent) || + /Firefox\/3\.[0-4]/.test(navigator.userAgent))) { + this.classBasedEach(elements, f); + } else { + each(elements, f); + } + }, + + classBasedEach: function(elements, f) { + var dom = this.ed.dom, nodes, element; + // Mark nodes + each(elements, function(element) { + dom.addClass(element, '_mce_act_on'); + }); + nodes = dom.select('._mce_act_on'); + while (nodes.length > 0) { + element = nodes.shift(); + dom.removeClass(element, '_mce_act_on'); + f(element); + nodes = dom.select('._mce_act_on'); + } + }, + + adjustPaddingFunction: function(isIndent) { + var indentAmount, indentUnits, ed = this.ed; + indentAmount = ed.settings.indentation; + indentUnits = /[a-z%]+/i.exec(indentAmount); + indentAmount = parseInt(indentAmount, 10); + return function(element) { + var currentIndent, newIndentAmount; + currentIndent = parseInt(ed.dom.getStyle(element, 'margin-left') || 0, 10) + parseInt(ed.dom.getStyle(element, 'padding-left') || 0, 10); + if (isIndent) { + newIndentAmount = currentIndent + indentAmount; + } else { + newIndentAmount = currentIndent - indentAmount; + } + ed.dom.setStyle(element, 'padding-left', ''); + ed.dom.setStyle(element, 'margin-left', newIndentAmount > 0 ? newIndentAmount + indentUnits : ''); + }; + }, + + selectedBlocks: function() { + var ed = this.ed + var selectedBlocks = ed.selection.getSelectedBlocks(); + return selectedBlocks.length == 0 ? [ ed.dom.getRoot() ] : selectedBlocks; + }, + + getInfo: function() { + return { + longname : 'Lists', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/lists', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + tinymce.PluginManager.add("lists", tinymce.plugins.Lists); +}()); diff --git a/js/tiny_mce/plugins/media/css/media.css b/js/tiny_mce/plugins/media/css/media.css index 2d087944..fd04898c 100644 --- a/js/tiny_mce/plugins/media/css/media.css +++ b/js/tiny_mce/plugins/media/css/media.css @@ -1,16 +1,17 @@ -#id, #name, #hspace, #vspace, #class_name, #align { width: 100px } -#hspace, #vspace { width: 50px } -#flash_quality, #flash_align, #flash_scale, #flash_salign, #flash_wmode { width: 100px } -#flash_base, #flash_flashvars { width: 240px } -#width, #height { width: 40px } -#src, #media_type { width: 250px } -#class { width: 120px } -#prev { margin: 0; border: 1px solid black; width: 380px; height: 230px; overflow: auto } -.panel_wrapper div.current { height: 390px; overflow: auto } -#flash_options, #shockwave_options, #qt_options, #wmp_options, #rmp_options { display: none } -.mceAddSelectValue { background-color: #DDDDDD } -#qt_starttime, #qt_endtime, #qt_fov, #qt_href, #qt_moveid, #qt_moviename, #qt_node, #qt_pan, #qt_qtsrc, #qt_qtsrcchokespeed, #qt_target, #qt_tilt, #qt_urlsubstituten, #qt_volume { width: 70px } -#wmp_balance, #wmp_baseurl, #wmp_captioningid, #wmp_currentmarker, #wmp_currentposition, #wmp_defaultframe, #wmp_playcount, #wmp_rate, #wmp_uimode, #wmp_volume { width: 70px } -#rmp_console, #rmp_numloop, #rmp_controls, #rmp_scriptcallbacks { width: 70px } -#shockwave_swvolume, #shockwave_swframe, #shockwave_swurl, #shockwave_swstretchvalign, #shockwave_swstretchhalign, #shockwave_swstretchstyle { width: 90px } -#qt_qtsrc { width: 200px } +#id, #name, #hspace, #vspace, #class_name, #align { width: 100px } +#hspace, #vspace { width: 50px } +#flash_quality, #flash_align, #flash_scale, #flash_salign, #flash_wmode { width: 100px } +#flash_base, #flash_flashvars, #html5_altsource1, #html5_altsource2, #html5_poster { width: 240px } +#width, #height { width: 40px } +#src, #media_type { width: 250px } +#class { width: 120px } +#prev { margin: 0; border: 1px solid black; width: 380px; height: 260px; overflow: auto } +.panel_wrapper div.current { height: 420px; overflow: auto } +#flash_options, #shockwave_options, #qt_options, #wmp_options, #rmp_options { display: none } +.mceAddSelectValue { background-color: #DDDDDD } +#qt_starttime, #qt_endtime, #qt_fov, #qt_href, #qt_moveid, #qt_moviename, #qt_node, #qt_pan, #qt_qtsrc, #qt_qtsrcchokespeed, #qt_target, #qt_tilt, #qt_urlsubstituten, #qt_volume { width: 70px } +#wmp_balance, #wmp_baseurl, #wmp_captioningid, #wmp_currentmarker, #wmp_currentposition, #wmp_defaultframe, #wmp_playcount, #wmp_rate, #wmp_uimode, #wmp_volume { width: 70px } +#rmp_console, #rmp_numloop, #rmp_controls, #rmp_scriptcallbacks { width: 70px } +#shockwave_swvolume, #shockwave_swframe, #shockwave_swurl, #shockwave_swstretchvalign, #shockwave_swstretchhalign, #shockwave_swstretchstyle { width: 90px } +#qt_qtsrc { width: 200px } +iframe {border: 1px solid gray} diff --git a/js/tiny_mce/plugins/media/editor_plugin.js b/js/tiny_mce/plugins/media/editor_plugin.js index 4bbe367e..37b4320b 100644 --- a/js/tiny_mce/plugins/media/editor_plugin.js +++ b/js/tiny_mce/plugins/media/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.MediaPlugin",{init:function(b,c){var e=this;e.editor=b;e.url=c;function f(g){return/^(mceItemFlash|mceItemShockWave|mceItemWindowsMedia|mceItemQuickTime|mceItemRealMedia)$/.test(g.className)}b.onPreInit.add(function(){b.serializer.addRules("param[name|value|_mce_value]")});b.addCommand("mceMedia",function(){b.windowManager.open({file:c+"/media.htm",width:430+parseInt(b.getLang("media.delta_width",0)),height:470+parseInt(b.getLang("media.delta_height",0)),inline:1},{plugin_url:c})});b.addButton("media",{title:"media.desc",cmd:"mceMedia"});b.onNodeChange.add(function(h,g,i){g.setActive("media",i.nodeName=="IMG"&&f(i))});b.onInit.add(function(){var g={mceItemFlash:"flash",mceItemShockWave:"shockwave",mceItemWindowsMedia:"windowsmedia",mceItemQuickTime:"quicktime",mceItemRealMedia:"realmedia"};b.selection.onSetContent.add(function(){e._spansToImgs(b.getBody())});b.selection.onBeforeSetContent.add(e._objectsToSpans,e);if(b.settings.content_css!==false){b.dom.loadCSS(c+"/css/content.css")}if(b.theme&&b.theme.onResolveName){b.theme.onResolveName.add(function(h,i){if(i.name=="img"){a(g,function(l,j){if(b.dom.hasClass(i.node,j)){i.name=l;i.title=b.dom.getAttrib(i.node,"title");return false}})}})}if(b&&b.plugins.contextmenu){b.plugins.contextmenu.onContextMenu.add(function(i,h,j){if(j.nodeName=="IMG"&&/mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(j.className)){h.add({title:"media.edit",icon:"media",cmd:"mceMedia"})}})}});b.onBeforeSetContent.add(e._objectsToSpans,e);b.onSetContent.add(function(){e._spansToImgs(b.getBody())});b.onPreProcess.add(function(g,i){var h=g.dom;if(i.set){e._spansToImgs(i.node);a(h.select("IMG",i.node),function(k){var j;if(f(k)){j=e._parse(k.title);h.setAttrib(k,"width",h.getAttrib(k,"width",j.width||100));h.setAttrib(k,"height",h.getAttrib(k,"height",j.height||100))}})}if(i.get){a(h.select("IMG",i.node),function(m){var l,j,k;if(g.getParam("media_use_script")){if(f(m)){m.className=m.className.replace(/mceItem/g,"mceTemp")}return}switch(m.className){case"mceItemFlash":l="d27cdb6e-ae6d-11cf-96b8-444553540000";j="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0";k="application/x-shockwave-flash";break;case"mceItemShockWave":l="166b1bca-3f9c-11cf-8075-444553540000";j="http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0";k="application/x-director";break;case"mceItemWindowsMedia":l=g.getParam("media_wmp6_compatible")?"05589fa1-c356-11ce-bf01-00aa0055595a":"6bf52a52-394a-11d3-b153-00c04f79faa6";j="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701";k="application/x-mplayer2";break;case"mceItemQuickTime":l="02bf25d5-8c17-4b23-bc80-d3488abddc6b";j="http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0";k="video/quicktime";break;case"mceItemRealMedia":l="cfcdaa03-8be4-11cf-b84b-0020afbbccfa";j="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0";k="audio/x-pn-realaudio-plugin";break}if(l){h.replace(e._buildObj({classid:l,codebase:j,type:k},m),m)}})}});b.onPostProcess.add(function(g,h){h.content=h.content.replace(/_mce_value=/g,"value=")});function d(g,h){h=new RegExp(h+'="([^"]+)"',"g").exec(g);return h?b.dom.decode(h[1]):""}b.onPostProcess.add(function(g,h){if(g.getParam("media_use_script")){h.content=h.content.replace(/]+>/g,function(j){var i=d(j,"class");if(/^(mceTempFlash|mceTempShockWave|mceTempWindowsMedia|mceTempQuickTime|mceTempRealMedia)$/.test(i)){at=e._parse(d(j,"title"));at.width=d(j,"width");at.height=d(j,"height");j=''; - } - - return im; - }); - } - }); - }, - - getInfo : function() { - return { - longname : 'Media', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - // Private methods - _objectsToSpans : function(ed, o) { - var t = this, h = o.content; - - h = h.replace(/]*>\s*write(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)\(\{([^\)]*)\}\);\s*<\/script>/gi, function(a, b, c) { - var o = t._parse(c); - - return '' - }); - - h = h.replace(/]*)>/gi, ''); - h = h.replace(/]*)\/?>/gi, ''); - h = h.replace(/]*)>/gi, ''); - h = h.replace(/<\/(object)([^>]*)>/gi, ''); - h = h.replace(/<\/embed>/gi, ''); - h = h.replace(/]*)>/gi, function(a, b) {return ''}); - h = h.replace(/\/ class=\"mceItemParam\"><\/span>/gi, 'class="mceItemParam">'); - - o.content = h; - }, - - _buildObj : function(o, n) { - var ob, ed = this.editor, dom = ed.dom, p = this._parse(n.title), stc; - - stc = ed.getParam('media_strict', true) && o.type == 'application/x-shockwave-flash'; - - p.width = o.width = dom.getAttrib(n, 'width') || 100; - p.height = o.height = dom.getAttrib(n, 'height') || 100; - - if (p.src) - p.src = ed.convertURL(p.src, 'src', n); - - if (stc) { - ob = dom.create('span', { - id : p.id, - _mce_name : 'object', - type : 'application/x-shockwave-flash', - data : p.src, - style : dom.getAttrib(n, 'style'), - width : o.width, - height : o.height - }); - } else { - ob = dom.create('span', { - id : p.id, - _mce_name : 'object', - classid : "clsid:" + o.classid, - style : dom.getAttrib(n, 'style'), - codebase : o.codebase, - width : o.width, - height : o.height - }); - } - - each (p, function(v, k) { - if (!/^(width|height|codebase|classid|id|_cx|_cy)$/.test(k)) { - // Use url instead of src in IE for Windows media - if (o.type == 'application/x-mplayer2' && k == 'src' && !p.url) - k = 'url'; - - if (v) - dom.add(ob, 'span', {_mce_name : 'param', name : k, '_mce_value' : v}); - } - }); - - if (!stc) - dom.add(ob, 'span', tinymce.extend({_mce_name : 'embed', type : o.type, style : dom.getAttrib(n, 'style')}, p)); - - return ob; - }, - - _spansToImgs : function(p) { - var t = this, dom = t.editor.dom, im, ci; - - each(dom.select('span', p), function(n) { - // Convert object into image - if (dom.getAttrib(n, 'class') == 'mceItemObject') { - ci = dom.getAttrib(n, "classid").toLowerCase().replace(/\s+/g, ''); - - switch (ci) { - case 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000': - dom.replace(t._createImg('mceItemFlash', n), n); - break; - - case 'clsid:166b1bca-3f9c-11cf-8075-444553540000': - dom.replace(t._createImg('mceItemShockWave', n), n); - break; - - case 'clsid:6bf52a52-394a-11d3-b153-00c04f79faa6': - case 'clsid:22d6f312-b0f6-11d0-94ab-0080c74c7e95': - case 'clsid:05589fa1-c356-11ce-bf01-00aa0055595a': - dom.replace(t._createImg('mceItemWindowsMedia', n), n); - break; - - case 'clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b': - dom.replace(t._createImg('mceItemQuickTime', n), n); - break; - - case 'clsid:cfcdaa03-8be4-11cf-b84b-0020afbbccfa': - dom.replace(t._createImg('mceItemRealMedia', n), n); - break; - - default: - dom.replace(t._createImg('mceItemFlash', n), n); - } - - return; - } - - // Convert embed into image - if (dom.getAttrib(n, 'class') == 'mceItemEmbed') { - switch (dom.getAttrib(n, 'type')) { - case 'application/x-shockwave-flash': - dom.replace(t._createImg('mceItemFlash', n), n); - break; - - case 'application/x-director': - dom.replace(t._createImg('mceItemShockWave', n), n); - break; - - case 'application/x-mplayer2': - dom.replace(t._createImg('mceItemWindowsMedia', n), n); - break; - - case 'video/quicktime': - dom.replace(t._createImg('mceItemQuickTime', n), n); - break; - - case 'audio/x-pn-realaudio-plugin': - dom.replace(t._createImg('mceItemRealMedia', n), n); - break; - - default: - dom.replace(t._createImg('mceItemFlash', n), n); - } - } - }); - }, - - _createImg : function(cl, n) { - var im, dom = this.editor.dom, pa = {}, ti = '', args; - - args = ['id', 'name', 'width', 'height', 'bgcolor', 'align', 'flashvars', 'src', 'wmode', 'allowfullscreen', 'quality', 'data']; - - // Create image - im = dom.create('img', { - src : this.url + '/img/trans.gif', - width : dom.getAttrib(n, 'width') || 100, - height : dom.getAttrib(n, 'height') || 100, - style : dom.getAttrib(n, 'style'), - 'class' : cl - }); - - // Setup base parameters - each(args, function(na) { - var v = dom.getAttrib(n, na); - - if (v) - pa[na] = v; - }); - - // Add optional parameters - each(dom.select('span', n), function(n) { - if (dom.hasClass(n, 'mceItemParam')) - pa[dom.getAttrib(n, 'name')] = dom.getAttrib(n, '_mce_value'); - }); - - // Use src not movie - if (pa.movie) { - pa.src = pa.movie; - delete pa.movie; - } - - // No src try data - if (!pa.src) { - pa.src = pa.data; - delete pa.data; - } - - // Merge with embed args - n = dom.select('.mceItemEmbed', n)[0]; - if (n) { - each(args, function(na) { - var v = dom.getAttrib(n, na); - - if (v && !pa[na]) - pa[na] = v; - }); - } - - delete pa.width; - delete pa.height; - - im.title = this._serialize(pa); - - return im; - }, - - _parse : function(s) { - return tinymce.util.JSON.parse('{' + s + '}'); - }, - - _serialize : function(o) { - return tinymce.util.JSON.serialize(o).replace(/[{}]/g, ''); - } - }); - - // Register plugin - tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin); -})(); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var rootAttributes = tinymce.explode('id,name,width,height,style,align,class,hspace,vspace,bgcolor,type'), excludedAttrs = tinymce.makeMap(rootAttributes.join(',')), Node = tinymce.html.Node, + mediaTypes, scriptRegExp, JSON = tinymce.util.JSON, mimeTypes; + + // Media types supported by this plugin + mediaTypes = [ + // Type, clsid:s, mime types, codebase + ["Flash", "d27cdb6e-ae6d-11cf-96b8-444553540000", "application/x-shockwave-flash", "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"], + ["ShockWave", "166b1bca-3f9c-11cf-8075-444553540000", "application/x-director", "http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0"], + ["WindowsMedia", "6bf52a52-394a-11d3-b153-00c04f79faa6,22d6f312-b0f6-11d0-94ab-0080c74c7e95,05589fa1-c356-11ce-bf01-00aa0055595a", "application/x-mplayer2", "http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701"], + ["QuickTime", "02bf25d5-8c17-4b23-bc80-d3488abddc6b", "video/quicktime", "http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0"], + ["RealMedia", "cfcdaa03-8be4-11cf-b84b-0020afbbccfa", "audio/x-pn-realaudio-plugin", "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"], + ["Java", "8ad9c840-044e-11d1-b3e9-00805f499d93", "application/x-java-applet", "http://java.sun.com/products/plugin/autodl/jinstall-1_5_0-windows-i586.cab#Version=1,5,0,0"], + ["Silverlight", "dfeaf541-f3e1-4c24-acac-99c30715084a", "application/x-silverlight-2"], + ["Iframe"], + ["Video"], + ["EmbeddedAudio"], + ["Audio"] + ]; + + function toArray(obj) { + var undef, out, i; + + if (obj && !obj.splice) { + out = []; + + for (i = 0; true; i++) { + if (obj[i]) + out[i] = obj[i]; + else + break; + } + + return out; + } + + return obj; + }; + + tinymce.create('tinymce.plugins.MediaPlugin', { + init : function(ed, url) { + var self = this, lookup = {}, i, y, item, name; + + function isMediaImg(node) { + return node && node.nodeName === 'IMG' && ed.dom.hasClass(node, 'mceItemMedia'); + }; + + self.editor = ed; + self.url = url; + + // Parse media types into a lookup table + scriptRegExp = ''; + for (i = 0; i < mediaTypes.length; i++) { + name = mediaTypes[i][0]; + + item = { + name : name, + clsids : tinymce.explode(mediaTypes[i][1] || ''), + mimes : tinymce.explode(mediaTypes[i][2] || ''), + codebase : mediaTypes[i][3] + }; + + for (y = 0; y < item.clsids.length; y++) + lookup['clsid:' + item.clsids[y]] = item; + + for (y = 0; y < item.mimes.length; y++) + lookup[item.mimes[y]] = item; + + lookup['mceItem' + name] = item; + lookup[name.toLowerCase()] = item; + + scriptRegExp += (scriptRegExp ? '|' : '') + name; + } + + // Handle the media_types setting + tinymce.each(ed.getParam("media_types", + "video=mp4,m4v,ogv,webm;" + + "silverlight=xap;" + + "flash=swf,flv;" + + "shockwave=dcr;" + + "quicktime=mov,qt,mpg,mpeg;" + + "shockwave=dcr;" + + "windowsmedia=avi,wmv,wm,asf,asx,wmx,wvx;" + + "realmedia=rm,ra,ram;" + + "java=jar;" + + "audio=mp3,ogg" + ).split(';'), function(item) { + var i, extensions, type; + + item = item.split(/=/); + extensions = tinymce.explode(item[1].toLowerCase()); + for (i = 0; i < extensions.length; i++) { + type = lookup[item[0].toLowerCase()]; + + if (type) + lookup[extensions[i]] = type; + } + }); + + scriptRegExp = new RegExp('write(' + scriptRegExp + ')\\(([^)]+)\\)'); + self.lookup = lookup; + + ed.onPreInit.add(function() { + // Allow video elements + ed.schema.addValidElements('object[id|style|width|height|classid|codebase|*],param[name|value],embed[id|style|width|height|type|src|*],video[*],audio[*],source[*]'); + + // Convert video elements to image placeholder + ed.parser.addNodeFilter('object,embed,video,audio,script,iframe', function(nodes) { + var i = nodes.length; + + while (i--) + self.objectToImg(nodes[i]); + }); + + // Convert image placeholders to video elements + ed.serializer.addNodeFilter('img', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if ((node.attr('class') || '').indexOf('mceItemMedia') !== -1) + self.imgToObject(node, args); + } + }); + }); + + ed.onInit.add(function() { + // Display "media" instead of "img" in element path + if (ed.theme && ed.theme.onResolveName) { + ed.theme.onResolveName.add(function(theme, path_object) { + if (path_object.name === 'img' && ed.dom.hasClass(path_object.node, 'mceItemMedia')) + path_object.name = 'media'; + }); + } + + // Add contect menu if it's loaded + if (ed && ed.plugins.contextmenu) { + ed.plugins.contextmenu.onContextMenu.add(function(plugin, menu, element) { + if (element.nodeName === 'IMG' && element.className.indexOf('mceItemMedia') !== -1) + menu.add({title : 'media.edit', icon : 'media', cmd : 'mceMedia'}); + }); + } + }); + + // Register commands + ed.addCommand('mceMedia', function() { + var data, img; + + img = ed.selection.getNode(); + if (isMediaImg(img)) { + data = ed.dom.getAttrib(img, 'data-mce-json'); + if (data) { + data = JSON.parse(data); + + // Add some extra properties to the data object + tinymce.each(rootAttributes, function(name) { + var value = ed.dom.getAttrib(img, name); + + if (value) + data[name] = value; + }); + + data.type = self.getType(img.className).name.toLowerCase(); + } + } + + if (!data) { + data = { + type : 'flash', + video: {sources:[]}, + params: {} + }; + } + + ed.windowManager.open({ + file : url + '/media.htm', + width : 430 + parseInt(ed.getLang('media.delta_width', 0)), + height : 500 + parseInt(ed.getLang('media.delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + data : data + }); + }); + + // Register buttons + ed.addButton('media', {title : 'media.desc', cmd : 'mceMedia'}); + + // Update media selection status + ed.onNodeChange.add(function(ed, cm, node) { + cm.setActive('media', isMediaImg(node)); + }); + }, + + convertUrl : function(url, force_absolute) { + var self = this, editor = self.editor, settings = editor.settings, + urlConverter = settings.url_converter, + urlConverterScope = settings.url_converter_scope || self; + + if (!url) + return url; + + if (force_absolute) + return editor.documentBaseURI.toAbsolute(url); + + return urlConverter.call(urlConverterScope, url, 'src', 'object'); + }, + + getInfo : function() { + return { + longname : 'Media', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + /** + * Converts the JSON data object to an img node. + */ + dataToImg : function(data, force_absolute) { + var self = this, editor = self.editor, baseUri = editor.documentBaseURI, sources, attrs, img, i; + + data.params.src = self.convertUrl(data.params.src, force_absolute); + + attrs = data.video.attrs; + if (attrs) + attrs.src = self.convertUrl(attrs.src, force_absolute); + + if (attrs) + attrs.poster = self.convertUrl(attrs.poster, force_absolute); + + sources = toArray(data.video.sources); + if (sources) { + for (i = 0; i < sources.length; i++) + sources[i].src = self.convertUrl(sources[i].src, force_absolute); + } + + img = self.editor.dom.create('img', { + id : data.id, + style : data.style, + align : data.align, + hspace : data.hspace, + vspace : data.vspace, + src : self.editor.theme.url + '/img/trans.gif', + 'class' : 'mceItemMedia mceItem' + self.getType(data.type).name, + 'data-mce-json' : JSON.serialize(data, "'") + }); + + img.width = data.width || (data.type == 'audio' ? "300" : "320"); + img.height = data.height || (data.type == 'audio' ? "32" : "240"); + + return img; + }, + + /** + * Converts the JSON data object to a HTML string. + */ + dataToHtml : function(data, force_absolute) { + return this.editor.serializer.serialize(this.dataToImg(data, force_absolute), {forced_root_block : '', force_absolute : force_absolute}); + }, + + /** + * Converts the JSON data object to a HTML string. + */ + htmlToData : function(html) { + var fragment, img, data; + + data = { + type : 'flash', + video: {sources:[]}, + params: {} + }; + + fragment = this.editor.parser.parse(html); + img = fragment.getAll('img')[0]; + + if (img) { + data = JSON.parse(img.attr('data-mce-json')); + data.type = this.getType(img.attr('class')).name.toLowerCase(); + + // Add some extra properties to the data object + tinymce.each(rootAttributes, function(name) { + var value = img.attr(name); + + if (value) + data[name] = value; + }); + } + + return data; + }, + + /** + * Get type item by extension, class, clsid or mime type. + * + * @method getType + * @param {String} value Value to get type item by. + * @return {Object} Type item object or undefined. + */ + getType : function(value) { + var i, values, typeItem; + + // Find type by checking the classes + values = tinymce.explode(value, ' '); + for (i = 0; i < values.length; i++) { + typeItem = this.lookup[values[i]]; + + if (typeItem) + return typeItem; + } + }, + + /** + * Converts a tinymce.html.Node image element to video/object/embed. + */ + imgToObject : function(node, args) { + var self = this, editor = self.editor, video, object, embed, iframe, name, value, data, + source, sources, params, param, typeItem, i, item, mp4Source, replacement, + posterSrc, style, audio; + + // Adds the flash player + function addPlayer(video_src, poster_src) { + var baseUri, flashVars, flashVarsOutput, params, flashPlayer; + + flashPlayer = editor.getParam('flash_video_player_url', self.convertUrl(self.url + '/moxieplayer.swf')); + if (flashPlayer) { + baseUri = editor.documentBaseURI; + data.params.src = flashPlayer; + + // Convert the movie url to absolute urls + if (editor.getParam('flash_video_player_absvideourl', true)) { + video_src = baseUri.toAbsolute(video_src || '', true); + poster_src = baseUri.toAbsolute(poster_src || '', true); + } + + // Generate flash vars + flashVarsOutput = ''; + flashVars = editor.getParam('flash_video_player_flashvars', {url : '$url', poster : '$poster'}); + tinymce.each(flashVars, function(value, name) { + // Replace $url and $poster variables in flashvars value + value = value.replace(/\$url/, video_src || ''); + value = value.replace(/\$poster/, poster_src || ''); + + if (value.length > 0) + flashVarsOutput += (flashVarsOutput ? '&' : '') + name + '=' + escape(value); + }); + + if (flashVarsOutput.length) + data.params.flashvars = flashVarsOutput; + + params = editor.getParam('flash_video_player_params', { + allowfullscreen: true, + allowscriptaccess: true + }); + + tinymce.each(params, function(value, name) { + data.params[name] = "" + value; + }); + } + }; + + data = node.attr('data-mce-json'); + if (!data) + return; + + data = JSON.parse(data); + typeItem = this.getType(node.attr('class')); + + style = node.attr('data-mce-style') + if (!style) { + style = node.attr('style'); + + if (style) + style = editor.dom.serializeStyle(editor.dom.parseStyle(style, 'img')); + } + + // Handle iframe + if (typeItem.name === 'Iframe') { + replacement = new Node('iframe', 1); + + tinymce.each(rootAttributes, function(name) { + var value = node.attr(name); + + if (name == 'class' && value) + value = value.replace(/mceItem.+ ?/g, ''); + + if (value && value.length > 0) + replacement.attr(name, value); + }); + + for (name in data.params) + replacement.attr(name, data.params[name]); + + replacement.attr({ + style: style, + src: data.params.src + }); + + node.replace(replacement); + + return; + } + + // Handle scripts + if (this.editor.settings.media_use_script) { + replacement = new Node('script', 1).attr('type', 'text/javascript'); + + value = new Node('#text', 3); + value.value = 'write' + typeItem.name + '(' + JSON.serialize(tinymce.extend(data.params, { + width: node.attr('width'), + height: node.attr('height') + })) + ');'; + + replacement.append(value); + node.replace(replacement); + + return; + } + + // Add HTML5 video element + if (typeItem.name === 'Video' && data.video.sources[0]) { + // Create new object element + video = new Node('video', 1).attr(tinymce.extend({ + id : node.attr('id'), + width: node.attr('width'), + height: node.attr('height'), + style : style + }, data.video.attrs)); + + // Get poster source and use that for flash fallback + if (data.video.attrs) + posterSrc = data.video.attrs.poster; + + sources = data.video.sources = toArray(data.video.sources); + for (i = 0; i < sources.length; i++) { + if (/\.mp4$/.test(sources[i].src)) + mp4Source = sources[i].src; + } + + if (!sources[0].type) { + video.attr('src', sources[0].src); + sources.splice(0, 1); + } + + for (i = 0; i < sources.length; i++) { + source = new Node('source', 1).attr(sources[i]); + source.shortEnded = true; + video.append(source); + } + + // Create flash fallback for video if we have a mp4 source + if (mp4Source) { + addPlayer(mp4Source, posterSrc); + typeItem = self.getType('flash'); + } else + data.params.src = ''; + } + + // Add HTML5 audio element + if (typeItem.name === 'Audio' && data.video.sources[0]) { + // Create new object element + audio = new Node('audio', 1).attr(tinymce.extend({ + id : node.attr('id'), + width: node.attr('width'), + height: node.attr('height'), + style : style + }, data.video.attrs)); + + // Get poster source and use that for flash fallback + if (data.video.attrs) + posterSrc = data.video.attrs.poster; + + sources = data.video.sources = toArray(data.video.sources); + if (!sources[0].type) { + audio.attr('src', sources[0].src); + sources.splice(0, 1); + } + + for (i = 0; i < sources.length; i++) { + source = new Node('source', 1).attr(sources[i]); + source.shortEnded = true; + audio.append(source); + } + + data.params.src = ''; + } + + if (typeItem.name === 'EmbeddedAudio') { + embed = new Node('embed', 1); + embed.shortEnded = true; + embed.attr({ + id: node.attr('id'), + width: node.attr('width'), + height: node.attr('height'), + style : style, + type: node.attr('type') + }); + + for (name in data.params) + embed.attr(name, data.params[name]); + + tinymce.each(rootAttributes, function(name) { + if (data[name] && name != 'type') + embed.attr(name, data[name]); + }); + + data.params.src = ''; + } + + // Do we have a params src then we can generate object + if (data.params.src) { + // Is flv movie add player for it + if (/\.flv$/i.test(data.params.src)) + addPlayer(data.params.src, ''); + + if (args && args.force_absolute) + data.params.src = editor.documentBaseURI.toAbsolute(data.params.src); + + // Create new object element + object = new Node('object', 1).attr({ + id : node.attr('id'), + width: node.attr('width'), + height: node.attr('height'), + style : style + }); + + tinymce.each(rootAttributes, function(name) { + var value = data[name]; + + if (name == 'class' && value) + value = value.replace(/mceItem.+ ?/g, ''); + + if (value && name != 'type') + object.attr(name, value); + }); + + // Add params + for (name in data.params) { + param = new Node('param', 1); + param.shortEnded = true; + value = data.params[name]; + + // Windows media needs to use url instead of src for the media URL + if (name === 'src' && typeItem.name === 'WindowsMedia') + name = 'url'; + + param.attr({name: name, value: value}); + object.append(param); + } + + // Setup add type and classid if strict is disabled + if (this.editor.getParam('media_strict', true)) { + object.attr({ + data: data.params.src, + type: typeItem.mimes[0] + }); + } else { + object.attr({ + classid: "clsid:" + typeItem.clsids[0], + codebase: typeItem.codebase + }); + + embed = new Node('embed', 1); + embed.shortEnded = true; + embed.attr({ + id: node.attr('id'), + width: node.attr('width'), + height: node.attr('height'), + style : style, + type: typeItem.mimes[0] + }); + + for (name in data.params) + embed.attr(name, data.params[name]); + + tinymce.each(rootAttributes, function(name) { + if (data[name] && name != 'type') + embed.attr(name, data[name]); + }); + + object.append(embed); + } + + // Insert raw HTML + if (data.object_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.object_html; + object.append(value); + } + + // Append object to video element if it exists + if (video) + video.append(object); + } + + if (video) { + // Insert raw HTML + if (data.video_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.video_html; + video.append(value); + } + } + + if (audio) { + // Insert raw HTML + if (data.video_html) { + value = new Node('#text', 3); + value.raw = true; + value.value = data.video_html; + audio.append(value); + } + } + + var n = video || audio || object || embed; + if (n) + node.replace(n); + else + node.remove(); + }, + + /** + * Converts a tinymce.html.Node video/object/embed to an img element. + * + * The video/object/embed will be converted into an image placeholder with a JSON data attribute like this: + * + * + * The JSON structure will be like this: + * {'params':{'flashvars':'something','quality':'high','src':'someurl'}, 'video':{'sources':[{src: 'someurl', type: 'video/mp4'}]}} + */ + objectToImg : function(node) { + var object, embed, video, iframe, img, name, id, width, height, style, i, html, + param, params, source, sources, data, type, lookup = this.lookup, + matches, attrs, urlConverter = this.editor.settings.url_converter, + urlConverterScope = this.editor.settings.url_converter_scope, + hspace, vspace, align, bgcolor; + + function getInnerHTML(node) { + return new tinymce.html.Serializer({ + inner: true, + validate: false + }).serialize(node); + }; + + function lookupAttribute(o, attr) { + return lookup[(o.attr(attr) || '').toLowerCase()]; + } + + function lookupExtension(src) { + var ext = src.replace(/^.*\.([^.]+)$/, '$1'); + return lookup[ext.toLowerCase() || '']; + } + + // If node isn't in document + if (!node.parent) + return; + + // Handle media scripts + if (node.name === 'script') { + if (node.firstChild) + matches = scriptRegExp.exec(node.firstChild.value); + + if (!matches) + return; + + type = matches[1]; + data = {video : {}, params : JSON.parse(matches[2])}; + width = data.params.width; + height = data.params.height; + } + + // Setup data objects + data = data || { + video : {}, + params : {} + }; + + // Setup new image object + img = new Node('img', 1); + img.attr({ + src : this.editor.theme.url + '/img/trans.gif' + }); + + // Video element + name = node.name; + if (name === 'video' || name == 'audio') { + video = node; + object = node.getAll('object')[0]; + embed = node.getAll('embed')[0]; + width = video.attr('width'); + height = video.attr('height'); + id = video.attr('id'); + data.video = {attrs : {}, sources : []}; + + // Get all video attributes + attrs = data.video.attrs; + for (name in video.attributes.map) + attrs[name] = video.attributes.map[name]; + + source = node.attr('src'); + if (source) + data.video.sources.push({src : urlConverter.call(urlConverterScope, source, 'src', node.name)}); + + // Get all sources + sources = video.getAll("source"); + for (i = 0; i < sources.length; i++) { + source = sources[i].remove(); + + data.video.sources.push({ + src: urlConverter.call(urlConverterScope, source.attr('src'), 'src', 'source'), + type: source.attr('type'), + media: source.attr('media') + }); + } + + // Convert the poster URL + if (attrs.poster) + attrs.poster = urlConverter.call(urlConverterScope, attrs.poster, 'poster', node.name); + } + + // Object element + if (node.name === 'object') { + object = node; + embed = node.getAll('embed')[0]; + } + + // Embed element + if (node.name === 'embed') + embed = node; + + // Iframe element + if (node.name === 'iframe') { + iframe = node; + type = 'Iframe'; + } + + if (object) { + // Get width/height + width = width || object.attr('width'); + height = height || object.attr('height'); + style = style || object.attr('style'); + id = id || object.attr('id'); + hspace = hspace || object.attr('hspace'); + vspace = vspace || object.attr('vspace'); + align = align || object.attr('align'); + bgcolor = bgcolor || object.attr('bgcolor'); + data.name = object.attr('name'); + + // Get all object params + params = object.getAll("param"); + for (i = 0; i < params.length; i++) { + param = params[i]; + name = param.remove().attr('name'); + + if (!excludedAttrs[name]) + data.params[name] = param.attr('value'); + } + + data.params.src = data.params.src || object.attr('data'); + } + + if (embed) { + // Get width/height + width = width || embed.attr('width'); + height = height || embed.attr('height'); + style = style || embed.attr('style'); + id = id || embed.attr('id'); + hspace = hspace || embed.attr('hspace'); + vspace = vspace || embed.attr('vspace'); + align = align || embed.attr('align'); + bgcolor = bgcolor || embed.attr('bgcolor'); + + // Get all embed attributes + for (name in embed.attributes.map) { + if (!excludedAttrs[name] && !data.params[name]) + data.params[name] = embed.attributes.map[name]; + } + } + + if (iframe) { + // Get width/height + width = iframe.attr('width'); + height = iframe.attr('height'); + style = style || iframe.attr('style'); + id = iframe.attr('id'); + hspace = iframe.attr('hspace'); + vspace = iframe.attr('vspace'); + align = iframe.attr('align'); + bgcolor = iframe.attr('bgcolor'); + + tinymce.each(rootAttributes, function(name) { + img.attr(name, iframe.attr(name)); + }); + + // Get all iframe attributes + for (name in iframe.attributes.map) { + if (!excludedAttrs[name] && !data.params[name]) + data.params[name] = iframe.attributes.map[name]; + } + } + + // Use src not movie + if (data.params.movie) { + data.params.src = data.params.src || data.params.movie; + delete data.params.movie; + } + + // Convert the URL to relative/absolute depending on configuration + if (data.params.src) + data.params.src = urlConverter.call(urlConverterScope, data.params.src, 'src', 'object'); + + if (video) { + if (node.name === 'video') + type = lookup.video.name; + else if (node.name === 'audio') + type = lookup.audio.name; + } + + if (object && !type) + type = (lookupAttribute(object, 'clsid') || lookupAttribute(object, 'classid') || lookupAttribute(object, 'type') || {}).name; + + if (embed && !type) + type = (lookupAttribute(embed, 'type') || lookupExtension(data.params.src) || {}).name; + + // for embedded audio we preserve the original specified type + if (embed && type == 'EmbeddedAudio') { + data.params.type = embed.attr('type'); + } + + // Replace the video/object/embed element with a placeholder image containing the data + node.replace(img); + + // Remove embed + if (embed) + embed.remove(); + + // Serialize the inner HTML of the object element + if (object) { + html = getInnerHTML(object.remove()); + + if (html) + data.object_html = html; + } + + // Serialize the inner HTML of the video element + if (video) { + html = getInnerHTML(video.remove()); + + if (html) + data.video_html = html; + } + + data.hspace = hspace; + data.vspace = vspace; + data.align = align; + data.bgcolor = bgcolor; + + // Set width/height of placeholder + img.attr({ + id : id, + 'class' : 'mceItemMedia mceItem' + (type || 'Flash'), + style : style, + width : width || (node.name == 'audio' ? "300" : "320"), + height : height || (node.name == 'audio' ? "32" : "240"), + hspace : hspace, + vspace : vspace, + align : align, + bgcolor : bgcolor, + "data-mce-json" : JSON.serialize(data, "'") + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin); +})(); diff --git a/js/tiny_mce/plugins/media/js/media.js b/js/tiny_mce/plugins/media/js/media.js index 86cfa985..37ebd6d2 100644 --- a/js/tiny_mce/plugins/media/js/media.js +++ b/js/tiny_mce/plugins/media/js/media.js @@ -1,630 +1,464 @@ -tinyMCEPopup.requireLangPack(); - -var oldWidth, oldHeight, ed, url; - -if (url = tinyMCEPopup.getParam("media_external_list_url")) - document.write(''); - -function init() { - var pl = "", f, val; - var type = "flash", fe, i; - - ed = tinyMCEPopup.editor; - - tinyMCEPopup.resizeToInnerSize(); - f = document.forms[0] - - fe = ed.selection.getNode(); - if (/mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) { - pl = fe.title; - - switch (ed.dom.getAttrib(fe, 'class')) { - case 'mceItemFlash': - type = 'flash'; - break; - - case 'mceItemFlashVideo': - type = 'flv'; - break; - - case 'mceItemShockWave': - type = 'shockwave'; - break; - - case 'mceItemWindowsMedia': - type = 'wmp'; - break; - - case 'mceItemQuickTime': - type = 'qt'; - break; - - case 'mceItemRealMedia': - type = 'rmp'; - break; - } - - document.forms[0].insert.value = ed.getLang('update', 'Insert', true); - } - - document.getElementById('filebrowsercontainer').innerHTML = getBrowserHTML('filebrowser','src','media','media'); - document.getElementById('qtsrcfilebrowsercontainer').innerHTML = getBrowserHTML('qtsrcfilebrowser','qt_qtsrc','media','media'); - document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); - - var html = getMediaListHTML('medialist','src','media','media'); - if (html == "") - document.getElementById("linklistrow").style.display = 'none'; - else - document.getElementById("linklistcontainer").innerHTML = html; - - // Resize some elements - if (isVisible('filebrowser')) - document.getElementById('src').style.width = '230px'; - - // Setup form - if (pl != "") { - pl = tinyMCEPopup.editor.plugins.media._parse(pl); - - switch (type) { - case "flash": - setBool(pl, 'flash', 'play'); - setBool(pl, 'flash', 'loop'); - setBool(pl, 'flash', 'menu'); - setBool(pl, 'flash', 'swliveconnect'); - setStr(pl, 'flash', 'quality'); - setStr(pl, 'flash', 'scale'); - setStr(pl, 'flash', 'salign'); - setStr(pl, 'flash', 'wmode'); - setStr(pl, 'flash', 'base'); - setStr(pl, 'flash', 'flashvars'); - break; - - case "qt": - setBool(pl, 'qt', 'loop'); - setBool(pl, 'qt', 'autoplay'); - setBool(pl, 'qt', 'cache'); - setBool(pl, 'qt', 'controller'); - setBool(pl, 'qt', 'correction'); - setBool(pl, 'qt', 'enablejavascript'); - setBool(pl, 'qt', 'kioskmode'); - setBool(pl, 'qt', 'autohref'); - setBool(pl, 'qt', 'playeveryframe'); - setBool(pl, 'qt', 'tarsetcache'); - setStr(pl, 'qt', 'scale'); - setStr(pl, 'qt', 'starttime'); - setStr(pl, 'qt', 'endtime'); - setStr(pl, 'qt', 'tarset'); - setStr(pl, 'qt', 'qtsrcchokespeed'); - setStr(pl, 'qt', 'volume'); - setStr(pl, 'qt', 'qtsrc'); - break; - - case "shockwave": - setBool(pl, 'shockwave', 'sound'); - setBool(pl, 'shockwave', 'progress'); - setBool(pl, 'shockwave', 'autostart'); - setBool(pl, 'shockwave', 'swliveconnect'); - setStr(pl, 'shockwave', 'swvolume'); - setStr(pl, 'shockwave', 'swstretchstyle'); - setStr(pl, 'shockwave', 'swstretchhalign'); - setStr(pl, 'shockwave', 'swstretchvalign'); - break; - - case "wmp": - setBool(pl, 'wmp', 'autostart'); - setBool(pl, 'wmp', 'enabled'); - setBool(pl, 'wmp', 'enablecontextmenu'); - setBool(pl, 'wmp', 'fullscreen'); - setBool(pl, 'wmp', 'invokeurls'); - setBool(pl, 'wmp', 'mute'); - setBool(pl, 'wmp', 'stretchtofit'); - setBool(pl, 'wmp', 'windowlessvideo'); - setStr(pl, 'wmp', 'balance'); - setStr(pl, 'wmp', 'baseurl'); - setStr(pl, 'wmp', 'captioningid'); - setStr(pl, 'wmp', 'currentmarker'); - setStr(pl, 'wmp', 'currentposition'); - setStr(pl, 'wmp', 'defaultframe'); - setStr(pl, 'wmp', 'playcount'); - setStr(pl, 'wmp', 'rate'); - setStr(pl, 'wmp', 'uimode'); - setStr(pl, 'wmp', 'volume'); - break; - - case "rmp": - setBool(pl, 'rmp', 'autostart'); - setBool(pl, 'rmp', 'loop'); - setBool(pl, 'rmp', 'autogotourl'); - setBool(pl, 'rmp', 'center'); - setBool(pl, 'rmp', 'imagestatus'); - setBool(pl, 'rmp', 'maintainaspect'); - setBool(pl, 'rmp', 'nojava'); - setBool(pl, 'rmp', 'prefetch'); - setBool(pl, 'rmp', 'shuffle'); - setStr(pl, 'rmp', 'console'); - setStr(pl, 'rmp', 'controls'); - setStr(pl, 'rmp', 'numloop'); - setStr(pl, 'rmp', 'scriptcallbacks'); - break; - } - - setStr(pl, null, 'src'); - setStr(pl, null, 'id'); - setStr(pl, null, 'name'); - setStr(pl, null, 'vspace'); - setStr(pl, null, 'hspace'); - setStr(pl, null, 'bgcolor'); - setStr(pl, null, 'align'); - setStr(pl, null, 'width'); - setStr(pl, null, 'height'); - - if ((val = ed.dom.getAttrib(fe, "width")) != "") - pl.width = f.width.value = val; - - if ((val = ed.dom.getAttrib(fe, "height")) != "") - pl.height = f.height.value = val; - - oldWidth = pl.width ? parseInt(pl.width) : 0; - oldHeight = pl.height ? parseInt(pl.height) : 0; - } else - oldWidth = oldHeight = 0; - - selectByValue(f, 'media_type', type); - changedType(type); - updateColor('bgcolor_pick', 'bgcolor'); - - TinyMCE_EditableSelects.init(); - generatePreview(); -} - -function insertMedia() { - var fe, f = document.forms[0], h; - - tinyMCEPopup.restoreSelection(); - - if (!AutoValidator.validate(f)) { - tinyMCEPopup.alert(ed.getLang('invalid_data')); - return false; - } - - f.width.value = f.width.value == "" ? 100 : f.width.value; - f.height.value = f.height.value == "" ? 100 : f.height.value; - - fe = ed.selection.getNode(); - if (fe != null && /mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) { - switch (f.media_type.options[f.media_type.selectedIndex].value) { - case "flash": - fe.className = "mceItemFlash"; - break; - - case "flv": - fe.className = "mceItemFlashVideo"; - break; - - case "shockwave": - fe.className = "mceItemShockWave"; - break; - - case "qt": - fe.className = "mceItemQuickTime"; - break; - - case "wmp": - fe.className = "mceItemWindowsMedia"; - break; - - case "rmp": - fe.className = "mceItemRealMedia"; - break; - } - - if (fe.width != f.width.value || fe.height != f.height.value) - ed.execCommand('mceRepaint'); - - fe.title = serializeParameters(); - fe.width = f.width.value; - fe.height = f.height.value; - fe.style.width = f.width.value + (f.width.value.indexOf('%') == -1 ? 'px' : ''); - fe.style.height = f.height.value + (f.height.value.indexOf('%') == -1 ? 'px' : ''); - fe.align = f.align.options[f.align.selectedIndex].value; - } else { - h = ' 0) { - var html = ""; - - html += ''; - - return html; - } - - return ""; -} - -function getType(v) { - var fo, i, c, el, x, f = document.forms[0]; - - fo = ed.getParam("media_types", "flash=swf;flv=flv;shockwave=dcr;qt=mov,qt,mpg,mp3,mp4,mpeg;shockwave=dcr;wmp=avi,wmv,wm,asf,asx,wmx,wvx;rmp=rm,ra,ram").split(';'); - - // YouTube - if (v.match(/watch\?v=(.+)(.*)/)) { - f.width.value = '425'; - f.height.value = '350'; - f.src.value = 'http://www.youtube.com/v/' + v.match(/v=(.*)(.*)/)[0].split('=')[1]; - return 'flash'; - } - - // Google video - if (v.indexOf('http://video.google.com/videoplay?docid=') == 0) { - f.width.value = '425'; - f.height.value = '326'; - f.src.value = 'http://video.google.com/googleplayer.swf?docId=' + v.substring('http://video.google.com/videoplay?docid='.length) + '&hl=en'; - return 'flash'; - } - - for (i=0; i 0 ? s.substring(0, s.length - 1) : s; - - return s; -} - -function setBool(pl, p, n) { - if (typeof(pl[n]) == "undefined") - return; - - document.forms[0].elements[p + "_" + n].checked = pl[n] != 'false'; -} - -function setStr(pl, p, n) { - var f = document.forms[0], e = f.elements[(p != null ? p + "_" : '') + n]; - - if (typeof(pl[n]) == "undefined") - return; - - if (e.type == "text") - e.value = pl[n]; - else - selectByValue(f, (p != null ? p + "_" : '') + n, pl[n]); -} - -function getBool(p, n, d, tv, fv) { - var v = document.forms[0].elements[p + "_" + n].checked; - - tv = typeof(tv) == 'undefined' ? 'true' : "'" + jsEncode(tv) + "'"; - fv = typeof(fv) == 'undefined' ? 'false' : "'" + jsEncode(fv) + "'"; - - return (v == d) ? '' : n + (v ? ':' + tv + ',' : ":\'" + fv + "\',"); -} - -function getStr(p, n, d) { - var e = document.forms[0].elements[(p != null ? p + "_" : "") + n]; - var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value; - - if (n == 'src') - v = tinyMCEPopup.editor.convertURL(v, 'src', null); - - return ((n == d || v == '') ? '' : n + ":'" + jsEncode(v) + "',"); -} - -function getInt(p, n, d) { - var e = document.forms[0].elements[(p != null ? p + "_" : "") + n]; - var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value; - - return ((n == d || v == '') ? '' : n + ":" + v.replace(/[^0-9]+/g, '') + ","); -} - -function jsEncode(s) { - s = s.replace(new RegExp('\\\\', 'g'), '\\\\'); - s = s.replace(new RegExp('"', 'g'), '\\"'); - s = s.replace(new RegExp("'", 'g'), "\\'"); - - return s; -} - -function generatePreview(c) { - var f = document.forms[0], p = document.getElementById('prev'), h = '', cls, pl, n, type, codebase, wp, hp, nw, nh; - - p.innerHTML = ''; - - nw = parseInt(f.width.value); - nh = parseInt(f.height.value); - - if (f.width.value != "" && f.height.value != "") { - if (f.constrain.checked) { - if (c == 'width' && oldWidth != 0) { - wp = nw / oldWidth; - nh = Math.round(wp * nh); - f.height.value = nh; - } else if (c == 'height' && oldHeight != 0) { - hp = nh / oldHeight; - nw = Math.round(hp * nw); - f.width.value = nw; - } - } - } - - if (f.width.value != "") - oldWidth = nw; - - if (f.height.value != "") - oldHeight = nh; - - // After constrain - pl = serializeParameters(); - - switch (f.media_type.options[f.media_type.selectedIndex].value) { - case "flash": - cls = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; - codebase = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'; - type = 'application/x-shockwave-flash'; - break; - - case "shockwave": - cls = 'clsid:166B1BCA-3F9C-11CF-8075-444553540000'; - codebase = 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0'; - type = 'application/x-director'; - break; - - case "qt": - cls = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B'; - codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0'; - type = 'video/quicktime'; - break; - - case "wmp": - cls = ed.getParam('media_wmp6_compatible') ? 'clsid:05589FA1-C356-11CE-BF01-00AA0055595A' : 'clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6'; - codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701'; - type = 'application/x-mplayer2'; - break; - - case "rmp": - cls = 'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA'; - codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701'; - type = 'audio/x-pn-realaudio-plugin'; - break; - } - - if (pl == '') { - p.innerHTML = ''; - return; - } - - pl = tinyMCEPopup.editor.plugins.media._parse(pl); - - if (!pl.src) { - p.innerHTML = ''; - return; - } - - pl.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(pl.src); - pl.width = !pl.width ? 100 : pl.width; - pl.height = !pl.height ? 100 : pl.height; - pl.id = !pl.id ? 'obj' : pl.id; - pl.name = !pl.name ? 'eobj' : pl.name; - pl.align = !pl.align ? '' : pl.align; - - // Avoid annoying warning about insecure items - if (!tinymce.isIE || document.location.protocol != 'https:') { - h += ''; - - for (n in pl) { - h += ''; - - // Add extra url parameter if it's an absolute URL - if (n == 'src' && pl[n].indexOf('://') != -1) - h += ''; - } - } - - h += ''); + + function get(id) { + return document.getElementById(id); + } + + function clone(obj) { + var i, len, copy, attr; + + if (null == obj || "object" != typeof obj) + return obj; + + // Handle Array + if ('length' in obj) { + copy = []; + + for (i = 0, len = obj.length; i < len; ++i) { + copy[i] = clone(obj[i]); + } + + return copy; + } + + // Handle Object + copy = {}; + for (attr in obj) { + if (obj.hasOwnProperty(attr)) + copy[attr] = clone(obj[attr]); + } + + return copy; + } + + function getVal(id) { + var elm = get(id); + + if (elm.nodeName == "SELECT") + return elm.options[elm.selectedIndex].value; + + if (elm.type == "checkbox") + return elm.checked; + + return elm.value; + } + + function setVal(id, value, name) { + if (typeof(value) != 'undefined') { + var elm = get(id); + + if (elm.nodeName == "SELECT") + selectByValue(document.forms[0], id, value); + else if (elm.type == "checkbox") { + if (typeof(value) == 'string') { + value = value.toLowerCase(); + value = (!name && value === 'true') || (name && value === name.toLowerCase()); + } + elm.checked = !!value; + } else + elm.value = value; + } + } + + window.Media = { + init : function() { + var html, editor, self = this; + + self.editor = editor = tinyMCEPopup.editor; + + // Setup file browsers and color pickers + get('filebrowsercontainer').innerHTML = getBrowserHTML('filebrowser','src','media','media'); + get('qtsrcfilebrowsercontainer').innerHTML = getBrowserHTML('qtsrcfilebrowser','quicktime_qtsrc','media','media'); + get('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + get('video_altsource1_filebrowser').innerHTML = getBrowserHTML('video_filebrowser_altsource1','video_altsource1','media','media'); + get('video_altsource2_filebrowser').innerHTML = getBrowserHTML('video_filebrowser_altsource2','video_altsource2','media','media'); + get('audio_altsource1_filebrowser').innerHTML = getBrowserHTML('audio_filebrowser_altsource1','audio_altsource1','media','media'); + get('audio_altsource2_filebrowser').innerHTML = getBrowserHTML('audio_filebrowser_altsource2','audio_altsource2','media','media'); + get('video_poster_filebrowser').innerHTML = getBrowserHTML('filebrowser_poster','video_poster','media','image'); + + html = self.getMediaListHTML('medialist', 'src', 'media', 'media'); + if (html == "") + get("linklistrow").style.display = 'none'; + else + get("linklistcontainer").innerHTML = html; + + if (isVisible('filebrowser')) + get('src').style.width = '230px'; + + if (isVisible('video_filebrowser_altsource1')) + get('video_altsource1').style.width = '220px'; + + if (isVisible('video_filebrowser_altsource2')) + get('video_altsource2').style.width = '220px'; + + if (isVisible('audio_filebrowser_altsource1')) + get('audio_altsource1').style.width = '220px'; + + if (isVisible('audio_filebrowser_altsource2')) + get('audio_altsource2').style.width = '220px'; + + if (isVisible('filebrowser_poster')) + get('video_poster').style.width = '220px'; + + editor.dom.setOuterHTML(get('media_type'), self.getMediaTypeHTML(editor)); + + self.setDefaultDialogSettings(editor); + self.data = clone(tinyMCEPopup.getWindowArg('data')); + self.dataToForm(); + self.preview(); + + updateColor('bgcolor_pick', 'bgcolor'); + }, + + insert : function() { + var editor = tinyMCEPopup.editor; + + this.formToData(); + editor.execCommand('mceRepaint'); + tinyMCEPopup.restoreSelection(); + editor.selection.setNode(editor.plugins.media.dataToImg(this.data)); + tinyMCEPopup.close(); + }, + + preview : function() { + get('prev').innerHTML = this.editor.plugins.media.dataToHtml(this.data, true); + }, + + moveStates : function(to_form, field) { + var data = this.data, editor = this.editor, + mediaPlugin = editor.plugins.media, ext, src, typeInfo, defaultStates, src; + + defaultStates = { + // QuickTime + quicktime_autoplay : true, + quicktime_controller : true, + + // Flash + flash_play : true, + flash_loop : true, + flash_menu : true, + + // WindowsMedia + windowsmedia_autostart : true, + windowsmedia_enablecontextmenu : true, + windowsmedia_invokeurls : true, + + // RealMedia + realmedia_autogotourl : true, + realmedia_imagestatus : true + }; + + function parseQueryParams(str) { + var out = {}; + + if (str) { + tinymce.each(str.split('&'), function(item) { + var parts = item.split('='); + + out[unescape(parts[0])] = unescape(parts[1]); + }); + } + + return out; + }; + + function setOptions(type, names) { + var i, name, formItemName, value, list; + + if (type == data.type || type == 'global') { + names = tinymce.explode(names); + for (i = 0; i < names.length; i++) { + name = names[i]; + formItemName = type == 'global' ? name : type + '_' + name; + + if (type == 'global') + list = data; + else if (type == 'video' || type == 'audio') { + list = data.video.attrs; + + if (!list && !to_form) + data.video.attrs = list = {}; + } else + list = data.params; + + if (list) { + if (to_form) { + setVal(formItemName, list[name], type == 'video' || type == 'audio' ? name : ''); + } else { + delete list[name]; + + value = getVal(formItemName); + if ((type == 'video' || type == 'audio') && value === true) + value = name; + + if (defaultStates[formItemName]) { + if (value !== defaultStates[formItemName]) { + value = "" + value; + list[name] = value; + } + } else if (value) { + value = "" + value; + list[name] = value; + } + } + } + } + } + } + + if (!to_form) { + data.type = get('media_type').options[get('media_type').selectedIndex].value; + data.width = getVal('width'); + data.height = getVal('height'); + + // Switch type based on extension + src = getVal('src'); + if (field == 'src') { + ext = src.replace(/^.*\.([^.]+)$/, '$1'); + if (typeInfo = mediaPlugin.getType(ext)) + data.type = typeInfo.name.toLowerCase(); + + setVal('media_type', data.type); + } + + if (data.type == "video" || data.type == "audio") { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src: getVal('src')}; + } + } + + // Hide all fieldsets and show the one active + get('video_options').style.display = 'none'; + get('audio_options').style.display = 'none'; + get('flash_options').style.display = 'none'; + get('quicktime_options').style.display = 'none'; + get('shockwave_options').style.display = 'none'; + get('windowsmedia_options').style.display = 'none'; + get('realmedia_options').style.display = 'none'; + get('embeddedaudio_options').style.display = 'none'; + + if (get(data.type + '_options')) + get(data.type + '_options').style.display = 'block'; + + setVal('media_type', data.type); + + setOptions('flash', 'play,loop,menu,swliveconnect,quality,scale,salign,wmode,base,flashvars'); + setOptions('quicktime', 'loop,autoplay,cache,controller,correction,enablejavascript,kioskmode,autohref,playeveryframe,targetcache,scale,starttime,endtime,target,qtsrcchokespeed,volume,qtsrc'); + setOptions('shockwave', 'sound,progress,autostart,swliveconnect,swvolume,swstretchstyle,swstretchhalign,swstretchvalign'); + setOptions('windowsmedia', 'autostart,enabled,enablecontextmenu,fullscreen,invokeurls,mute,stretchtofit,windowlessvideo,balance,baseurl,captioningid,currentmarker,currentposition,defaultframe,playcount,rate,uimode,volume'); + setOptions('realmedia', 'autostart,loop,autogotourl,center,imagestatus,maintainaspect,nojava,prefetch,shuffle,console,controls,numloop,scriptcallbacks'); + setOptions('video', 'poster,autoplay,loop,muted,preload,controls'); + setOptions('audio', 'autoplay,loop,preload,controls'); + setOptions('embeddedaudio', 'autoplay,loop,controls'); + setOptions('global', 'id,name,vspace,hspace,bgcolor,align,width,height'); + + if (to_form) { + if (data.type == 'video') { + if (data.video.sources[0]) + setVal('src', data.video.sources[0].src); + + src = data.video.sources[1]; + if (src) + setVal('video_altsource1', src.src); + + src = data.video.sources[2]; + if (src) + setVal('video_altsource2', src.src); + } else if (data.type == 'audio') { + if (data.video.sources[0]) + setVal('src', data.video.sources[0].src); + + src = data.video.sources[1]; + if (src) + setVal('audio_altsource1', src.src); + + src = data.video.sources[2]; + if (src) + setVal('audio_altsource2', src.src); + } else { + // Check flash vars + if (data.type == 'flash') { + tinymce.each(editor.getParam('flash_video_player_flashvars', {url : '$url', poster : '$poster'}), function(value, name) { + if (value == '$url') + data.params.src = parseQueryParams(data.params.flashvars)[name] || data.params.src || ''; + }); + } + + setVal('src', data.params.src); + } + } else { + src = getVal("src"); + + // YouTube *NEW* + if (src.match(/youtu.be\/[a-z1-9.-_]+/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/youtu.be\/([a-z1-9.-_]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + + // YouTube + if (src.match(/youtube.com(.+)v=([^&]+)/)) { + data.width = 425; + data.height = 350; + data.params.frameborder = '0'; + data.type = 'iframe'; + src = 'http://www.youtube.com/embed/' + src.match(/v=([^&]+)/)[1]; + setVal('src', src); + setVal('media_type', data.type); + } + + // Google video + if (src.match(/video.google.com(.+)docid=([^&]+)/)) { + data.width = 425; + data.height = 326; + data.type = 'flash'; + src = 'http://video.google.com/googleplayer.swf?docId=' + src.match(/docid=([^&]+)/)[1] + '&hl=en'; + setVal('src', src); + setVal('media_type', data.type); + } + + if (data.type == 'video') { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src : src}; + + src = getVal("video_altsource1"); + if (src) + data.video.sources[1] = {src : src}; + + src = getVal("video_altsource2"); + if (src) + data.video.sources[2] = {src : src}; + } else if (data.type == 'audio') { + if (!data.video.sources) + data.video.sources = []; + + data.video.sources[0] = {src : src}; + + src = getVal("audio_altsource1"); + if (src) + data.video.sources[1] = {src : src}; + + src = getVal("audio_altsource2"); + if (src) + data.video.sources[2] = {src : src}; + } else + data.params.src = src; + + // Set default size + setVal('width', data.width || (data.type == 'audio' ? 300 : 320)); + setVal('height', data.height || (data.type == 'audio' ? 32 : 240)); + } + }, + + dataToForm : function() { + this.moveStates(true); + }, + + formToData : function(field) { + if (field == "width" || field == "height") + this.changeSize(field); + + if (field == 'source') { + this.moveStates(false, field); + setVal('source', this.editor.plugins.media.dataToHtml(this.data)); + this.panel = 'source'; + } else { + if (this.panel == 'source') { + this.data = clone(this.editor.plugins.media.htmlToData(getVal('source'))); + this.dataToForm(); + this.panel = ''; + } + + this.moveStates(false, field); + this.preview(); + } + }, + + beforeResize : function() { + this.width = parseInt(getVal('width') || (this.data.type == 'audio' ? "300" : "320"), 10); + this.height = parseInt(getVal('height') || (this.data.type == 'audio' ? "32" : "240"), 10); + }, + + changeSize : function(type) { + var width, height, scale, size; + + if (get('constrain').checked) { + width = parseInt(getVal('width') || (this.data.type == 'audio' ? "300" : "320"), 10); + height = parseInt(getVal('height') || (this.data.type == 'audio' ? "32" : "240"), 10); + + if (type == 'width') { + this.height = Math.round((width / this.width) * height); + setVal('height', this.height); + } else { + this.width = Math.round((height / this.height) * width); + setVal('width', this.width); + } + } + }, + + getMediaListHTML : function() { + if (typeof(tinyMCEMediaList) != "undefined" && tinyMCEMediaList.length > 0) { + var html = ""; + + html += ''; + + return html; + } + + return ""; + }, + + getMediaTypeHTML : function(editor) { + function option(media_type){ + return '' + } + var html = ""; + html += ''; + return html; + }, + + setDefaultDialogSettings : function(editor) { + var defaultDialogSettings = editor.getParam("media_dialog_defaults", {}); + tinymce.each(defaultDialogSettings, function(v, k) { + setVal(k, v); + }); + } + }; + + tinyMCEPopup.requireLangPack(); + tinyMCEPopup.onInit.add(function() { + Media.init(); + }); +})(); diff --git a/js/tiny_mce/plugins/media/langs/en_dlg.js b/js/tiny_mce/plugins/media/langs/en_dlg.js index 6d0a996f..ecef3a80 100644 --- a/js/tiny_mce/plugins/media/langs/en_dlg.js +++ b/js/tiny_mce/plugins/media/langs/en_dlg.js @@ -1,103 +1 @@ -tinyMCE.addI18n('en.media_dlg',{ -title:"Insert / edit embedded media", -general:"General", -advanced:"Advanced", -file:"File/URL", -list:"List", -size:"Dimensions", -preview:"Preview", -constrain_proportions:"Constrain proportions", -type:"Type", -id:"Id", -name:"Name", -class_name:"Class", -vspace:"V-Space", -hspace:"H-Space", -play:"Auto play", -loop:"Loop", -menu:"Show menu", -quality:"Quality", -scale:"Scale", -align:"Align", -salign:"SAlign", -wmode:"WMode", -bgcolor:"Background", -base:"Base", -flashvars:"Flashvars", -liveconnect:"SWLiveConnect", -autohref:"AutoHREF", -cache:"Cache", -hidden:"Hidden", -controller:"Controller", -kioskmode:"Kiosk mode", -playeveryframe:"Play every frame", -targetcache:"Target cache", -correction:"No correction", -enablejavascript:"Enable JavaScript", -starttime:"Start time", -endtime:"End time", -href:"Href", -qtsrcchokespeed:"Choke speed", -target:"Target", -volume:"Volume", -autostart:"Auto start", -enabled:"Enabled", -fullscreen:"Fullscreen", -invokeurls:"Invoke URLs", -mute:"Mute", -stretchtofit:"Stretch to fit", -windowlessvideo:"Windowless video", -balance:"Balance", -baseurl:"Base URL", -captioningid:"Captioning id", -currentmarker:"Current marker", -currentposition:"Current position", -defaultframe:"Default frame", -playcount:"Play count", -rate:"Rate", -uimode:"UI Mode", -flash_options:"Flash options", -qt_options:"Quicktime options", -wmp_options:"Windows media player options", -rmp_options:"Real media player options", -shockwave_options:"Shockwave options", -autogotourl:"Auto goto URL", -center:"Center", -imagestatus:"Image status", -maintainaspect:"Maintain aspect", -nojava:"No java", -prefetch:"Prefetch", -shuffle:"Shuffle", -console:"Console", -numloop:"Num loops", -controls:"Controls", -scriptcallbacks:"Script callbacks", -swstretchstyle:"Stretch style", -swstretchhalign:"Stretch H-Align", -swstretchvalign:"Stretch V-Align", -sound:"Sound", -progress:"Progress", -qtsrc:"QT Src", -qt_stream_warn:"Streamed rtsp resources should be added to the QT Src field under the advanced tab.\nYou should also add a non streamed version to the Src field..", -align_top:"Top", -align_right:"Right", -align_bottom:"Bottom", -align_left:"Left", -align_center:"Center", -align_top_left:"Top left", -align_top_right:"Top right", -align_bottom_left:"Bottom left", -align_bottom_right:"Bottom right", -flv_options:"Flash video options", -flv_scalemode:"Scale mode", -flv_buffer:"Buffer", -flv_startimage:"Start image", -flv_starttime:"Start time", -flv_defaultvolume:"Default volumne", -flv_hiddengui:"Hidden GUI", -flv_autostart:"Auto start", -flv_loop:"Loop", -flv_showscalemodes:"Show scale modes", -flv_smoothvideo:"Smooth video", -flv_jscallback:"JS Callback" -}); \ No newline at end of file +tinyMCE.addI18n('en.media_dlg',{list:"List",file:"File/URL",advanced:"Advanced",general:"General",title:"Insert/Edit Embedded Media","align_top_left":"Top Left","align_center":"Center","align_left":"Left","align_bottom":"Bottom","align_right":"Right","align_top":"Top","qt_stream_warn":"Streamed RTSP resources should be added to the QT Source field under the Advanced tab.\nYou should also add a non-streamed version to the Source field.",qtsrc:"QT Source",progress:"Progress",sound:"Sound",swstretchvalign:"Stretch V-Align",swstretchhalign:"Stretch H-Align",swstretchstyle:"Stretch Style",scriptcallbacks:"Script Callbacks","align_top_right":"Top Right",uimode:"UI Mode",rate:"Rate",playcount:"Play Count",defaultframe:"Default Frame",currentposition:"Current Position",currentmarker:"Current Marker",captioningid:"Captioning ID",baseurl:"Base URL",balance:"Balance",windowlessvideo:"Windowless Video",stretchtofit:"Stretch to Fit",mute:"Mute",invokeurls:"Invoke URLs",fullscreen:"Full Screen",enabled:"Enabled",autostart:"Auto Start",volume:"Volume",target:"Target",qtsrcchokespeed:"Choke Speed",href:"HREF",endtime:"End Time",starttime:"Start Time",enablejavascript:"Enable JavaScript",correction:"No Correction",targetcache:"Target Cache",playeveryframe:"Play Every Frame",kioskmode:"Kiosk Mode",controller:"Controller",menu:"Show Menu",loop:"Loop",play:"Auto Play",hspace:"H-Space",vspace:"V-Space","class_name":"Class",name:"Name",id:"ID",type:"Type",size:"Dimensions",preview:"Preview","constrain_proportions":"Constrain Proportions",controls:"Controls",numloop:"Num Loops",console:"Console",cache:"Cache",autohref:"Auto HREF",liveconnect:"SWLiveConnect",flashvars:"Flash Vars",base:"Base",bgcolor:"Background",wmode:"WMode",salign:"SAlign",align:"Align",scale:"Scale",quality:"Quality",shuffle:"Shuffle",prefetch:"Prefetch",nojava:"No Java",maintainaspect:"Maintain Aspect",imagestatus:"Image Status",center:"Center",autogotourl:"Auto Goto URL","shockwave_options":"Shockwave Options","rmp_options":"Real Media Player Options","wmp_options":"Windows Media Player Options","qt_options":"QuickTime Options","flash_options":"Flash Options",hidden:"Hidden","align_bottom_left":"Bottom Left","align_bottom_right":"Bottom Right","html5_video_options":"HTML5 Video Options",altsource1:"Alternative source 1",altsource2:"Alternative source 2",preload:"Preload",poster:"Poster",source:"Source","html5_audio_options":"Audio Options","preload_none":"Don\'t Preload","preload_metadata":"Preload video metadata","preload_auto":"Let user\'s browser decide", "embedded_audio_options":"Embedded Audio Options", video:"HTML5 Video", audio:"HTML5 Audio", flash:"Flash", quicktime:"QuickTime", shockwave:"Shockwave", windowsmedia:"Windows Media", realmedia:"Real Media", iframe:"Iframe", embeddedaudio:"Embedded Audio" }); diff --git a/js/tiny_mce/plugins/media/media.htm b/js/tiny_mce/plugins/media/media.htm index 73a903f7..50efe918 100644 --- a/js/tiny_mce/plugins/media/media.htm +++ b/js/tiny_mce/plugins/media/media.htm @@ -1,817 +1,922 @@ - - - - {#media_dlg.title} - - - - - - - - - -
    - - -
    -
    -
    - {#media_dlg.general} - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - -
     
    -
    - - - - - - -
    x   
    -
    -
    - -
    - {#media_dlg.preview} - -
    -
    - -
    -
    - {#media_dlg.advanced} - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - -
     
    -
    -
    - -
    - {#media_dlg.flash_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - - - - - - - -
    -
    - -
    - {#media_dlg.flv_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    -
    - -
    - {#media_dlg.qt_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    -  
    - - - - - -
     
    -
    -
    - -
    - {#media_dlg.wmp_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    -
    - -
    - {#media_dlg.rmp_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    -   -
    -
    - -
    - {#media_dlg.shockwave_options} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    - - - - - -
    -
    -
    -
    -
    - -
    - - -
    -
    - - + + + + {#media_dlg.title} + + + + + + + + + +
    + + +
    +
    +
    + {#media_dlg.general} + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + +
     
    +
    + + + + + + +
    x   
    +
    +
    + +
    + {#media_dlg.preview} + +
    +
    + +
    +
    + {#media_dlg.advanced} + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
     
    +
    +
    + +
    + {#media_dlg.html5_video_options} + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
     
    +
    + + + + + +
     
    +
    + + + + + +
     
    +
    + +
    + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    + +
    + {#media_dlg.embedded_audio_options} + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    + +
    + {#media_dlg.html5_audio_options} + + + + + + + + + + + + + + + + +
    + + + + + +
     
    +
    + + + + + +
     
    +
    + +
    + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    + +
    + {#media_dlg.flash_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + + + + + + + +
    +
    + +
    + {#media_dlg.qt_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +  
    + + + + + +
     
    +
    +
    + +
    + {#media_dlg.wmp_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    + +
    + {#media_dlg.rmp_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +   +
    +
    + +
    + {#media_dlg.shockwave_options} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    + + + + + +
    +
    +
    +
    + +
    +
    + {#media_dlg.source} + +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/media/moxieplayer.swf b/js/tiny_mce/plugins/media/moxieplayer.swf new file mode 100644 index 00000000..585d772d Binary files /dev/null and b/js/tiny_mce/plugins/media/moxieplayer.swf differ diff --git a/js/tiny_mce/plugins/nonbreaking/editor_plugin.js b/js/tiny_mce/plugins/nonbreaking/editor_plugin.js index f2dbbff2..687f5486 100644 --- a/js/tiny_mce/plugins/nonbreaking/editor_plugin.js +++ b/js/tiny_mce/plugins/nonbreaking/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.Nonbreaking",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceNonBreaking",function(){a.execCommand("mceInsertContent",false,(a.plugins.visualchars&&a.plugins.visualchars.state)?'·':" ")});a.addButton("nonbreaking",{title:"nonbreaking.nonbreaking_desc",cmd:"mceNonBreaking"});if(a.getParam("nonbreaking_force_tab")){a.onKeyDown.add(function(d,f){if(tinymce.isIE&&f.keyCode==9){d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking");tinymce.dom.Event.cancel(f)}})}},getInfo:function(){return{longname:"Nonbreaking space",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("nonbreaking",tinymce.plugins.Nonbreaking)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.Nonbreaking",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceNonBreaking",function(){a.execCommand("mceInsertContent",false,(a.plugins.visualchars&&a.plugins.visualchars.state)?' ':" ")});a.addButton("nonbreaking",{title:"nonbreaking.nonbreaking_desc",cmd:"mceNonBreaking"});if(a.getParam("nonbreaking_force_tab")){a.onKeyDown.add(function(d,f){if(f.keyCode==9){f.preventDefault();d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking");d.execCommand("mceNonBreaking")}})}},getInfo:function(){return{longname:"Nonbreaking space",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("nonbreaking",tinymce.plugins.Nonbreaking)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/nonbreaking/editor_plugin_src.js b/js/tiny_mce/plugins/nonbreaking/editor_plugin_src.js index e3b078bf..0a048b37 100644 --- a/js/tiny_mce/plugins/nonbreaking/editor_plugin_src.js +++ b/js/tiny_mce/plugins/nonbreaking/editor_plugin_src.js @@ -1,53 +1,54 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.Nonbreaking', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - - // Register commands - ed.addCommand('mceNonBreaking', function() { - ed.execCommand('mceInsertContent', false, (ed.plugins.visualchars && ed.plugins.visualchars.state) ? '·' : ' '); - }); - - // Register buttons - ed.addButton('nonbreaking', {title : 'nonbreaking.nonbreaking_desc', cmd : 'mceNonBreaking'}); - - if (ed.getParam('nonbreaking_force_tab')) { - ed.onKeyDown.add(function(ed, e) { - if (tinymce.isIE && e.keyCode == 9) { - ed.execCommand('mceNonBreaking'); - ed.execCommand('mceNonBreaking'); - ed.execCommand('mceNonBreaking'); - tinymce.dom.Event.cancel(e); - } - }); - } - }, - - getInfo : function() { - return { - longname : 'Nonbreaking space', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - - // Private methods - }); - - // Register plugin - tinymce.PluginManager.add('nonbreaking', tinymce.plugins.Nonbreaking); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.Nonbreaking', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + + // Register commands + ed.addCommand('mceNonBreaking', function() { + ed.execCommand('mceInsertContent', false, (ed.plugins.visualchars && ed.plugins.visualchars.state) ? ' ' : ' '); + }); + + // Register buttons + ed.addButton('nonbreaking', {title : 'nonbreaking.nonbreaking_desc', cmd : 'mceNonBreaking'}); + + if (ed.getParam('nonbreaking_force_tab')) { + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode == 9) { + e.preventDefault(); + + ed.execCommand('mceNonBreaking'); + ed.execCommand('mceNonBreaking'); + ed.execCommand('mceNonBreaking'); + } + }); + } + }, + + getInfo : function() { + return { + longname : 'Nonbreaking space', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + + // Private methods + }); + + // Register plugin + tinymce.PluginManager.add('nonbreaking', tinymce.plugins.Nonbreaking); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/noneditable/editor_plugin.js b/js/tiny_mce/plugins/noneditable/editor_plugin.js index 9945cd85..2d60138e 100644 --- a/js/tiny_mce/plugins/noneditable/editor_plugin.js +++ b/js/tiny_mce/plugins/noneditable/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.dom.Event;tinymce.create("tinymce.plugins.NonEditablePlugin",{init:function(d,e){var f=this,c,b;f.editor=d;c=d.getParam("noneditable_editable_class","mceEditable");b=d.getParam("noneditable_noneditable_class","mceNonEditable");d.onNodeChange.addToTop(function(h,g,k){var j,i;j=h.dom.getParent(h.selection.getStart(),function(l){return h.dom.hasClass(l,b)});i=h.dom.getParent(h.selection.getEnd(),function(l){return h.dom.hasClass(l,b)});if(j||i){f._setDisabled(1);return false}else{f._setDisabled(0)}})},getInfo:function(){return{longname:"Non editable elements",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_block:function(c,d){var b=d.keyCode;if((b>32&&b<41)||(b>111&&b<124)){return}return a.cancel(d)},_setDisabled:function(d){var c=this,b=c.editor;tinymce.each(b.controlManager.controls,function(e){e.setDisabled(d)});if(d!==c.disabled){if(d){b.onKeyDown.addToTop(c._block);b.onKeyPress.addToTop(c._block);b.onKeyUp.addToTop(c._block);b.onPaste.addToTop(c._block)}else{b.onKeyDown.remove(c._block);b.onKeyPress.remove(c._block);b.onKeyUp.remove(c._block);b.onPaste.remove(c._block)}c.disabled=d}}});tinymce.PluginManager.add("noneditable",tinymce.plugins.NonEditablePlugin)})(); \ No newline at end of file +(function(){var a=tinymce.dom.Event;tinymce.create("tinymce.plugins.NonEditablePlugin",{init:function(d,e){var f=this,c,b,g;f.editor=d;c=d.getParam("noneditable_editable_class","mceEditable");b=d.getParam("noneditable_noneditable_class","mceNonEditable");d.onNodeChange.addToTop(function(i,h,l){var k,j;k=i.dom.getParent(i.selection.getStart(),function(m){return i.dom.hasClass(m,b)});j=i.dom.getParent(i.selection.getEnd(),function(m){return i.dom.hasClass(m,b)});if(k||j){g=1;f._setDisabled(1);return false}else{if(g==1){f._setDisabled(0);g=0}}})},getInfo:function(){return{longname:"Non editable elements",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_block:function(c,d){var b=d.keyCode;if((b>32&&b<41)||(b>111&&b<124)){return}return a.cancel(d)},_setDisabled:function(d){var c=this,b=c.editor;tinymce.each(b.controlManager.controls,function(e){e.setDisabled(d)});if(d!==c.disabled){if(d){b.onKeyDown.addToTop(c._block);b.onKeyPress.addToTop(c._block);b.onKeyUp.addToTop(c._block);b.onPaste.addToTop(c._block);b.onContextMenu.addToTop(c._block)}else{b.onKeyDown.remove(c._block);b.onKeyPress.remove(c._block);b.onKeyUp.remove(c._block);b.onPaste.remove(c._block);b.onContextMenu.remove(c._block)}c.disabled=d}}});tinymce.PluginManager.add("noneditable",tinymce.plugins.NonEditablePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/noneditable/editor_plugin_src.js b/js/tiny_mce/plugins/noneditable/editor_plugin_src.js index 656c971b..47b8a3be 100644 --- a/js/tiny_mce/plugins/noneditable/editor_plugin_src.js +++ b/js/tiny_mce/plugins/noneditable/editor_plugin_src.js @@ -1,90 +1,95 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var Event = tinymce.dom.Event; - - tinymce.create('tinymce.plugins.NonEditablePlugin', { - init : function(ed, url) { - var t = this, editClass, nonEditClass; - - t.editor = ed; - editClass = ed.getParam("noneditable_editable_class", "mceEditable"); - nonEditClass = ed.getParam("noneditable_noneditable_class", "mceNonEditable"); - - ed.onNodeChange.addToTop(function(ed, cm, n) { - var sc, ec; - - // Block if start or end is inside a non editable element - sc = ed.dom.getParent(ed.selection.getStart(), function(n) { - return ed.dom.hasClass(n, nonEditClass); - }); - - ec = ed.dom.getParent(ed.selection.getEnd(), function(n) { - return ed.dom.hasClass(n, nonEditClass); - }); - - // Block or unblock - if (sc || ec) { - t._setDisabled(1); - return false; - } else - t._setDisabled(0); - }); - }, - - getInfo : function() { - return { - longname : 'Non editable elements', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - _block : function(ed, e) { - var k = e.keyCode; - - // Don't block arrow keys, pg up/down, and F1-F12 - if ((k > 32 && k < 41) || (k > 111 && k < 124)) - return; - - return Event.cancel(e); - }, - - _setDisabled : function(s) { - var t = this, ed = t.editor; - - tinymce.each(ed.controlManager.controls, function(c) { - c.setDisabled(s); - }); - - if (s !== t.disabled) { - if (s) { - ed.onKeyDown.addToTop(t._block); - ed.onKeyPress.addToTop(t._block); - ed.onKeyUp.addToTop(t._block); - ed.onPaste.addToTop(t._block); - } else { - ed.onKeyDown.remove(t._block); - ed.onKeyPress.remove(t._block); - ed.onKeyUp.remove(t._block); - ed.onPaste.remove(t._block); - } - - t.disabled = s; - } - } - }); - - // Register plugin - tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var Event = tinymce.dom.Event; + + tinymce.create('tinymce.plugins.NonEditablePlugin', { + init : function(ed, url) { + var t = this, editClass, nonEditClass, state; + + t.editor = ed; + editClass = ed.getParam("noneditable_editable_class", "mceEditable"); + nonEditClass = ed.getParam("noneditable_noneditable_class", "mceNonEditable"); + + ed.onNodeChange.addToTop(function(ed, cm, n) { + var sc, ec; + + // Block if start or end is inside a non editable element + sc = ed.dom.getParent(ed.selection.getStart(), function(n) { + return ed.dom.hasClass(n, nonEditClass); + }); + + ec = ed.dom.getParent(ed.selection.getEnd(), function(n) { + return ed.dom.hasClass(n, nonEditClass); + }); + + // Block or unblock + if (sc || ec) { + state = 1; + t._setDisabled(1); + return false; + } else if (state == 1) { + t._setDisabled(0); + state = 0; + } + }); + }, + + getInfo : function() { + return { + longname : 'Non editable elements', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _block : function(ed, e) { + var k = e.keyCode; + + // Don't block arrow keys, pg up/down, and F1-F12 + if ((k > 32 && k < 41) || (k > 111 && k < 124)) + return; + + return Event.cancel(e); + }, + + _setDisabled : function(s) { + var t = this, ed = t.editor; + + tinymce.each(ed.controlManager.controls, function(c) { + c.setDisabled(s); + }); + + if (s !== t.disabled) { + if (s) { + ed.onKeyDown.addToTop(t._block); + ed.onKeyPress.addToTop(t._block); + ed.onKeyUp.addToTop(t._block); + ed.onPaste.addToTop(t._block); + ed.onContextMenu.addToTop(t._block); + } else { + ed.onKeyDown.remove(t._block); + ed.onKeyPress.remove(t._block); + ed.onKeyUp.remove(t._block); + ed.onPaste.remove(t._block); + ed.onContextMenu.remove(t._block); + } + + t.disabled = s; + } + } + }); + + // Register plugin + tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/pagebreak/editor_plugin.js b/js/tiny_mce/plugins/pagebreak/editor_plugin.js index a212f696..35085e8a 100644 --- a/js/tiny_mce/plugins/pagebreak/editor_plugin.js +++ b/js/tiny_mce/plugins/pagebreak/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.PageBreakPlugin",{init:function(b,d){var f='',a="mcePageBreak",c=b.getParam("pagebreak_separator",""),e;e=new RegExp(c.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(g){return"\\"+g}),"g");b.addCommand("mcePageBreak",function(){b.execCommand("mceInsertContent",0,f)});b.addButton("pagebreak",{title:"pagebreak.desc",cmd:a});b.onInit.add(function(){if(b.settings.content_css!==false){b.dom.loadCSS(d+"/css/content.css")}if(b.theme.onResolveName){b.theme.onResolveName.add(function(g,h){if(h.node.nodeName=="IMG"&&b.dom.hasClass(h.node,a)){h.name="pagebreak"}})}});b.onClick.add(function(g,h){h=h.target;if(h.nodeName==="IMG"&&g.dom.hasClass(h,a)){g.selection.select(h)}});b.onNodeChange.add(function(h,g,i){g.setActive("pagebreak",i.nodeName==="IMG"&&h.dom.hasClass(i,a))});b.onBeforeSetContent.add(function(g,h){h.content=h.content.replace(e,f)});b.onPostProcess.add(function(g,h){if(h.get){h.content=h.content.replace(/]+>/g,function(i){if(i.indexOf('class="mcePageBreak')!==-1){i=c}return i})}})},getInfo:function(){return{longname:"PageBreak",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("pagebreak",tinymce.plugins.PageBreakPlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.PageBreakPlugin",{init:function(b,d){var f='',a="mcePageBreak",c=b.getParam("pagebreak_separator",""),e;e=new RegExp(c.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(g){return"\\"+g}),"g");b.addCommand("mcePageBreak",function(){b.execCommand("mceInsertContent",0,f)});b.addButton("pagebreak",{title:"pagebreak.desc",cmd:a});b.onInit.add(function(){if(b.theme.onResolveName){b.theme.onResolveName.add(function(g,h){if(h.node.nodeName=="IMG"&&b.dom.hasClass(h.node,a)){h.name="pagebreak"}})}});b.onClick.add(function(g,h){h=h.target;if(h.nodeName==="IMG"&&g.dom.hasClass(h,a)){g.selection.select(h)}});b.onNodeChange.add(function(h,g,i){g.setActive("pagebreak",i.nodeName==="IMG"&&h.dom.hasClass(i,a))});b.onBeforeSetContent.add(function(g,h){h.content=h.content.replace(e,f)});b.onPostProcess.add(function(g,h){if(h.get){h.content=h.content.replace(/]+>/g,function(i){if(i.indexOf('class="mcePageBreak')!==-1){i=c}return i})}})},getInfo:function(){return{longname:"PageBreak",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("pagebreak",tinymce.plugins.PageBreakPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/pagebreak/editor_plugin_src.js b/js/tiny_mce/plugins/pagebreak/editor_plugin_src.js index 4e1eb0a7..fc3b3b4a 100644 --- a/js/tiny_mce/plugins/pagebreak/editor_plugin_src.js +++ b/js/tiny_mce/plugins/pagebreak/editor_plugin_src.js @@ -1,77 +1,74 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.PageBreakPlugin', { - init : function(ed, url) { - var pb = '', cls = 'mcePageBreak', sep = ed.getParam('pagebreak_separator', ''), pbRE; - - pbRE = new RegExp(sep.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {return '\\' + a;}), 'g'); - - // Register commands - ed.addCommand('mcePageBreak', function() { - ed.execCommand('mceInsertContent', 0, pb); - }); - - // Register buttons - ed.addButton('pagebreak', {title : 'pagebreak.desc', cmd : cls}); - - ed.onInit.add(function() { - if (ed.settings.content_css !== false) - ed.dom.loadCSS(url + "/css/content.css"); - - if (ed.theme.onResolveName) { - ed.theme.onResolveName.add(function(th, o) { - if (o.node.nodeName == 'IMG' && ed.dom.hasClass(o.node, cls)) - o.name = 'pagebreak'; - }); - } - }); - - ed.onClick.add(function(ed, e) { - e = e.target; - - if (e.nodeName === 'IMG' && ed.dom.hasClass(e, cls)) - ed.selection.select(e); - }); - - ed.onNodeChange.add(function(ed, cm, n) { - cm.setActive('pagebreak', n.nodeName === 'IMG' && ed.dom.hasClass(n, cls)); - }); - - ed.onBeforeSetContent.add(function(ed, o) { - o.content = o.content.replace(pbRE, pb); - }); - - ed.onPostProcess.add(function(ed, o) { - if (o.get) - o.content = o.content.replace(/]+>/g, function(im) { - if (im.indexOf('class="mcePageBreak') !== -1) - im = sep; - - return im; - }); - }); - }, - - getInfo : function() { - return { - longname : 'PageBreak', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('pagebreak', tinymce.plugins.PageBreakPlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.PageBreakPlugin', { + init : function(ed, url) { + var pb = '', cls = 'mcePageBreak', sep = ed.getParam('pagebreak_separator', ''), pbRE; + + pbRE = new RegExp(sep.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {return '\\' + a;}), 'g'); + + // Register commands + ed.addCommand('mcePageBreak', function() { + ed.execCommand('mceInsertContent', 0, pb); + }); + + // Register buttons + ed.addButton('pagebreak', {title : 'pagebreak.desc', cmd : cls}); + + ed.onInit.add(function() { + if (ed.theme.onResolveName) { + ed.theme.onResolveName.add(function(th, o) { + if (o.node.nodeName == 'IMG' && ed.dom.hasClass(o.node, cls)) + o.name = 'pagebreak'; + }); + } + }); + + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'IMG' && ed.dom.hasClass(e, cls)) + ed.selection.select(e); + }); + + ed.onNodeChange.add(function(ed, cm, n) { + cm.setActive('pagebreak', n.nodeName === 'IMG' && ed.dom.hasClass(n, cls)); + }); + + ed.onBeforeSetContent.add(function(ed, o) { + o.content = o.content.replace(pbRE, pb); + }); + + ed.onPostProcess.add(function(ed, o) { + if (o.get) + o.content = o.content.replace(/]+>/g, function(im) { + if (im.indexOf('class="mcePageBreak') !== -1) + im = sep; + + return im; + }); + }); + }, + + getInfo : function() { + return { + longname : 'PageBreak', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('pagebreak', tinymce.plugins.PageBreakPlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/paste/editor_plugin.js b/js/tiny_mce/plugins/paste/editor_plugin.js index 78fd6d73..e47a5c63 100644 --- a/js/tiny_mce/plugins/paste/editor_plugin.js +++ b/js/tiny_mce/plugins/paste/editor_plugin.js @@ -1 +1 @@ -(function(){var c=tinymce.each,d=null,a={paste_auto_cleanup_on_paste:true,paste_block_drop:false,paste_retain_style_properties:"none",paste_strip_class_attributes:"mso",paste_remove_spans:false,paste_remove_styles:false,paste_remove_styles_if_webkit:true,paste_convert_middot_lists:true,paste_convert_headers_to_strong:false,paste_dialog_width:"450",paste_dialog_height:"400",paste_text_use_dialog:false,paste_text_sticky:false,paste_text_notifyalways:false,paste_text_linebreaktype:"p",paste_text_replacements:[[/\u2026/g,"..."],[/[\x93\x94\u201c\u201d]/g,'"'],[/[\x60\x91\x92\u2018\u2019]/g,"'"]]};function b(e,f){return e.getParam(f,a[f])}tinymce.create("tinymce.plugins.PastePlugin",{init:function(e,f){var g=this;g.editor=e;g.url=f;g.onPreProcess=new tinymce.util.Dispatcher(g);g.onPostProcess=new tinymce.util.Dispatcher(g);g.onPreProcess.add(g._preProcess);g.onPostProcess.add(g._postProcess);g.onPreProcess.add(function(j,k){e.execCallback("paste_preprocess",j,k)});g.onPostProcess.add(function(j,k){e.execCallback("paste_postprocess",j,k)});e.pasteAsPlainText=false;function i(l,j){var k=e.dom;g.onPreProcess.dispatch(g,l);l.node=k.create("div",0,l.content);g.onPostProcess.dispatch(g,l);l.content=e.serializer.serialize(l.node,{getInner:1});if((!j)&&(e.pasteAsPlainText)){g._insertPlainText(e,k,l.content);if(!b(e,"paste_text_sticky")){e.pasteAsPlainText=false;e.controlManager.setActive("pastetext",false)}}else{if(/<(p|h[1-6]|ul|ol)/.test(l.content)){g._insertBlockContent(e,k,l.content)}else{g._insert(l.content)}}}e.addCommand("mceInsertClipboardContent",function(j,k){i(k,true)});if(!b(e,"paste_text_use_dialog")){e.addCommand("mcePasteText",function(k,j){var l=tinymce.util.Cookie;e.pasteAsPlainText=!e.pasteAsPlainText;e.controlManager.setActive("pastetext",e.pasteAsPlainText);if((e.pasteAsPlainText)&&(!l.get("tinymcePasteText"))){if(b(e,"paste_text_sticky")){e.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.")}else{e.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode.")}if(!b(e,"paste_text_notifyalways")){l.set("tinymcePasteText","1",new Date(new Date().getFullYear()+1,12,31))}}})}e.addButton("pastetext",{title:"paste.paste_text_desc",cmd:"mcePasteText"});e.addButton("selectall",{title:"paste.selectall_desc",cmd:"selectall"});function h(s){var m,q,k,l=e.selection,p=e.dom,r=e.getBody(),j;if(p.get("_mcePaste")){return}m=p.add(r,"div",{id:"_mcePaste","class":"mcePaste"},"\uFEFF");if(r!=e.getDoc().body){j=p.getPos(e.selection.getStart(),r).y}else{j=r.scrollTop}p.setStyles(m,{position:"absolute",left:-10000,top:j,width:1,height:1,overflow:"hidden"});if(tinymce.isIE){k=p.doc.body.createTextRange();k.moveToElementText(m);k.execCommand("Paste");p.remove(m);if(m.innerHTML==="\uFEFF"){e.execCommand("mcePasteWord");s.preventDefault();return}i({content:m.innerHTML});return tinymce.dom.Event.cancel(s)}else{function o(n){n.preventDefault()}p.bind(e.getDoc(),"mousedown",o);p.bind(e.getDoc(),"keydown",o);q=e.selection.getRng();m=m.firstChild;k=e.getDoc().createRange();k.setStart(m,0);k.setEnd(m,1);l.setRng(k);window.setTimeout(function(){var t="",n=p.select("div.mcePaste");c(n,function(u){c(p.select("div.mcePaste",u),function(v){p.remove(v,1)});t+=(p.select("> span.Apple-style-span div",u)[0]||p.select("> span.Apple-style-span",u)[0]||u).innerHTML});c(n,function(u){p.remove(u)});if(q){l.setRng(q)}i({content:t});p.unbind(e.getDoc(),"mousedown",o);p.unbind(e.getDoc(),"keydown",o)},0)}}if(b(e,"paste_auto_cleanup_on_paste")){if(tinymce.isOpera||/Firefox\/2/.test(navigator.userAgent)){e.onKeyDown.add(function(j,k){if(((tinymce.isMac?k.metaKey:k.ctrlKey)&&k.keyCode==86)||(k.shiftKey&&k.keyCode==45)){h(k)}})}else{e.onPaste.addToTop(function(j,k){return h(k)})}}if(b(e,"paste_block_drop")){e.onInit.add(function(){e.dom.bind(e.getBody(),["dragend","dragover","draggesture","dragdrop","drop","drag"],function(j){j.preventDefault();j.stopPropagation();return false})})}g._legacySupport()},getInfo:function(){return{longname:"Paste text/word",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_preProcess:function(i,f){var l=this.editor,k=f.content,q=tinymce.grep,p=tinymce.explode,g=tinymce.trim,m,j;function e(h){c(h,function(o){if(o.constructor==RegExp){k=k.replace(o,"")}else{k=k.replace(o[0],o[1])}})}if(/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(k)||f.wordContent){f.wordContent=true;e([/^\s*( )+/gi,/( |]*>)+\s*$/gi]);if(b(l,"paste_convert_headers_to_strong")){k=k.replace(/

    ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,"

    $1

    ")}if(b(l,"paste_convert_middot_lists")){e([[//gi,"$&__MCE_ITEM__"],[/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi,"$1__MCE_ITEM__"]])}e([//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\u00a0"]]);do{m=k.length;k=k.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1")}while(m!=k.length);if(b(l,"paste_retain_style_properties").replace(/^none$/i,"").length==0){k=k.replace(/<\/?span[^>]*>/gi,"")}else{e([[/([\s\u00a0]*)<\/span>/gi,function(o,h){return(h.length>0)?h.replace(/./," ").slice(Math.floor(h.length/2)).split("").join("\u00a0"):""}],[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,function(u,h,t){var v=[],o=0,r=p(g(t).replace(/"/gi,"'"),";");c(r,function(s){var w,y,z=p(s,":");function x(A){return A+((A!=="0")&&(/\d$/.test(A)))?"px":""}if(z.length==2){w=z[0].toLowerCase();y=z[1].toLowerCase();switch(w){case"mso-padding-alt":case"mso-padding-top-alt":case"mso-padding-right-alt":case"mso-padding-bottom-alt":case"mso-padding-left-alt":case"mso-margin-alt":case"mso-margin-top-alt":case"mso-margin-right-alt":case"mso-margin-bottom-alt":case"mso-margin-left-alt":case"mso-table-layout-alt":case"mso-height":case"mso-width":case"mso-vertical-align-alt":v[o++]=w.replace(/^mso-|-alt$/g,"")+":"+x(y);return;case"horiz-align":v[o++]="text-align:"+y;return;case"vert-align":v[o++]="vertical-align:"+y;return;case"font-color":case"mso-foreground":v[o++]="color:"+y;return;case"mso-background":case"mso-highlight":v[o++]="background:"+y;return;case"mso-default-height":v[o++]="min-height:"+x(y);return;case"mso-default-width":v[o++]="min-width:"+x(y);return;case"mso-padding-between-alt":v[o++]="border-collapse:separate;border-spacing:"+x(y);return;case"text-line-through":if((y=="single")||(y=="double")){v[o++]="text-decoration:line-through"}return;case"mso-zero-height":if(y=="yes"){v[o++]="display:none"}return}if(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(w)){return}v[o++]=w+":"+z[1]}});if(o>0){return h+' style="'+v.join(";")+'"'}else{return h}}]])}}if(b(l,"paste_convert_headers_to_strong")){e([[/]*>/gi,"

    "],[/<\/h[1-6][^>]*>/gi,"

    "]])}j=b(l,"paste_strip_class_attributes");if(j!=="none"){function n(r,o){if(j==="all"){return""}var h=q(p(o.replace(/^(["'])(.*)\1$/,"$2")," "),function(s){return(/^(?!mso)/i.test(s))});return h.length?' class="'+h.join(" ")+'"':""}k=k.replace(/ class="([^"]+)"/gi,n);k=k.replace(/ class=(\w+)/gi,n)}if(b(l,"paste_remove_spans")){k=k.replace(/<\/?span[^>]*>/gi,"")}f.content=k},_postProcess:function(h,j){var g=this,f=g.editor,i=f.dom,e;if(j.wordContent){c(i.select("a",j.node),function(k){if(!k.href||k.href.indexOf("#_Toc")!=-1){i.remove(k,1)}});if(b(f,"paste_convert_middot_lists")){g._convertLists(h,j)}e=b(f,"paste_retain_style_properties");if((tinymce.is(e,"string"))&&(e!=="all")&&(e!=="*")){e=tinymce.explode(e.replace(/^none$/i,""));c(i.select("*",j.node),function(n){var o={},l=0,m,p,k;if(e){for(m=0;m0){i.setStyles(n,o)}else{if(n.nodeName=="SPAN"&&!n.className){i.remove(n,true)}}})}}if(b(f,"paste_remove_styles")||(b(f,"paste_remove_styles_if_webkit")&&tinymce.isWebKit)){c(i.select("*[style]",j.node),function(k){k.removeAttribute("style");k.removeAttribute("_mce_style")})}else{if(tinymce.isWebKit){c(i.select("*",j.node),function(k){k.removeAttribute("_mce_style")})}}},_convertLists:function(h,f){var j=h.editor.dom,i,m,e=-1,g,n=[],l,k;c(j.select("p",f.node),function(u){var r,v="",t,s,o,q;for(r=u.firstChild;r&&r.nodeType==3;r=r.nextSibling){v+=r.nodeValue}v=u.innerHTML.replace(/<\/?\w+[^>]*>/gi,"").replace(/ /g,"\u00a0");if(/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(v)){t="ul"}if(/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(v)){t="ol"}if(t){g=parseFloat(u.style.marginLeft||0);if(g>e){n.push(g)}if(!i||t!=l){i=j.create(t);j.insertAfter(i,u)}else{if(g>e){i=m.appendChild(j.create(t))}else{if(g]*>/gi,"");if(t=="ul"&&/^[\u2022\u00b7\u00a7\u00d8o]/.test(p)){j.remove(w)}else{if(/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(p)){j.remove(w)}}});s=u.innerHTML;if(t=="ul"){s=u.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/,"")}else{s=u.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^\s*\w+\.( |\u00a0)+\s*/,"")}m=i.appendChild(j.create("li",0,s));j.remove(u);e=g;l=t}else{i=e=0}});k=f.node.innerHTML;if(k.indexOf("__MCE_ITEM__")!=-1){f.node.innerHTML=k.replace(/__MCE_ITEM__/g,"")}},_insertBlockContent:function(l,h,m){var f,j,g=l.selection,q,n,e,o,i,k="mce_marker";function p(t){var s;if(tinymce.isIE){s=l.getDoc().body.createTextRange();s.moveToElementText(t);s.collapse(false);s.select()}else{g.select(t,1);g.collapse(false)}}this._insert(' ',1);j=h.get(k);f=h.getParent(j,"p,h1,h2,h3,h4,h5,h6,ul,ol,th,td");if(f&&!/TD|TH/.test(f.nodeName)){j=h.split(f,j);c(h.create("div",0,m).childNodes,function(r){q=j.parentNode.insertBefore(r.cloneNode(true),j)});p(q)}else{h.setOuterHTML(j,m);g.select(l.getBody(),1);g.collapse(0)}while(n=h.get(k)){h.remove(n)}n=g.getStart();e=h.getViewPort(l.getWin());o=l.dom.getPos(n).y;i=n.clientHeight;if(oe.y+e.h){l.getDoc().body.scrollTop=o0)){if(!d){d=("34,quot,38,amp,39,apos,60,lt,62,gt,"+j.serializer.settings.entities).split(",")}if(/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(v)){q([/[\n\r]+/g])}else{q([/\r+/g])}q([[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi,"\n\n"],[/]*>|<\/tr>/gi,"\n"],[/<\/t[dh]>\s*]*>/gi,"\t"],/<[a-z!\/?][^>]*>/gi,[/ /gi," "],[/&(#\d+|[a-z0-9]{1,10});/gi,function(i,h){if(h.charAt(0)==="#"){return String.fromCharCode(h.slice(1))}else{return((i=y(d,h))>0)?String.fromCharCode(d[i-1]):" "}}],[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi,"$1"],[/\n{3,}/g,"\n\n"],/^\s+|\s+$/g]);v=x.encode(v);if(!s.isCollapsed()){z.execCommand("Delete",false,null)}if(m(o,"array")||(m(o,"array"))){q(o)}else{if(m(o,"string")){q(new RegExp(o,"gi"))}}if(g=="none"){q([[/\n+/g," "]])}else{if(g=="br"){q([[/\n/g,"
    "]])}else{q([/^\s+|\s+$/g,[/\n\n/g,"

    "],[/\n/g,"
    "]])}}if((l=v.indexOf("

    "))!=-1){k=v.lastIndexOf("

    ");r=s.getNode();e=[];do{if(r.nodeType==1){if(r.nodeName=="TD"||r.nodeName=="BODY"){break}e[e.length]=r}}while(r=r.parentNode);if(e.length>0){p=v.substring(0,l);f="";for(t=0,u=e.length;t";f+="<"+e[e.length-t-1].nodeName.toLowerCase()+">"}if(l==k){v=p+f+v.substring(l+7)}else{v=p+v.substring(l+4,k+4)+f+v.substring(k+7)}}}j.execCommand("mceInsertRawHTML",false,v+' ');window.setTimeout(function(){var h=x.get("_plain_text_marker"),B,i,A,w;s.select(h,false);z.execCommand("Delete",false,null);h=null;B=s.getStart();i=x.getViewPort(n);A=x.getPos(B).y;w=B.clientHeight;if((Ai.y+i.h)){z.body.scrollTop=A")});return}}if(o.get("_mcePaste")){return}l=o.add(q,"div",{id:"_mcePaste","class":"mcePaste","data-mce-bogus":"1"},"\uFEFF\uFEFF");if(q!=d.getDoc().body){i=o.getPos(d.selection.getStart(),q).y}else{i=q.scrollTop+o.getViewPort(d.getWin()).y}o.setStyles(l,{position:"absolute",left:tinymce.isGecko?-40:0,top:i-25,width:1,height:1,overflow:"hidden"});if(tinymce.isIE){t=k.getRng();j=o.doc.body.createTextRange();j.moveToElementText(l);j.execCommand("Paste");o.remove(l);if(l.innerHTML==="\uFEFF\uFEFF"){d.execCommand("mcePasteWord");s.preventDefault();return}k.setRng(t);k.setContent("");setTimeout(function(){h({content:l.innerHTML})},0);return tinymce.dom.Event.cancel(s)}else{function m(n){n.preventDefault()}o.bind(d.getDoc(),"mousedown",m);o.bind(d.getDoc(),"keydown",m);p=d.selection.getRng();l=l.firstChild;j=d.getDoc().createRange();j.setStart(l,0);j.setEnd(l,2);k.setRng(j);window.setTimeout(function(){var u="",n;if(!o.select("div.mcePaste > div.mcePaste").length){n=o.select("div.mcePaste");c(n,function(w){var v=w.firstChild;if(v&&v.nodeName=="DIV"&&v.style.marginTop&&v.style.backgroundColor){o.remove(v,1)}c(o.select("span.Apple-style-span",w),function(x){o.remove(x,1)});c(o.select("br[data-mce-bogus]",w),function(x){o.remove(x)});if(w.parentNode.className!="mcePaste"){u+=w.innerHTML}})}else{u="

    "+o.encode(r).replace(/\r?\n\r?\n/g,"

    ").replace(/\r?\n/g,"
    ")+"

    "}c(o.select("div.mcePaste"),function(v){o.remove(v)});if(p){k.setRng(p)}h({content:u});o.unbind(d.getDoc(),"mousedown",m);o.unbind(d.getDoc(),"keydown",m)},0)}}if(b(d,"paste_auto_cleanup_on_paste")){if(tinymce.isOpera||/Firefox\/2/.test(navigator.userAgent)){d.onKeyDown.addToTop(function(i,j){if(((tinymce.isMac?j.metaKey:j.ctrlKey)&&j.keyCode==86)||(j.shiftKey&&j.keyCode==45)){g(j)}})}else{d.onPaste.addToTop(function(i,j){return g(j)})}}d.onInit.add(function(){d.controlManager.setActive("pastetext",d.pasteAsPlainText);if(b(d,"paste_block_drop")){d.dom.bind(d.getBody(),["dragend","dragover","draggesture","dragdrop","drop","drag"],function(i){i.preventDefault();i.stopPropagation();return false})}});f._legacySupport()},getInfo:function(){return{longname:"Paste text/word",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_preProcess:function(g,e){var k=this.editor,j=e.content,p=tinymce.grep,n=tinymce.explode,f=tinymce.trim,l,i;function d(h){c(h,function(o){if(o.constructor==RegExp){j=j.replace(o,"")}else{j=j.replace(o[0],o[1])}})}if(k.settings.paste_enable_default_filters==false){return}if(tinymce.isIE&&document.documentMode>=9){d([[/(?:
     [\s\r\n]+|
    )*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:
     [\s\r\n]+|
    )*/g,"$1"]]);d([[/

    /g,"

    "],[/
    /g," "],[/

    /g,"
    "]])}if(/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(j)||e.wordContent){e.wordContent=true;d([/^\s*( )+/gi,/( |]*>)+\s*$/gi]);if(b(k,"paste_convert_headers_to_strong")){j=j.replace(/

    ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi,"

    $1

    ")}if(b(k,"paste_convert_middot_lists")){d([[//gi,"$&__MCE_ITEM__"],[/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi,"$1__MCE_ITEM__"],[/(]+(?:MsoListParagraph)[^>]+>)/gi,"$1__MCE_ITEM__"]])}d([//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\u00a0"]]);do{l=j.length;j=j.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi,"$1")}while(l!=j.length);if(b(k,"paste_retain_style_properties").replace(/^none$/i,"").length==0){j=j.replace(/<\/?span[^>]*>/gi,"")}else{d([[/([\s\u00a0]*)<\/span>/gi,function(o,h){return(h.length>0)?h.replace(/./," ").slice(Math.floor(h.length/2)).split("").join("\u00a0"):""}],[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,function(t,h,r){var u=[],o=0,q=n(f(r).replace(/"/gi,"'"),";");c(q,function(s){var w,y,z=n(s,":");function x(A){return A+((A!=="0")&&(/\d$/.test(A)))?"px":""}if(z.length==2){w=z[0].toLowerCase();y=z[1].toLowerCase();switch(w){case"mso-padding-alt":case"mso-padding-top-alt":case"mso-padding-right-alt":case"mso-padding-bottom-alt":case"mso-padding-left-alt":case"mso-margin-alt":case"mso-margin-top-alt":case"mso-margin-right-alt":case"mso-margin-bottom-alt":case"mso-margin-left-alt":case"mso-table-layout-alt":case"mso-height":case"mso-width":case"mso-vertical-align-alt":u[o++]=w.replace(/^mso-|-alt$/g,"")+":"+x(y);return;case"horiz-align":u[o++]="text-align:"+y;return;case"vert-align":u[o++]="vertical-align:"+y;return;case"font-color":case"mso-foreground":u[o++]="color:"+y;return;case"mso-background":case"mso-highlight":u[o++]="background:"+y;return;case"mso-default-height":u[o++]="min-height:"+x(y);return;case"mso-default-width":u[o++]="min-width:"+x(y);return;case"mso-padding-between-alt":u[o++]="border-collapse:separate;border-spacing:"+x(y);return;case"text-line-through":if((y=="single")||(y=="double")){u[o++]="text-decoration:line-through"}return;case"mso-zero-height":if(y=="yes"){u[o++]="display:none"}return}if(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(w)){return}u[o++]=w+":"+z[1]}});if(o>0){return h+' style="'+u.join(";")+'"'}else{return h}}]])}}if(b(k,"paste_convert_headers_to_strong")){d([[/]*>/gi,"

    "],[/<\/h[1-6][^>]*>/gi,"

    "]])}d([[/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi,""]]);i=b(k,"paste_strip_class_attributes");if(i!=="none"){function m(q,o){if(i==="all"){return""}var h=p(n(o.replace(/^(["'])(.*)\1$/,"$2")," "),function(r){return(/^(?!mso)/i.test(r))});return h.length?' class="'+h.join(" ")+'"':""}j=j.replace(/ class="([^"]+)"/gi,m);j=j.replace(/ class=([\-\w]+)/gi,m)}if(b(k,"paste_remove_spans")){j=j.replace(/<\/?span[^>]*>/gi,"")}e.content=j},_postProcess:function(g,i){var f=this,e=f.editor,h=e.dom,d;if(e.settings.paste_enable_default_filters==false){return}if(i.wordContent){c(h.select("a",i.node),function(j){if(!j.href||j.href.indexOf("#_Toc")!=-1){h.remove(j,1)}});if(b(e,"paste_convert_middot_lists")){f._convertLists(g,i)}d=b(e,"paste_retain_style_properties");if((tinymce.is(d,"string"))&&(d!=="all")&&(d!=="*")){d=tinymce.explode(d.replace(/^none$/i,""));c(h.select("*",i.node),function(m){var n={},k=0,l,o,j;if(d){for(l=0;l0){h.setStyles(m,n)}else{if(m.nodeName=="SPAN"&&!m.className){h.remove(m,true)}}})}}if(b(e,"paste_remove_styles")||(b(e,"paste_remove_styles_if_webkit")&&tinymce.isWebKit)){c(h.select("*[style]",i.node),function(j){j.removeAttribute("style");j.removeAttribute("data-mce-style")})}else{if(tinymce.isWebKit){c(h.select("*",i.node),function(j){j.removeAttribute("data-mce-style")})}}},_convertLists:function(g,e){var i=g.editor.dom,h,l,d=-1,f,m=[],k,j;c(i.select("p",e.node),function(t){var q,u="",s,r,n,o;for(q=t.firstChild;q&&q.nodeType==3;q=q.nextSibling){u+=q.nodeValue}u=t.innerHTML.replace(/<\/?\w+[^>]*>/gi,"").replace(/ /g,"\u00a0");if(/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(u)){s="ul"}if(/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(u)){s="ol"}if(s){f=parseFloat(t.style.marginLeft||0);if(f>d){m.push(f)}if(!h||s!=k){h=i.create(s);i.insertAfter(h,t)}else{if(f>d){h=l.appendChild(i.create(s))}else{if(f]*>/gi,"");if(s=="ul"&&/^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(p)){i.remove(v)}else{if(/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(p)){i.remove(v)}}});r=t.innerHTML;if(s=="ul"){r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/,"")}else{r=t.innerHTML.replace(/__MCE_ITEM__/g,"").replace(/^\s*\w+\.( |\u00a0)+\s*/,"")}l=h.appendChild(i.create("li",0,r));i.remove(t);d=f;k=s}else{h=d=0}});j=e.node.innerHTML;if(j.indexOf("__MCE_ITEM__")!=-1){e.node.innerHTML=j.replace(/__MCE_ITEM__/g,"")}},_insert:function(f,d){var e=this.editor,g=e.selection.getRng();if(!e.selection.isCollapsed()&&g.startContainer!=g.endContainer){e.getDoc().execCommand("Delete",false,null)}e.execCommand("mceInsertContent",false,f,{skip_undo:d})},_insertPlainText:function(g){var d=this.editor,e=b(d,"paste_text_linebreaktype"),i=b(d,"paste_text_replacements"),f=tinymce.is;function h(j){c(j,function(k){if(k.constructor==RegExp){g=g.replace(k,"")}else{g=g.replace(k[0],k[1])}})}if((typeof(g)==="string")&&(g.length>0)){if(/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(g)){h([/[\n\r]+/g])}else{h([/\r+/g])}h([[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi,"\n\n"],[/]*>|<\/tr>/gi,"\n"],[/<\/t[dh]>\s*]*>/gi,"\t"],/<[a-z!\/?][^>]*>/gi,[/ /gi," "],[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi,"$1"],[/\n{3,}/g,"\n\n"]]);g=d.dom.decode(tinymce.html.Entities.encodeRaw(g));if(f(i,"array")){h(i)}else{if(f(i,"string")){h(new RegExp(i,"gi"))}}if(e=="none"){h([[/\n+/g," "]])}else{if(e=="br"){h([[/\n/g,"
    "]])}else{if(e=="p"){h([[/\n+/g,"

    "],[/^(.*<\/p>)(

    )$/,"

    $1"]])}else{h([[/\n\n/g,"

    "],[/^(.*<\/p>)(

    )$/,"

    $1"],[/\n/g,"
    "]])}}}d.execCommand("mceInsertContent",false,g)}},_legacySupport:function(){var e=this,d=e.editor;d.addCommand("mcePasteWord",function(){d.windowManager.open({file:e.url+"/pasteword.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})});if(b(d,"paste_text_use_dialog")){d.addCommand("mcePasteText",function(){d.windowManager.open({file:e.url+"/pastetext.htm",width:parseInt(b(d,"paste_dialog_width")),height:parseInt(b(d,"paste_dialog_height")),inline:1})})}d.addButton("pasteword",{title:"paste.paste_word_desc",cmd:"mcePasteWord"})}});tinymce.PluginManager.add("paste",tinymce.plugins.PastePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/paste/editor_plugin_src.js b/js/tiny_mce/plugins/paste/editor_plugin_src.js index d85b39bd..cec4abf9 100644 --- a/js/tiny_mce/plugins/paste/editor_plugin_src.js +++ b/js/tiny_mce/plugins/paste/editor_plugin_src.js @@ -1,929 +1,871 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var each = tinymce.each, - entities = null, - defs = { - paste_auto_cleanup_on_paste : true, - paste_block_drop : false, - paste_retain_style_properties : "none", - paste_strip_class_attributes : "mso", - paste_remove_spans : false, - paste_remove_styles : false, - paste_remove_styles_if_webkit : true, - paste_convert_middot_lists : true, - paste_convert_headers_to_strong : false, - paste_dialog_width : "450", - paste_dialog_height : "400", - paste_text_use_dialog : false, - paste_text_sticky : false, - paste_text_notifyalways : false, - paste_text_linebreaktype : "p", - paste_text_replacements : [ - [/\u2026/g, "..."], - [/[\x93\x94\u201c\u201d]/g, '"'], - [/[\x60\x91\x92\u2018\u2019]/g, "'"] - ] - }; - - function getParam(ed, name) { - return ed.getParam(name, defs[name]); - } - - tinymce.create('tinymce.plugins.PastePlugin', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - t.url = url; - - // Setup plugin events - t.onPreProcess = new tinymce.util.Dispatcher(t); - t.onPostProcess = new tinymce.util.Dispatcher(t); - - // Register default handlers - t.onPreProcess.add(t._preProcess); - t.onPostProcess.add(t._postProcess); - - // Register optional preprocess handler - t.onPreProcess.add(function(pl, o) { - ed.execCallback('paste_preprocess', pl, o); - }); - - // Register optional postprocess - t.onPostProcess.add(function(pl, o) { - ed.execCallback('paste_postprocess', pl, o); - }); - - // Initialize plain text flag - ed.pasteAsPlainText = false; - - // This function executes the process handlers and inserts the contents - // force_rich overrides plain text mode set by user, important for pasting with execCommand - function process(o, force_rich) { - var dom = ed.dom; - - // Execute pre process handlers - t.onPreProcess.dispatch(t, o); - - // Create DOM structure - o.node = dom.create('div', 0, o.content); - - // Execute post process handlers - t.onPostProcess.dispatch(t, o); - - // Serialize content - o.content = ed.serializer.serialize(o.node, {getInner : 1}); - - // Plain text option active? - if ((!force_rich) && (ed.pasteAsPlainText)) { - t._insertPlainText(ed, dom, o.content); - - if (!getParam(ed, "paste_text_sticky")) { - ed.pasteAsPlainText = false; - ed.controlManager.setActive("pastetext", false); - } - } else if (/<(p|h[1-6]|ul|ol)/.test(o.content)) { - // Handle insertion of contents containing block elements separately - t._insertBlockContent(ed, dom, o.content); - } else { - t._insert(o.content); - } - } - - // Add command for external usage - ed.addCommand('mceInsertClipboardContent', function(u, o) { - process(o, true); - }); - - if (!getParam(ed, "paste_text_use_dialog")) { - ed.addCommand('mcePasteText', function(u, v) { - var cookie = tinymce.util.Cookie; - - ed.pasteAsPlainText = !ed.pasteAsPlainText; - ed.controlManager.setActive('pastetext', ed.pasteAsPlainText); - - if ((ed.pasteAsPlainText) && (!cookie.get("tinymcePasteText"))) { - if (getParam(ed, "paste_text_sticky")) { - ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode."); - } else { - ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode."); - } - - if (!getParam(ed, "paste_text_notifyalways")) { - cookie.set("tinymcePasteText", "1", new Date(new Date().getFullYear() + 1, 12, 31)) - } - } - }); - } - - ed.addButton('pastetext', {title: 'paste.paste_text_desc', cmd: 'mcePasteText'}); - ed.addButton('selectall', {title: 'paste.selectall_desc', cmd: 'selectall'}); - - // This function grabs the contents from the clipboard by adding a - // hidden div and placing the caret inside it and after the browser paste - // is done it grabs that contents and processes that - function grabContent(e) { - var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY; - - if (dom.get('_mcePaste')) - return; - - // Create container to paste into - n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste'}, '\uFEFF'); - - // If contentEditable mode we need to find out the position of the closest element - if (body != ed.getDoc().body) - posY = dom.getPos(ed.selection.getStart(), body).y; - else - posY = body.scrollTop; - - // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles - dom.setStyles(n, { - position : 'absolute', - left : -10000, - top : posY, - width : 1, - height : 1, - overflow : 'hidden' - }); - - if (tinymce.isIE) { - // Select the container - rng = dom.doc.body.createTextRange(); - rng.moveToElementText(n); - rng.execCommand('Paste'); - - // Remove container - dom.remove(n); - - // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due - // to IE security settings so we pass the junk though better than nothing right - if (n.innerHTML === '\uFEFF') { - ed.execCommand('mcePasteWord'); - e.preventDefault(); - return; - } - - // Process contents - process({content : n.innerHTML}); - - // Block the real paste event - return tinymce.dom.Event.cancel(e); - } else { - function block(e) { - e.preventDefault(); - }; - - // Block mousedown and click to prevent selection change - dom.bind(ed.getDoc(), 'mousedown', block); - dom.bind(ed.getDoc(), 'keydown', block); - - or = ed.selection.getRng(); - - // Move caret into hidden div - n = n.firstChild; - rng = ed.getDoc().createRange(); - rng.setStart(n, 0); - rng.setEnd(n, 1); - sel.setRng(rng); - - // Wait a while and grab the pasted contents - window.setTimeout(function() { - var h = '', nl = dom.select('div.mcePaste'); - - // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string - each(nl, function(n) { - // WebKit duplicates the divs so we need to remove them - each(dom.select('div.mcePaste', n), function(n) { - dom.remove(n, 1); - }); - - // Contents in WebKit is sometimes wrapped in a apple style span so we need to grab it from that one - h += (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML; - }); - - // Remove the nodes - each(nl, function(n) { - dom.remove(n); - }); - - // Restore the old selection - if (or) - sel.setRng(or); - - process({content : h}); - - // Unblock events ones we got the contents - dom.unbind(ed.getDoc(), 'mousedown', block); - dom.unbind(ed.getDoc(), 'keydown', block); - }, 0); - } - } - - // Check if we should use the new auto process method - if (getParam(ed, "paste_auto_cleanup_on_paste")) { - // Is it's Opera or older FF use key handler - if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { - ed.onKeyDown.add(function(ed, e) { - if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) - grabContent(e); - }); - } else { - // Grab contents on paste event on Gecko and WebKit - ed.onPaste.addToTop(function(ed, e) { - return grabContent(e); - }); - } - } - - // Block all drag/drop events - if (getParam(ed, "paste_block_drop")) { - ed.onInit.add(function() { - ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { - e.preventDefault(); - e.stopPropagation(); - - return false; - }); - }); - } - - // Add legacy support - t._legacySupport(); - }, - - getInfo : function() { - return { - longname : 'Paste text/word', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - _preProcess : function(pl, o) { - //console.log('Before preprocess:' + o.content); - - var ed = this.editor, - h = o.content, - grep = tinymce.grep, - explode = tinymce.explode, - trim = tinymce.trim, - len, stripClass; - - function process(items) { - each(items, function(v) { - // Remove or replace - if (v.constructor == RegExp) - h = h.replace(v, ''); - else - h = h.replace(v[0], v[1]); - }); - } - - // Detect Word content and process it more aggressive - if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { - o.wordContent = true; // Mark the pasted contents as word specific content - //console.log('Word contents detected.'); - - // Process away some basic content - process([ - /^\s*( )+/gi, //   entities at the start of contents - /( |]*>)+\s*$/gi //   entities at the end of contents - ]); - - if (getParam(ed, "paste_convert_headers_to_strong")) { - h = h.replace(/

    ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "

    $1

    "); - } - - if (getParam(ed, "paste_convert_middot_lists")) { - process([ - [//gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker - [/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol spans to item markers - ]); - } - - process([ - // Word comments like conditional comments etc - //gi, - - // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags - /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, - - // Convert into for line-though - [/<(\/?)s>/gi, "<$1strike>"], - - // Replace nsbp entites to char since it's easier to handle - [/ /gi, "\u00a0"] - ]); - - // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag. - // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. - do { - len = h.length; - h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); - } while (len != h.length); - - // Remove all spans if no styles is to be retained - if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) { - h = h.replace(/<\/?span[^>]*>/gi, ""); - } else { - // We're keeping styles, so at least clean them up. - // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx - - process([ - // Convert ___ to string of alternating breaking/non-breaking spaces of same length - [/([\s\u00a0]*)<\/span>/gi, - function(str, spaces) { - return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ""; - } - ], - - // Examine all styles: delete junk, transform some, and keep the rest - [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi, - function(str, tag, style) { - var n = [], - i = 0, - s = explode(trim(style).replace(/"/gi, "'"), ";"); - - // Examine each style definition within the tag's style attribute - each(s, function(v) { - var name, value, - parts = explode(v, ":"); - - function ensureUnits(v) { - return v + ((v !== "0") && (/\d$/.test(v)))? "px" : ""; - } - - if (parts.length == 2) { - name = parts[0].toLowerCase(); - value = parts[1].toLowerCase(); - - // Translate certain MS Office styles into their CSS equivalents - switch (name) { - case "mso-padding-alt": - case "mso-padding-top-alt": - case "mso-padding-right-alt": - case "mso-padding-bottom-alt": - case "mso-padding-left-alt": - case "mso-margin-alt": - case "mso-margin-top-alt": - case "mso-margin-right-alt": - case "mso-margin-bottom-alt": - case "mso-margin-left-alt": - case "mso-table-layout-alt": - case "mso-height": - case "mso-width": - case "mso-vertical-align-alt": - n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value); - return; - - case "horiz-align": - n[i++] = "text-align:" + value; - return; - - case "vert-align": - n[i++] = "vertical-align:" + value; - return; - - case "font-color": - case "mso-foreground": - n[i++] = "color:" + value; - return; - - case "mso-background": - case "mso-highlight": - n[i++] = "background:" + value; - return; - - case "mso-default-height": - n[i++] = "min-height:" + ensureUnits(value); - return; - - case "mso-default-width": - n[i++] = "min-width:" + ensureUnits(value); - return; - - case "mso-padding-between-alt": - n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value); - return; - - case "text-line-through": - if ((value == "single") || (value == "double")) { - n[i++] = "text-decoration:line-through"; - } - return; - - case "mso-zero-height": - if (value == "yes") { - n[i++] = "display:none"; - } - return; - } - - // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name - if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) { - return; - } - - // If it reached this point, it must be a valid CSS style - n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case - } - }); - - // If style attribute contained any valid styles the re-write it; otherwise delete style attribute. - if (i > 0) { - return tag + ' style="' + n.join(';') + '"'; - } else { - return tag; - } - } - ] - ]); - } - } - - // Replace headers with - if (getParam(ed, "paste_convert_headers_to_strong")) { - process([ - [/]*>/gi, "

    "], - [/<\/h[1-6][^>]*>/gi, "

    "] - ]); - } - - // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). - // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. - stripClass = getParam(ed, "paste_strip_class_attributes"); - - if (stripClass !== "none") { - function removeClasses(match, g1) { - if (stripClass === "all") - return ''; - - var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "), - function(v) { - return (/^(?!mso)/i.test(v)); - } - ); - - return cls.length ? ' class="' + cls.join(" ") + '"' : ''; - }; - - h = h.replace(/ class="([^"]+)"/gi, removeClasses); - h = h.replace(/ class=(\w+)/gi, removeClasses); - } - - // Remove spans option - if (getParam(ed, "paste_remove_spans")) { - h = h.replace(/<\/?span[^>]*>/gi, ""); - } - - //console.log('After preprocess:' + h); - - o.content = h; - }, - - /** - * Various post process items. - */ - _postProcess : function(pl, o) { - var t = this, ed = t.editor, dom = ed.dom, styleProps; - - if (o.wordContent) { - // Remove named anchors or TOC links - each(dom.select('a', o.node), function(a) { - if (!a.href || a.href.indexOf('#_Toc') != -1) - dom.remove(a, 1); - }); - - if (getParam(ed, "paste_convert_middot_lists")) { - t._convertLists(pl, o); - } - - // Process styles - styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties - - // Process only if a string was specified and not equal to "all" or "*" - if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) { - styleProps = tinymce.explode(styleProps.replace(/^none$/i, "")); - - // Retains some style properties - each(dom.select('*', o.node), function(el) { - var newStyle = {}, npc = 0, i, sp, sv; - - // Store a subset of the existing styles - if (styleProps) { - for (i = 0; i < styleProps.length; i++) { - sp = styleProps[i]; - sv = dom.getStyle(el, sp); - - if (sv) { - newStyle[sp] = sv; - npc++; - } - } - } - - // Remove all of the existing styles - dom.setAttrib(el, 'style', ''); - - if (styleProps && npc > 0) - dom.setStyles(el, newStyle); // Add back the stored subset of styles - else // Remove empty span tags that do not have class attributes - if (el.nodeName == 'SPAN' && !el.className) - dom.remove(el, true); - }); - } - } - - // Remove all style information or only specifically on WebKit to avoid the style bug on that browser - if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { - each(dom.select('*[style]', o.node), function(el) { - el.removeAttribute('style'); - el.removeAttribute('_mce_style'); - }); - } else { - if (tinymce.isWebKit) { - // We need to compress the styles on WebKit since if you paste it will become - // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles - each(dom.select('*', o.node), function(el) { - el.removeAttribute('_mce_style'); - }); - } - } - }, - - /** - * Converts the most common bullet and number formats in Office into a real semantic UL/LI list. - */ - _convertLists : function(pl, o) { - var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html; - - // Convert middot lists into real semantic lists - each(dom.select('p', o.node), function(p) { - var sib, val = '', type, html, idx, parents; - - // Get text node value at beginning of paragraph - for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling) - val += sib.nodeValue; - - val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); - - // Detect unordered lists look for bullets - if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val)) - type = 'ul'; - - // Detect ordered lists 1., a. or ixv. - if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val)) - type = 'ol'; - - // Check if node value matches the list pattern: o   - if (type) { - margin = parseFloat(p.style.marginLeft || 0); - - if (margin > lastMargin) - levels.push(margin); - - if (!listElm || type != lastType) { - listElm = dom.create(type); - dom.insertAfter(listElm, p); - } else { - // Nested list element - if (margin > lastMargin) { - listElm = li.appendChild(dom.create(type)); - } else if (margin < lastMargin) { - // Find parent level based on margin value - idx = tinymce.inArray(levels, margin); - parents = dom.getParents(listElm.parentNode, type); - listElm = parents[parents.length - 1 - idx] || listElm; - } - } - - // Remove middot or number spans if they exists - each(dom.select('span', p), function(span) { - var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); - - // Remove span with the middot or the number - if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html)) - dom.remove(span); - else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) - dom.remove(span); - }); - - html = p.innerHTML; - - // Remove middot/list items - if (type == 'ul') - html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, ''); - else - html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); - - // Create li and add paragraph data into the new li - li = listElm.appendChild(dom.create('li', 0, html)); - dom.remove(p); - - lastMargin = margin; - lastType = type; - } else - listElm = lastMargin = 0; // End list element - }); - - // Remove any left over makers - html = o.node.innerHTML; - if (html.indexOf('__MCE_ITEM__') != -1) - o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); - }, - - /** - * This method will split the current block parent and insert the contents inside the split position. - * This logic can be improved so text nodes at the start/end remain in the start/end block elements - */ - _insertBlockContent : function(ed, dom, content) { - var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight, markerId = 'mce_marker'; - - function select(n) { - var r; - - if (tinymce.isIE) { - r = ed.getDoc().body.createTextRange(); - r.moveToElementText(n); - r.collapse(false); - r.select(); - } else { - sel.select(n, 1); - sel.collapse(false); - } - } - - // Insert a marker for the caret position - this._insert(' ', 1); - marker = dom.get(markerId); - parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td'); - - // If it's a parent block but not a table cell - if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) { - // Split parent block - marker = dom.split(parentBlock, marker); - - // Insert nodes before the marker - each(dom.create('div', 0, content).childNodes, function(n) { - last = marker.parentNode.insertBefore(n.cloneNode(true), marker); - }); - - // Move caret after marker - select(last); - } else { - dom.setOuterHTML(marker, content); - sel.select(ed.getBody(), 1); - sel.collapse(0); - } - - // Remove marker if it's left - while (elm = dom.get(markerId)) - dom.remove(elm); - - // Get element, position and height - elm = sel.getStart(); - vp = dom.getViewPort(ed.getWin()); - y = ed.dom.getPos(elm).y; - elmHeight = elm.clientHeight; - - // Is element within viewport if not then scroll it into view - if (y < vp.y || y + elmHeight > vp.y + vp.h) - ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25; - }, - - /** - * Inserts the specified contents at the caret position. - */ - _insert : function(h, skip_undo) { - var ed = this.editor; - - // First delete the contents seems to work better on WebKit - if (!ed.selection.isCollapsed()) - ed.getDoc().execCommand('Delete', false, null); - - // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents - ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo}); - }, - - /** - * Instead of the old plain text method which tried to re-create a paste operation, the - * new approach adds a plain text mode toggle switch that changes the behavior of paste. - * This function is passed the same input that the regular paste plugin produces. - * It performs additional scrubbing and produces (and inserts) the plain text. - * This approach leverages all of the great existing functionality in the paste - * plugin, and requires minimal changes to add the new functionality. - * Speednet - June 2009 - */ - _insertPlainText : function(ed, dom, h) { - var i, len, pos, rpos, node, breakElms, before, after, - w = ed.getWin(), - d = ed.getDoc(), - sel = ed.selection, - is = tinymce.is, - inArray = tinymce.inArray, - linebr = getParam(ed, "paste_text_linebreaktype"), - rl = getParam(ed, "paste_text_replacements"); - - function process(items) { - each(items, function(v) { - if (v.constructor == RegExp) - h = h.replace(v, ""); - else - h = h.replace(v[0], v[1]); - }); - }; - - if ((typeof(h) === "string") && (h.length > 0)) { - if (!entities) - entities = ("34,quot,38,amp,39,apos,60,lt,62,gt," + ed.serializer.settings.entities).split(","); - - // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line - if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) { - process([ - /[\n\r]+/g - ]); - } else { - // Otherwise just get rid of carriage returns (only need linefeeds) - process([ - /\r+/g - ]); - } - - process([ - [/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank line after them - [/]*>|<\/tr>/gi, "\n"], // Single linebreak for
    tags and table rows - [/<\/t[dh]>\s*]*>/gi, "\t"], // Table cells get tabs betweem them - /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags - [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) - [ - // HTML entity - /&(#\d+|[a-z0-9]{1,10});/gi, - - // Replace with actual character - function(e, s) { - if (s.charAt(0) === "#") { - return String.fromCharCode(s.slice(1)); - } - else { - return ((e = inArray(entities, s)) > 0)? String.fromCharCode(entities[e-1]) : " "; - } - } - ], - [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars. - [/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks - /^\s+|\s+$/g // Trim the front & back - ]); - - h = dom.encode(h); - - // Delete any highlighted text before pasting - if (!sel.isCollapsed()) { - d.execCommand("Delete", false, null); - } - - // Perform default or custom replacements - if (is(rl, "array") || (is(rl, "array"))) { - process(rl); - } - else if (is(rl, "string")) { - process(new RegExp(rl, "gi")); - } - - // Treat paragraphs as specified in the config - if (linebr == "none") { - process([ - [/\n+/g, " "] - ]); - } - else if (linebr == "br") { - process([ - [/\n/g, "
    "] - ]); - } - else { - process([ - /^\s+|\s+$/g, - [/\n\n/g, "

    "], - [/\n/g, "
    "] - ]); - } - - // This next piece of code handles the situation where we're pasting more than one paragraph of plain - // text, and we are pasting the content into the middle of a block node in the editor. The block - // node gets split at the selection point into "Para A" and "Para B" (for the purposes of explaining). - // The first paragraph of the pasted text is appended to "Para A", and the last paragraph of the - // pasted text is prepended to "Para B". Any other paragraphs of pasted text are placed between - // "Para A" and "Para B". This code solves a host of problems with the original plain text plugin and - // now handles styles correctly. (Pasting plain text into a styled paragraph is supposed to make the - // plain text take the same style as the existing paragraph.) - if ((pos = h.indexOf("

    ")) != -1) { - rpos = h.lastIndexOf("

    "); - node = sel.getNode(); - breakElms = []; // Get list of elements to break - - do { - if (node.nodeType == 1) { - // Don't break tables and break at body - if (node.nodeName == "TD" || node.nodeName == "BODY") { - break; - } - - breakElms[breakElms.length] = node; - } - } while (node = node.parentNode); - - // Are we in the middle of a block node? - if (breakElms.length > 0) { - before = h.substring(0, pos); - after = ""; - - for (i=0, len=breakElms.length; i"; - after += "<" + breakElms[breakElms.length-i-1].nodeName.toLowerCase() + ">"; - } - - if (pos == rpos) { - h = before + after + h.substring(pos+7); - } - else { - h = before + h.substring(pos+4, rpos+4) + after + h.substring(rpos+7); - } - } - } - - // Insert content at the caret, plus add a marker for repositioning the caret - ed.execCommand("mceInsertRawHTML", false, h + ' '); - - // Reposition the caret to the marker, which was placed immediately after the inserted content. - // Needs to be done asynchronously (in window.setTimeout) or else it doesn't work in all browsers. - // The second part of the code scrolls the content up if the caret is positioned off-screen. - // This is only necessary for WebKit browsers, but it doesn't hurt to use for all. - window.setTimeout(function() { - var marker = dom.get('_plain_text_marker'), - elm, vp, y, elmHeight; - - sel.select(marker, false); - d.execCommand("Delete", false, null); - marker = null; - - // Get element, position and height - elm = sel.getStart(); - vp = dom.getViewPort(w); - y = dom.getPos(elm).y; - elmHeight = elm.clientHeight; - - // Is element within viewport if not then scroll it into view - if ((y < vp.y) || (y + elmHeight > vp.y + vp.h)) { - d.body.scrollTop = y < vp.y ? y : y - vp.h + 25; - } - }, 0); - } - }, - - /** - * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine. - */ - _legacySupport : function() { - var t = this, ed = t.editor; - - // Register command(s) for backwards compatibility - ed.addCommand("mcePasteWord", function() { - ed.windowManager.open({ - file: t.url + "/pasteword.htm", - width: parseInt(getParam(ed, "paste_dialog_width")), - height: parseInt(getParam(ed, "paste_dialog_height")), - inline: 1 - }); - }); - - if (getParam(ed, "paste_text_use_dialog")) { - ed.addCommand("mcePasteText", function() { - ed.windowManager.open({ - file : t.url + "/pastetext.htm", - width: parseInt(getParam(ed, "paste_dialog_width")), - height: parseInt(getParam(ed, "paste_dialog_height")), - inline : 1 - }); - }); - } - - // Register button for backwards compatibility - ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"}); - } - }); - - // Register plugin - tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin); -})(); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var each = tinymce.each, + defs = { + paste_auto_cleanup_on_paste : true, + paste_enable_default_filters : true, + paste_block_drop : false, + paste_retain_style_properties : "none", + paste_strip_class_attributes : "mso", + paste_remove_spans : false, + paste_remove_styles : false, + paste_remove_styles_if_webkit : true, + paste_convert_middot_lists : true, + paste_convert_headers_to_strong : false, + paste_dialog_width : "450", + paste_dialog_height : "400", + paste_text_use_dialog : false, + paste_text_sticky : false, + paste_text_sticky_default : false, + paste_text_notifyalways : false, + paste_text_linebreaktype : "combined", + paste_text_replacements : [ + [/\u2026/g, "..."], + [/[\x93\x94\u201c\u201d]/g, '"'], + [/[\x60\x91\x92\u2018\u2019]/g, "'"] + ] + }; + + function getParam(ed, name) { + return ed.getParam(name, defs[name]); + } + + tinymce.create('tinymce.plugins.PastePlugin', { + init : function(ed, url) { + var t = this; + + t.editor = ed; + t.url = url; + + // Setup plugin events + t.onPreProcess = new tinymce.util.Dispatcher(t); + t.onPostProcess = new tinymce.util.Dispatcher(t); + + // Register default handlers + t.onPreProcess.add(t._preProcess); + t.onPostProcess.add(t._postProcess); + + // Register optional preprocess handler + t.onPreProcess.add(function(pl, o) { + ed.execCallback('paste_preprocess', pl, o); + }); + + // Register optional postprocess + t.onPostProcess.add(function(pl, o) { + ed.execCallback('paste_postprocess', pl, o); + }); + + ed.onKeyDown.addToTop(function(ed, e) { + // Block ctrl+v from adding an undo level since the default logic in tinymce.Editor will add that + if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) + return false; // Stop other listeners + }); + + // Initialize plain text flag + ed.pasteAsPlainText = getParam(ed, 'paste_text_sticky_default'); + + // This function executes the process handlers and inserts the contents + // force_rich overrides plain text mode set by user, important for pasting with execCommand + function process(o, force_rich) { + var dom = ed.dom, rng; + + // Execute pre process handlers + t.onPreProcess.dispatch(t, o); + + // Create DOM structure + o.node = dom.create('div', 0, o.content); + + // If pasting inside the same element and the contents is only one block + // remove the block and keep the text since Firefox will copy parts of pre and h1-h6 as a pre element + if (tinymce.isGecko) { + rng = ed.selection.getRng(true); + if (rng.startContainer == rng.endContainer && rng.startContainer.nodeType == 3) { + // Is only one block node and it doesn't contain word stuff + if (o.node.childNodes.length === 1 && /^(p|h[1-6]|pre)$/i.test(o.node.firstChild.nodeName) && o.content.indexOf('__MCE_ITEM__') === -1) + dom.remove(o.node.firstChild, true); + } + } + + // Execute post process handlers + t.onPostProcess.dispatch(t, o); + + // Serialize content + o.content = ed.serializer.serialize(o.node, {getInner : 1, forced_root_block : ''}); + + // Plain text option active? + if ((!force_rich) && (ed.pasteAsPlainText)) { + t._insertPlainText(o.content); + + if (!getParam(ed, "paste_text_sticky")) { + ed.pasteAsPlainText = false; + ed.controlManager.setActive("pastetext", false); + } + } else { + t._insert(o.content); + } + } + + // Add command for external usage + ed.addCommand('mceInsertClipboardContent', function(u, o) { + process(o, true); + }); + + if (!getParam(ed, "paste_text_use_dialog")) { + ed.addCommand('mcePasteText', function(u, v) { + var cookie = tinymce.util.Cookie; + + ed.pasteAsPlainText = !ed.pasteAsPlainText; + ed.controlManager.setActive('pastetext', ed.pasteAsPlainText); + + if ((ed.pasteAsPlainText) && (!cookie.get("tinymcePasteText"))) { + if (getParam(ed, "paste_text_sticky")) { + ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky')); + } else { + ed.windowManager.alert(ed.translate('paste.plaintext_mode')); + } + + if (!getParam(ed, "paste_text_notifyalways")) { + cookie.set("tinymcePasteText", "1", new Date(new Date().getFullYear() + 1, 12, 31)) + } + } + }); + } + + ed.addButton('pastetext', {title: 'paste.paste_text_desc', cmd: 'mcePasteText'}); + ed.addButton('selectall', {title: 'paste.selectall_desc', cmd: 'selectall'}); + + // This function grabs the contents from the clipboard by adding a + // hidden div and placing the caret inside it and after the browser paste + // is done it grabs that contents and processes that + function grabContent(e) { + var n, or, rng, oldRng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY, textContent; + + // Check if browser supports direct plaintext access + if (e.clipboardData || dom.doc.dataTransfer) { + textContent = (e.clipboardData || dom.doc.dataTransfer).getData('Text'); + + if (ed.pasteAsPlainText) { + e.preventDefault(); + process({content : dom.encode(textContent).replace(/\r?\n/g, '
    ')}); + return; + } + } + + if (dom.get('_mcePaste')) + return; + + // Create container to paste into + n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste', 'data-mce-bogus' : '1'}, '\uFEFF\uFEFF'); + + // If contentEditable mode we need to find out the position of the closest element + if (body != ed.getDoc().body) + posY = dom.getPos(ed.selection.getStart(), body).y; + else + posY = body.scrollTop + dom.getViewPort(ed.getWin()).y; + + // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles + // If also needs to be in view on IE or the paste would fail + dom.setStyles(n, { + position : 'absolute', + left : tinymce.isGecko ? -40 : 0, // Need to move it out of site on Gecko since it will othewise display a ghost resize rect for the div + top : posY - 25, + width : 1, + height : 1, + overflow : 'hidden' + }); + + if (tinymce.isIE) { + // Store away the old range + oldRng = sel.getRng(); + + // Select the container + rng = dom.doc.body.createTextRange(); + rng.moveToElementText(n); + rng.execCommand('Paste'); + + // Remove container + dom.remove(n); + + // Check if the contents was changed, if it wasn't then clipboard extraction failed probably due + // to IE security settings so we pass the junk though better than nothing right + if (n.innerHTML === '\uFEFF\uFEFF') { + ed.execCommand('mcePasteWord'); + e.preventDefault(); + return; + } + + // Restore the old range and clear the contents before pasting + sel.setRng(oldRng); + sel.setContent(''); + + // For some odd reason we need to detach the the mceInsertContent call from the paste event + // It's like IE has a reference to the parent element that you paste in and the selection gets messed up + // when it tries to restore the selection + setTimeout(function() { + // Process contents + process({content : n.innerHTML}); + }, 0); + + // Block the real paste event + return tinymce.dom.Event.cancel(e); + } else { + function block(e) { + e.preventDefault(); + }; + + // Block mousedown and click to prevent selection change + dom.bind(ed.getDoc(), 'mousedown', block); + dom.bind(ed.getDoc(), 'keydown', block); + + or = ed.selection.getRng(); + + // Move select contents inside DIV + n = n.firstChild; + rng = ed.getDoc().createRange(); + rng.setStart(n, 0); + rng.setEnd(n, 2); + sel.setRng(rng); + + // Wait a while and grab the pasted contents + window.setTimeout(function() { + var h = '', nl; + + // Paste divs duplicated in paste divs seems to happen when you paste plain text so lets first look for that broken behavior in WebKit + if (!dom.select('div.mcePaste > div.mcePaste').length) { + nl = dom.select('div.mcePaste'); + + // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string + each(nl, function(n) { + var child = n.firstChild; + + // WebKit inserts a DIV container with lots of odd styles + if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) { + dom.remove(child, 1); + } + + // Remove apply style spans + each(dom.select('span.Apple-style-span', n), function(n) { + dom.remove(n, 1); + }); + + // Remove bogus br elements + each(dom.select('br[data-mce-bogus]', n), function(n) { + dom.remove(n); + }); + + // WebKit will make a copy of the DIV for each line of plain text pasted and insert them into the DIV + if (n.parentNode.className != 'mcePaste') + h += n.innerHTML; + }); + } else { + // Found WebKit weirdness so force the content into paragraphs this seems to happen when you paste plain text from Nodepad etc + // So this logic will replace double enter with paragraphs and single enter with br so it kind of looks the same + h = '

    ' + dom.encode(textContent).replace(/\r?\n\r?\n/g, '

    ').replace(/\r?\n/g, '
    ') + '

    '; + } + + // Remove the nodes + each(dom.select('div.mcePaste'), function(n) { + dom.remove(n); + }); + + // Restore the old selection + if (or) + sel.setRng(or); + + process({content : h}); + + // Unblock events ones we got the contents + dom.unbind(ed.getDoc(), 'mousedown', block); + dom.unbind(ed.getDoc(), 'keydown', block); + }, 0); + } + } + + // Check if we should use the new auto process method + if (getParam(ed, "paste_auto_cleanup_on_paste")) { + // Is it's Opera or older FF use key handler + if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) { + ed.onKeyDown.addToTop(function(ed, e) { + if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) + grabContent(e); + }); + } else { + // Grab contents on paste event on Gecko and WebKit + ed.onPaste.addToTop(function(ed, e) { + return grabContent(e); + }); + } + } + + ed.onInit.add(function() { + ed.controlManager.setActive("pastetext", ed.pasteAsPlainText); + + // Block all drag/drop events + if (getParam(ed, "paste_block_drop")) { + ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) { + e.preventDefault(); + e.stopPropagation(); + + return false; + }); + } + }); + + // Add legacy support + t._legacySupport(); + }, + + getInfo : function() { + return { + longname : 'Paste text/word', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + _preProcess : function(pl, o) { + var ed = this.editor, + h = o.content, + grep = tinymce.grep, + explode = tinymce.explode, + trim = tinymce.trim, + len, stripClass; + + //console.log('Before preprocess:' + o.content); + + function process(items) { + each(items, function(v) { + // Remove or replace + if (v.constructor == RegExp) + h = h.replace(v, ''); + else + h = h.replace(v[0], v[1]); + }); + } + + if (ed.settings.paste_enable_default_filters == false) { + return; + } + + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + if (tinymce.isIE && document.documentMode >= 9) { + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + process([[/(?:
     [\s\r\n]+|
    )*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:
     [\s\r\n]+|
    )*/g, '$1']]); + + // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break + process([ + [/

    /g, '

    '], // Replace multiple BR elements with uppercase BR to keep them intact + [/
    /g, ' '], // Replace single br elements with space since they are word wrap BR:s + [/

    /g, '
    '] // Replace back the double brs but into a single BR + ]); + } + + // Detect Word content and process it more aggressive + if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) { + o.wordContent = true; // Mark the pasted contents as word specific content + //console.log('Word contents detected.'); + + // Process away some basic content + process([ + /^\s*( )+/gi, //   entities at the start of contents + /( |]*>)+\s*$/gi //   entities at the end of contents + ]); + + if (getParam(ed, "paste_convert_headers_to_strong")) { + h = h.replace(/

    ]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "

    $1

    "); + } + + if (getParam(ed, "paste_convert_middot_lists")) { + process([ + [//gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker + [/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item markers + [/(]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to item markers (FF) + ]); + } + + process([ + // Word comments like conditional comments etc + //gi, + + // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags + /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, + + // Convert into for line-though + [/<(\/?)s>/gi, "<$1strike>"], + + // Replace nsbp entites to char since it's easier to handle + [/ /gi, "\u00a0"] + ]); + + // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag. + // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot. + do { + len = h.length; + h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1"); + } while (len != h.length); + + // Remove all spans if no styles is to be retained + if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } else { + // We're keeping styles, so at least clean them up. + // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx + + process([ + // Convert ___ to string of alternating breaking/non-breaking spaces of same length + [/([\s\u00a0]*)<\/span>/gi, + function(str, spaces) { + return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : ""; + } + ], + + // Examine all styles: delete junk, transform some, and keep the rest + [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi, + function(str, tag, style) { + var n = [], + i = 0, + s = explode(trim(style).replace(/"/gi, "'"), ";"); + + // Examine each style definition within the tag's style attribute + each(s, function(v) { + var name, value, + parts = explode(v, ":"); + + function ensureUnits(v) { + return v + ((v !== "0") && (/\d$/.test(v)))? "px" : ""; + } + + if (parts.length == 2) { + name = parts[0].toLowerCase(); + value = parts[1].toLowerCase(); + + // Translate certain MS Office styles into their CSS equivalents + switch (name) { + case "mso-padding-alt": + case "mso-padding-top-alt": + case "mso-padding-right-alt": + case "mso-padding-bottom-alt": + case "mso-padding-left-alt": + case "mso-margin-alt": + case "mso-margin-top-alt": + case "mso-margin-right-alt": + case "mso-margin-bottom-alt": + case "mso-margin-left-alt": + case "mso-table-layout-alt": + case "mso-height": + case "mso-width": + case "mso-vertical-align-alt": + n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value); + return; + + case "horiz-align": + n[i++] = "text-align:" + value; + return; + + case "vert-align": + n[i++] = "vertical-align:" + value; + return; + + case "font-color": + case "mso-foreground": + n[i++] = "color:" + value; + return; + + case "mso-background": + case "mso-highlight": + n[i++] = "background:" + value; + return; + + case "mso-default-height": + n[i++] = "min-height:" + ensureUnits(value); + return; + + case "mso-default-width": + n[i++] = "min-width:" + ensureUnits(value); + return; + + case "mso-padding-between-alt": + n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value); + return; + + case "text-line-through": + if ((value == "single") || (value == "double")) { + n[i++] = "text-decoration:line-through"; + } + return; + + case "mso-zero-height": + if (value == "yes") { + n[i++] = "display:none"; + } + return; + } + + // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name + if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) { + return; + } + + // If it reached this point, it must be a valid CSS style + n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case + } + }); + + // If style attribute contained any valid styles the re-write it; otherwise delete style attribute. + if (i > 0) { + return tag + ' style="' + n.join(';') + '"'; + } else { + return tag; + } + } + ] + ]); + } + } + + // Replace headers with + if (getParam(ed, "paste_convert_headers_to_strong")) { + process([ + [/]*>/gi, "

    "], + [/<\/h[1-6][^>]*>/gi, "

    "] + ]); + } + + process([ + // Copy paste from Java like Open Office will produce this junk on FF + [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, ''] + ]); + + // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso"). + // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation. + stripClass = getParam(ed, "paste_strip_class_attributes"); + + if (stripClass !== "none") { + function removeClasses(match, g1) { + if (stripClass === "all") + return ''; + + var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "), + function(v) { + return (/^(?!mso)/i.test(v)); + } + ); + + return cls.length ? ' class="' + cls.join(" ") + '"' : ''; + }; + + h = h.replace(/ class="([^"]+)"/gi, removeClasses); + h = h.replace(/ class=([\-\w]+)/gi, removeClasses); + } + + // Remove spans option + if (getParam(ed, "paste_remove_spans")) { + h = h.replace(/<\/?span[^>]*>/gi, ""); + } + + //console.log('After preprocess:' + h); + + o.content = h; + }, + + /** + * Various post process items. + */ + _postProcess : function(pl, o) { + var t = this, ed = t.editor, dom = ed.dom, styleProps; + + if (ed.settings.paste_enable_default_filters == false) { + return; + } + + if (o.wordContent) { + // Remove named anchors or TOC links + each(dom.select('a', o.node), function(a) { + if (!a.href || a.href.indexOf('#_Toc') != -1) + dom.remove(a, 1); + }); + + if (getParam(ed, "paste_convert_middot_lists")) { + t._convertLists(pl, o); + } + + // Process styles + styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties + + // Process only if a string was specified and not equal to "all" or "*" + if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) { + styleProps = tinymce.explode(styleProps.replace(/^none$/i, "")); + + // Retains some style properties + each(dom.select('*', o.node), function(el) { + var newStyle = {}, npc = 0, i, sp, sv; + + // Store a subset of the existing styles + if (styleProps) { + for (i = 0; i < styleProps.length; i++) { + sp = styleProps[i]; + sv = dom.getStyle(el, sp); + + if (sv) { + newStyle[sp] = sv; + npc++; + } + } + } + + // Remove all of the existing styles + dom.setAttrib(el, 'style', ''); + + if (styleProps && npc > 0) + dom.setStyles(el, newStyle); // Add back the stored subset of styles + else // Remove empty span tags that do not have class attributes + if (el.nodeName == 'SPAN' && !el.className) + dom.remove(el, true); + }); + } + } + + // Remove all style information or only specifically on WebKit to avoid the style bug on that browser + if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) { + each(dom.select('*[style]', o.node), function(el) { + el.removeAttribute('style'); + el.removeAttribute('data-mce-style'); + }); + } else { + if (tinymce.isWebKit) { + // We need to compress the styles on WebKit since if you paste it will become + // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles + each(dom.select('*', o.node), function(el) { + el.removeAttribute('data-mce-style'); + }); + } + } + }, + + /** + * Converts the most common bullet and number formats in Office into a real semantic UL/LI list. + */ + _convertLists : function(pl, o) { + var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html; + + // Convert middot lists into real semantic lists + each(dom.select('p', o.node), function(p) { + var sib, val = '', type, html, idx, parents; + + // Get text node value at beginning of paragraph + for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling) + val += sib.nodeValue; + + val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0'); + + // Detect unordered lists look for bullets + if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(val)) + type = 'ul'; + + // Detect ordered lists 1., a. or ixv. + if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(val)) + type = 'ol'; + + // Check if node value matches the list pattern: o   + if (type) { + margin = parseFloat(p.style.marginLeft || 0); + + if (margin > lastMargin) + levels.push(margin); + + if (!listElm || type != lastType) { + listElm = dom.create(type); + dom.insertAfter(listElm, p); + } else { + // Nested list element + if (margin > lastMargin) { + listElm = li.appendChild(dom.create(type)); + } else if (margin < lastMargin) { + // Find parent level based on margin value + idx = tinymce.inArray(levels, margin); + parents = dom.getParents(listElm.parentNode, type); + listElm = parents[parents.length - 1 - idx] || listElm; + } + } + + // Remove middot or number spans if they exists + each(dom.select('span', p), function(span) { + var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, ''); + + // Remove span with the middot or the number + if (type == 'ul' && /^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(html)) + dom.remove(span); + else if (/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(html)) + dom.remove(span); + }); + + html = p.innerHTML; + + // Remove middot/list items + if (type == 'ul') + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/, ''); + else + html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, ''); + + // Create li and add paragraph data into the new li + li = listElm.appendChild(dom.create('li', 0, html)); + dom.remove(p); + + lastMargin = margin; + lastType = type; + } else + listElm = lastMargin = 0; // End list element + }); + + // Remove any left over makers + html = o.node.innerHTML; + if (html.indexOf('__MCE_ITEM__') != -1) + o.node.innerHTML = html.replace(/__MCE_ITEM__/g, ''); + }, + + /** + * Inserts the specified contents at the caret position. + */ + _insert : function(h, skip_undo) { + var ed = this.editor, r = ed.selection.getRng(); + + // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells. + if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer) + ed.getDoc().execCommand('Delete', false, null); + + ed.execCommand('mceInsertContent', false, h, {skip_undo : skip_undo}); + }, + + /** + * Instead of the old plain text method which tried to re-create a paste operation, the + * new approach adds a plain text mode toggle switch that changes the behavior of paste. + * This function is passed the same input that the regular paste plugin produces. + * It performs additional scrubbing and produces (and inserts) the plain text. + * This approach leverages all of the great existing functionality in the paste + * plugin, and requires minimal changes to add the new functionality. + * Speednet - June 2009 + */ + _insertPlainText : function(content) { + var ed = this.editor, + linebr = getParam(ed, "paste_text_linebreaktype"), + rl = getParam(ed, "paste_text_replacements"), + is = tinymce.is; + + function process(items) { + each(items, function(v) { + if (v.constructor == RegExp) + content = content.replace(v, ""); + else + content = content.replace(v[0], v[1]); + }); + }; + + if ((typeof(content) === "string") && (content.length > 0)) { + // If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line + if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(content)) { + process([ + /[\n\r]+/g + ]); + } else { + // Otherwise just get rid of carriage returns (only need linefeeds) + process([ + /\r+/g + ]); + } + + process([ + [/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank line after them + [/]*>|<\/tr>/gi, "\n"], // Single linebreak for
    tags and table rows + [/<\/t[dh]>\s*]*>/gi, "\t"], // Table cells get tabs betweem them + /<[a-z!\/?][^>]*>/gi, // Delete all remaining tags + [/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*) + [/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"],// Cool little RegExp deletes whitespace around linebreak chars. + [/\n{3,}/g, "\n\n"] // Max. 2 consecutive linebreaks + ]); + + content = ed.dom.decode(tinymce.html.Entities.encodeRaw(content)); + + // Perform default or custom replacements + if (is(rl, "array")) { + process(rl); + } else if (is(rl, "string")) { + process(new RegExp(rl, "gi")); + } + + // Treat paragraphs as specified in the config + if (linebr == "none") { + // Convert all line breaks to space + process([ + [/\n+/g, " "] + ]); + } else if (linebr == "br") { + // Convert all line breaks to
    + process([ + [/\n/g, "
    "] + ]); + } else if (linebr == "p") { + // Convert all line breaks to

    ...

    + process([ + [/\n+/g, "

    "], + [/^(.*<\/p>)(

    )$/, '

    $1'] + ]); + } else { + // defaults to "combined" + // Convert single line breaks to
    and double line breaks to

    ...

    + process([ + [/\n\n/g, "

    "], + [/^(.*<\/p>)(

    )$/, '

    $1'], + [/\n/g, "
    "] + ]); + } + + ed.execCommand('mceInsertContent', false, content); + } + }, + + /** + * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine. + */ + _legacySupport : function() { + var t = this, ed = t.editor; + + // Register command(s) for backwards compatibility + ed.addCommand("mcePasteWord", function() { + ed.windowManager.open({ + file: t.url + "/pasteword.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline: 1 + }); + }); + + if (getParam(ed, "paste_text_use_dialog")) { + ed.addCommand("mcePasteText", function() { + ed.windowManager.open({ + file : t.url + "/pastetext.htm", + width: parseInt(getParam(ed, "paste_dialog_width")), + height: parseInt(getParam(ed, "paste_dialog_height")), + inline : 1 + }); + }); + } + + // Register button for backwards compatibility + ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"}); + } + }); + + // Register plugin + tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin); +})(); diff --git a/js/tiny_mce/plugins/paste/langs/en_dlg.js b/js/tiny_mce/plugins/paste/langs/en_dlg.js index eeac7789..bc74daf8 100644 --- a/js/tiny_mce/plugins/paste/langs/en_dlg.js +++ b/js/tiny_mce/plugins/paste/langs/en_dlg.js @@ -1,5 +1 @@ -tinyMCE.addI18n('en.paste_dlg',{ -text_title:"Use CTRL+V on your keyboard to paste the text into the window.", -text_linebreaks:"Keep linebreaks", -word_title:"Use CTRL+V on your keyboard to paste the text into the window." -}); \ No newline at end of file +tinyMCE.addI18n('en.paste_dlg',{"word_title":"Use Ctrl+V on your keyboard to paste the text into the window.","text_linebreaks":"Keep Linebreaks","text_title":"Use Ctrl+V on your keyboard to paste the text into the window."}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/searchreplace/editor_plugin.js b/js/tiny_mce/plugins/searchreplace/editor_plugin.js index cd9c985b..165bc12d 100644 --- a/js/tiny_mce/plugins/searchreplace/editor_plugin.js +++ b/js/tiny_mce/plugins/searchreplace/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:170+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("search",{title:"searchreplace.search_desc",cmd:"mceSearch"});a.addButton("replace",{title:"searchreplace.replace_desc",cmd:"mceReplace"});a.addShortcut("ctrl+f","searchreplace.search_desc","mceSearch")},getInfo:function(){return{longname:"Search/Replace",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("searchreplace",tinymce.plugins.SearchReplacePlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){window.focus();a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:170+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("search",{title:"searchreplace.search_desc",cmd:"mceSearch"});a.addButton("replace",{title:"searchreplace.replace_desc",cmd:"mceReplace"});a.addShortcut("ctrl+f","searchreplace.search_desc","mceSearch")},getInfo:function(){return{longname:"Search/Replace",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("searchreplace",tinymce.plugins.SearchReplacePlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/searchreplace/editor_plugin_src.js b/js/tiny_mce/plugins/searchreplace/editor_plugin_src.js index 1433a06a..b0c013fd 100644 --- a/js/tiny_mce/plugins/searchreplace/editor_plugin_src.js +++ b/js/tiny_mce/plugins/searchreplace/editor_plugin_src.js @@ -1,57 +1,61 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.SearchReplacePlugin', { - init : function(ed, url) { - function open(m) { - ed.windowManager.open({ - file : url + '/searchreplace.htm', - width : 420 + parseInt(ed.getLang('searchreplace.delta_width', 0)), - height : 170 + parseInt(ed.getLang('searchreplace.delta_height', 0)), - inline : 1, - auto_focus : 0 - }, { - mode : m, - search_string : ed.selection.getContent({format : 'text'}), - plugin_url : url - }); - }; - - // Register commands - ed.addCommand('mceSearch', function() { - open('search'); - }); - - ed.addCommand('mceReplace', function() { - open('replace'); - }); - - // Register buttons - ed.addButton('search', {title : 'searchreplace.search_desc', cmd : 'mceSearch'}); - ed.addButton('replace', {title : 'searchreplace.replace_desc', cmd : 'mceReplace'}); - - ed.addShortcut('ctrl+f', 'searchreplace.search_desc', 'mceSearch'); - }, - - getInfo : function() { - return { - longname : 'Search/Replace', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('searchreplace', tinymce.plugins.SearchReplacePlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.SearchReplacePlugin', { + init : function(ed, url) { + function open(m) { + // Keep IE from writing out the f/r character to the editor + // instance while initializing a new dialog. See: #3131190 + window.focus(); + + ed.windowManager.open({ + file : url + '/searchreplace.htm', + width : 420 + parseInt(ed.getLang('searchreplace.delta_width', 0)), + height : 170 + parseInt(ed.getLang('searchreplace.delta_height', 0)), + inline : 1, + auto_focus : 0 + }, { + mode : m, + search_string : ed.selection.getContent({format : 'text'}), + plugin_url : url + }); + }; + + // Register commands + ed.addCommand('mceSearch', function() { + open('search'); + }); + + ed.addCommand('mceReplace', function() { + open('replace'); + }); + + // Register buttons + ed.addButton('search', {title : 'searchreplace.search_desc', cmd : 'mceSearch'}); + ed.addButton('replace', {title : 'searchreplace.replace_desc', cmd : 'mceReplace'}); + + ed.addShortcut('ctrl+f', 'searchreplace.search_desc', 'mceSearch'); + }, + + getInfo : function() { + return { + longname : 'Search/Replace', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('searchreplace', tinymce.plugins.SearchReplacePlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/searchreplace/js/searchreplace.js b/js/tiny_mce/plugins/searchreplace/js/searchreplace.js index c0a62432..b1630ca8 100644 --- a/js/tiny_mce/plugins/searchreplace/js/searchreplace.js +++ b/js/tiny_mce/plugins/searchreplace/js/searchreplace.js @@ -1,130 +1,142 @@ -tinyMCEPopup.requireLangPack(); - -var SearchReplaceDialog = { - init : function(ed) { - var f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); - - this.switchMode(m); - - f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string"); - - // Focus input field - f[m + '_panel_searchstring'].focus(); - }, - - switchMode : function(m) { - var f, lm = this.lastMode; - - if (lm != m) { - f = document.forms[0]; - - if (lm) { - f[m + '_panel_searchstring'].value = f[lm + '_panel_searchstring'].value; - f[m + '_panel_backwardsu'].checked = f[lm + '_panel_backwardsu'].checked; - f[m + '_panel_backwardsd'].checked = f[lm + '_panel_backwardsd'].checked; - f[m + '_panel_casesensitivebox'].checked = f[lm + '_panel_casesensitivebox'].checked; - } - - mcTabs.displayTab(m + '_tab', m + '_panel'); - document.getElementById("replaceBtn").style.display = (m == "replace") ? "inline" : "none"; - document.getElementById("replaceAllBtn").style.display = (m == "replace") ? "inline" : "none"; - this.lastMode = m; - } - }, - - searchNext : function(a) { - var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0; - - // Get input - f = document.forms[0]; - s = f[m + '_panel_searchstring'].value; - b = f[m + '_panel_backwardsu'].checked; - ca = f[m + '_panel_casesensitivebox'].checked; - rs = f['replace_panel_replacestring'].value; - - if (s == '') - return; - - function fix() { - // Correct Firefox graphics glitches - r = se.getRng().cloneRange(); - ed.getDoc().execCommand('SelectAll', false, null); - se.setRng(r); - }; - - function replace() { - if (tinymce.isIE) - ed.selection.getRng().duplicate().pasteHTML(rs); // Needs to be duplicated due to selection bug in IE - else - ed.getDoc().execCommand('InsertHTML', false, rs); - }; - - // IE flags - if (ca) - fl = fl | 4; - - switch (a) { - case 'all': - // Move caret to beginning of text - ed.execCommand('SelectAll'); - ed.selection.collapse(true); - - if (tinymce.isIE) { - while (r.findText(s, b ? -1 : 1, fl)) { - r.scrollIntoView(); - r.select(); - replace(); - fo = 1; - - if (b) { - r.moveEnd("character", -(rs.length)); // Otherwise will loop forever - } - } - - tinyMCEPopup.storeSelection(); - } else { - while (w.find(s, ca, b, false, false, false, false)) { - replace(); - fo = 1; - } - } - - if (fo) - tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.allreplaced')); - else - tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); - - return; - - case 'current': - if (!ed.selection.isCollapsed()) - replace(); - - break; - } - - se.collapse(b); - r = se.getRng(); - - // Whats the point - if (!s) - return; - - if (tinymce.isIE) { - if (r.findText(s, b ? -1 : 1, fl)) { - r.scrollIntoView(); - r.select(); - } else - tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); - - tinyMCEPopup.storeSelection(); - } else { - if (!w.find(s, ca, b, false, false, false, false)) - tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); - else - fix(); - } - } -}; - -tinyMCEPopup.onInit.add(SearchReplaceDialog.init, SearchReplaceDialog); +tinyMCEPopup.requireLangPack(); + +var SearchReplaceDialog = { + init : function(ed) { + var t = this, f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode"); + + t.switchMode(m); + + f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string"); + + // Focus input field + f[m + '_panel_searchstring'].focus(); + + mcTabs.onChange.add(function(tab_id, panel_id) { + t.switchMode(tab_id.substring(0, tab_id.indexOf('_'))); + }); + }, + + switchMode : function(m) { + var f, lm = this.lastMode; + + if (lm != m) { + f = document.forms[0]; + + if (lm) { + f[m + '_panel_searchstring'].value = f[lm + '_panel_searchstring'].value; + f[m + '_panel_backwardsu'].checked = f[lm + '_panel_backwardsu'].checked; + f[m + '_panel_backwardsd'].checked = f[lm + '_panel_backwardsd'].checked; + f[m + '_panel_casesensitivebox'].checked = f[lm + '_panel_casesensitivebox'].checked; + } + + mcTabs.displayTab(m + '_tab', m + '_panel'); + document.getElementById("replaceBtn").style.display = (m == "replace") ? "inline" : "none"; + document.getElementById("replaceAllBtn").style.display = (m == "replace") ? "inline" : "none"; + this.lastMode = m; + } + }, + + searchNext : function(a) { + var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0; + + // Get input + f = document.forms[0]; + s = f[m + '_panel_searchstring'].value; + b = f[m + '_panel_backwardsu'].checked; + ca = f[m + '_panel_casesensitivebox'].checked; + rs = f['replace_panel_replacestring'].value; + + if (tinymce.isIE) { + r = ed.getDoc().selection.createRange(); + } + + if (s == '') + return; + + function fix() { + // Correct Firefox graphics glitches + // TODO: Verify if this is actually needed any more, maybe it was for very old FF versions? + r = se.getRng().cloneRange(); + ed.getDoc().execCommand('SelectAll', false, null); + se.setRng(r); + }; + + function replace() { + ed.selection.setContent(rs); // Needs to be duplicated due to selection bug in IE + }; + + // IE flags + if (ca) + fl = fl | 4; + + switch (a) { + case 'all': + // Move caret to beginning of text + ed.execCommand('SelectAll'); + ed.selection.collapse(true); + + if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + + while (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + replace(); + fo = 1; + + if (b) { + r.moveEnd("character", -(rs.length)); // Otherwise will loop forever + } + } + + tinyMCEPopup.storeSelection(); + } else { + while (w.find(s, ca, b, false, false, false, false)) { + replace(); + fo = 1; + } + } + + if (fo) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.allreplaced')); + else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + return; + + case 'current': + if (!ed.selection.isCollapsed()) + replace(); + + break; + } + + se.collapse(b); + r = se.getRng(); + + // Whats the point + if (!s) + return; + + if (tinymce.isIE) { + ed.focus(); + r = ed.getDoc().selection.createRange(); + + if (r.findText(s, b ? -1 : 1, fl)) { + r.scrollIntoView(); + r.select(); + } else + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + + tinyMCEPopup.storeSelection(); + } else { + if (!w.find(s, ca, b, false, false, false, false)) + tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound')); + else + fix(); + } + } +}; + +tinyMCEPopup.onInit.add(SearchReplaceDialog.init, SearchReplaceDialog); diff --git a/js/tiny_mce/plugins/searchreplace/langs/en_dlg.js b/js/tiny_mce/plugins/searchreplace/langs/en_dlg.js index 370959af..8a659009 100644 --- a/js/tiny_mce/plugins/searchreplace/langs/en_dlg.js +++ b/js/tiny_mce/plugins/searchreplace/langs/en_dlg.js @@ -1,16 +1 @@ -tinyMCE.addI18n('en.searchreplace_dlg',{ -searchnext_desc:"Find again", -notfound:"The search has been completed. The search string could not be found.", -search_title:"Find", -replace_title:"Find/Replace", -allreplaced:"All occurrences of the search string were replaced.", -findwhat:"Find what", -replacewith:"Replace with", -direction:"Direction", -up:"Up", -down:"Down", -mcase:"Match case", -findnext:"Find next", -replace:"Replace", -replaceall:"Replace all" -}); \ No newline at end of file +tinyMCE.addI18n('en.searchreplace_dlg',{findwhat:"Find What",replacewith:"Replace with",direction:"Direction",up:"Up",down:"Down",mcase:"Match Case",findnext:"Find Next",allreplaced:"All occurrences of the search string were replaced.","searchnext_desc":"Find Again",notfound:"The search has been completed. The search string could not be found.","search_title":"Find","replace_title":"Find/Replace",replaceall:"Replace All",replace:"Replace"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/searchreplace/searchreplace.htm b/js/tiny_mce/plugins/searchreplace/searchreplace.htm index d0424cfc..bac5a184 100644 --- a/js/tiny_mce/plugins/searchreplace/searchreplace.htm +++ b/js/tiny_mce/plugins/searchreplace/searchreplace.htm @@ -1,99 +1,100 @@ - - - - {#searchreplace_dlg.replace_title} - - - - - - - -

    - - -
    -
    - - - - - - - - - - - -
    - - - - - - - - -
    -
    - - - - - -
    -
    -
    - -
    - - - - - - - - - - - - - - - -
    - - - - - - - - -
    -
    - - - - - -
    -
    -
    - -
    - -
    - - - - -
    -
    - - + + + + {#searchreplace_dlg.replace_title} + + + + + + + + +
    + + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + +
    +
    +
    + +
    + + + + + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + +
    +
    +
    + +
    + +
    + + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/spellchecker/editor_plugin.js b/js/tiny_mce/plugins/spellchecker/editor_plugin.js index 377e4e8a..71fbb68a 100644 --- a/js/tiny_mce/plugins/spellchecker/editor_plugin.js +++ b/js/tiny_mce/plugins/spellchecker/editor_plugin.js @@ -1 +1 @@ -(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;e.addCommand("mceSpellCheck",function(){if(!g.active){e.setProgressState(1);g._sendRPC("checkWords",[g.selectedLang,g._getWords()],function(h){if(h.length>0){g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgressState(0);e.windowManager.alert("spellchecker.no_mpell")}})}else{g._done()}});e.onInit.add(function(){if(e.settings.content_css!==false){e.dom.loadCSS(f+"/css/content.css")}});e.onClick.add(g._showMenu,g);e.onContextMenu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active){g._removeWords()}});e.onNodeChange.add(function(i,h){h.setActive("spellchecker",g.active)});e.onSetContent.add(function(){g._done()});e.onBeforeGetContent.add(function(){g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen"){g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0){h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d){var f=this,g,e=f.editor;if(h=="spellchecker"){g=d.createSplitButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(function(j,i){i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);c(f.languages,function(n,m){var p={icon:1},l;p.onclick=function(){l.setSelected(1);f.selectedItem.setSelected(0);f.selectedItem=l;f.selectedLang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);if(n==f.selectedLang){f.selectedItem=l}})});return g}},_walk:function(i,g){var h=this.editor.getDoc(),e;if(h.createTreeWalker){e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!=null){g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function(){var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$%&()*+,-./:;<=>?@[]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');for(d=0;d$1$2');q=q.replace(g,'$1$2');j.replace(j.create("span",{"class":"mceItemHidden"},q),r)}}});l.moveToBookmark(m)},_showMenu:function(g,i){var h=this,g=h.editor,d=h._menu,k,j=g.dom,f=j.getViewPort(g.getWin());if(!d){k=b.getPos(g.getContentAreaContainer());d=g.controlManager.createDropMenu("spellcheckermenu",{offset_x:k.x,offset_y:k.y,"class":"mceNoIcons"});h._menu=d}if(j.hasClass(i.target,"mceItemHiddenSpellWord")){d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDisabled(1);h._sendRPC("getSuggestions",[h.selectedLang,j.decode(i.target.innerHTML)],function(e){d.removeAll();if(e.length>0){d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(e,function(l){d.add({title:l,onclick:function(){j.replace(g.getDoc().createTextNode(l),i.target);h._checkDone()}})});d.addSeparator()}else{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}d.add({title:"spellchecker.ignore_word",onclick:function(){j.remove(i.target,1);h._checkDone()}});d.add({title:"spellchecker.ignore_words",onclick:function(){h._removeWords(j.decode(i.target.innerHTML));h._checkDone()}});d.update()});g.selection.select(i.target);k=j.getPos(i.target);d.showMenu(k.x,k.y+i.target.offsetHeight-f.y);return tinymce.dom.Event.cancel(i)}else{d.hideMenu()}},_checkDone:function(){var e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h){if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f){e._done()}},_done:function(){var d=this,e=d.active;if(d.active){d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e){d.editor.nodeChanged()}}},_sendRPC:function(e,h,d){var g=this,f=g.editor.getParam("spellchecker_rpc_url","{backend}");if(f=="{backend}"){g.editor.setProgressState(0);alert("Please specify: spellchecker_rpc_url");return}a.sendRPC({url:f,method:e,params:h,success:d,error:function(j,i){g.editor.setProgressState(0);g.editor.windowManager.alert(j.errstr||("Error response: "+i.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.SpellcheckerPlugin)})(); \ No newline at end of file +(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;g.rpcUrl=e.getParam("spellchecker_rpc_url","{backend}");if(g.rpcUrl=="{backend}"){if(tinymce.isIE){return}g.hasSupport=true;e.onContextMenu.addToTop(function(h,i){if(g.active){return false}})}e.addCommand("mceSpellCheck",function(){if(g.rpcUrl=="{backend}"){g.editor.getBody().spellcheck=g.active=!g.active;return}if(!g.active){e.setProgressState(1);g._sendRPC("checkWords",[g.selectedLang,g._getWords()],function(h){if(h.length>0){g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgressState(0);if(e.getParam("spellchecker_report_no_misspellings",true)){e.windowManager.alert("spellchecker.no_mpell")}}})}else{g._done()}});if(e.settings.content_css!==false){e.contentCSS.push(f+"/css/content.css")}e.onClick.add(g._showMenu,g);e.onContextMenu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active){g._removeWords()}});e.onNodeChange.add(function(i,h){h.setActive("spellchecker",g.active)});e.onSetContent.add(function(){g._done()});e.onBeforeGetContent.add(function(){g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen"){g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0){h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d){var f=this,g,e=f.editor;if(h=="spellchecker"){if(f.rpcUrl=="{backend}"){if(f.hasSupport){g=d.createButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f})}return g}g=d.createSplitButton(h,{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(function(j,i){i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);c(f.languages,function(n,m){var p={icon:1},l;p.onclick=function(){if(n==f.selectedLang){return}l.setSelected(1);f.selectedItem.setSelected(0);f.selectedItem=l;f.selectedLang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);if(n==f.selectedLang){f.selectedItem=l}})});return g}},_walk:function(i,g){var h=this.editor.getDoc(),e;if(h.createTreeWalker){e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!=null){g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function(){var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$%&()*+,-./:;<=>?@[]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c');for(d=0;d$2");while((s=p.indexOf(""))!=-1){o=p.substring(0,s);if(o.length){r=j.createTextNode(f.decode(o));q.appendChild(r)}p=p.substring(s+10);s=p.indexOf("");o=p.substring(0,s);p=p.substring(s+11);q.appendChild(f.create("span",{"class":"mceItemHiddenSpellWord"},o))}if(p.length){r=j.createTextNode(f.decode(p));q.appendChild(r)}}else{q.innerHTML=p.replace(e,'$1$2')}f.replace(q,t)}});h.moveToBookmark(i)},_showMenu:function(h,j){var i=this,h=i.editor,d=i._menu,l,k=h.dom,g=k.getViewPort(h.getWin()),f=j.target;j=0;if(!d){d=h.controlManager.createDropMenu("spellcheckermenu",{"class":"mceNoIcons"});i._menu=d}if(k.hasClass(f,"mceItemHiddenSpellWord")){d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDisabled(1);i._sendRPC("getSuggestions",[i.selectedLang,k.decode(f.innerHTML)],function(m){var e;d.removeAll();if(m.length>0){d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(m,function(n){d.add({title:n,onclick:function(){k.replace(h.getDoc().createTextNode(n),f);i._checkDone()}})});d.addSeparator()}else{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}if(h.getParam("show_ignore_words",true)){e=i.editor.getParam("spellchecker_enable_ignore_rpc","");d.add({title:"spellchecker.ignore_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}}});d.add({title:"spellchecker.ignore_words",onclick:function(){var n=f.innerHTML;i._removeWords(k.decode(n));i._checkDone();if(e){h.setProgressState(1);i._sendRPC("ignoreWords",[i.selectedLang,n],function(o){h.setProgressState(0)})}}})}if(i.editor.getParam("spellchecker_enable_learn_rpc")){d.add({title:"spellchecker.learn_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();h.setProgressState(1);i._sendRPC("learnWord",[i.selectedLang,n],function(o){h.setProgressState(0)})}})}d.update()});l=b.getPos(h.getContentAreaContainer());d.settings.offset_x=l.x;d.settings.offset_y=l.y;h.selection.select(f);l=k.getPos(f);d.showMenu(l.x,l.y+f.offsetHeight-g.y);return tinymce.dom.Event.cancel(j)}else{d.hideMenu()}},_checkDone:function(){var e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h){if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f){e._done()}},_done:function(){var d=this,e=d.active;if(d.active){d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e){d.editor.nodeChanged()}}},_sendRPC:function(e,g,d){var f=this;a.sendRPC({url:f.rpcUrl,method:e,params:g,success:d,error:function(i,h){f.editor.setProgressState(0);f.editor.windowManager.alert(i.errstr||("Error response: "+h.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.SpellcheckerPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js b/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js index 0b3d3b60..24eec5a0 100644 --- a/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js +++ b/js/tiny_mce/plugins/spellchecker/editor_plugin_src.js @@ -1,341 +1,436 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM; - - tinymce.create('tinymce.plugins.SpellcheckerPlugin', { - getInfo : function() { - return { - longname : 'Spellchecker', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - init : function(ed, url) { - var t = this, cm; - - t.url = url; - t.editor = ed; - - // Register commands - ed.addCommand('mceSpellCheck', function() { - if (!t.active) { - ed.setProgressState(1); - t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) { - if (r.length > 0) { - t.active = 1; - t._markWords(r); - ed.setProgressState(0); - ed.nodeChanged(); - } else { - ed.setProgressState(0); - ed.windowManager.alert('spellchecker.no_mpell'); - } - }); - } else - t._done(); - }); - - ed.onInit.add(function() { - if (ed.settings.content_css !== false) - ed.dom.loadCSS(url + '/css/content.css'); - }); - - ed.onClick.add(t._showMenu, t); - ed.onContextMenu.add(t._showMenu, t); - ed.onBeforeGetContent.add(function() { - if (t.active) - t._removeWords(); - }); - - ed.onNodeChange.add(function(ed, cm) { - cm.setActive('spellchecker', t.active); - }); - - ed.onSetContent.add(function() { - t._done(); - }); - - ed.onBeforeGetContent.add(function() { - t._done(); - }); - - ed.onBeforeExecCommand.add(function(ed, cmd) { - if (cmd == 'mceFullScreen') - t._done(); - }); - - // Find selected language - t.languages = {}; - each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) { - if (k.indexOf('+') === 0) { - k = k.substring(1); - t.selectedLang = v; - } - - t.languages[k] = v; - }); - }, - - createControl : function(n, cm) { - var t = this, c, ed = t.editor; - - if (n == 'spellchecker') { - c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); - - c.onRenderMenu.add(function(c, m) { - m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1); - each(t.languages, function(v, k) { - var o = {icon : 1}, mi; - - o.onclick = function() { - mi.setSelected(1); - t.selectedItem.setSelected(0); - t.selectedItem = mi; - t.selectedLang = v; - }; - - o.title = k; - mi = m.add(o); - mi.setSelected(v == t.selectedLang); - - if (v == t.selectedLang) - t.selectedItem = mi; - }) - }); - - return c; - } - }, - - // Internal functions - - _walk : function(n, f) { - var d = this.editor.getDoc(), w; - - if (d.createTreeWalker) { - w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); - - while ((n = w.nextNode()) != null) - f.call(this, n); - } else - tinymce.walk(n, f, 'childNodes'); - }, - - _getSeparators : function() { - var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}§©«®±¶·¸»¼½¾¿×÷¤\u201d\u201c'); - - // Build word separator regexp - for (i=0; i$1$2'); - v = v.replace(r3, '$1$2'); - - dom.replace(dom.create('span', {'class' : 'mceItemHidden'}, v), n); - } - } - }); - - se.moveToBookmark(b); - }, - - _showMenu : function(ed, e) { - var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()); - - if (!m) { - p1 = DOM.getPos(ed.getContentAreaContainer()); - //p2 = DOM.getPos(ed.getContainer()); - - m = ed.controlManager.createDropMenu('spellcheckermenu', { - offset_x : p1.x, - offset_y : p1.y, - 'class' : 'mceNoIcons' - }); - - t._menu = m; - } - - if (dom.hasClass(e.target, 'mceItemHiddenSpellWord')) { - m.removeAll(); - m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1); - - t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(e.target.innerHTML)], function(r) { - m.removeAll(); - - if (r.length > 0) { - m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); - each(r, function(v) { - m.add({title : v, onclick : function() { - dom.replace(ed.getDoc().createTextNode(v), e.target); - t._checkDone(); - }}); - }); - - m.addSeparator(); - } else - m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); - - m.add({ - title : 'spellchecker.ignore_word', - onclick : function() { - dom.remove(e.target, 1); - t._checkDone(); - } - }); - - m.add({ - title : 'spellchecker.ignore_words', - onclick : function() { - t._removeWords(dom.decode(e.target.innerHTML)); - t._checkDone(); - } - }); - - m.update(); - }); - - ed.selection.select(e.target); - p1 = dom.getPos(e.target); - m.showMenu(p1.x, p1.y + e.target.offsetHeight - vp.y); - - return tinymce.dom.Event.cancel(e); - } else - m.hideMenu(); - }, - - _checkDone : function() { - var t = this, ed = t.editor, dom = ed.dom, o; - - each(dom.select('span'), function(n) { - if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) { - o = true; - return false; - } - }); - - if (!o) - t._done(); - }, - - _done : function() { - var t = this, la = t.active; - - if (t.active) { - t.active = 0; - t._removeWords(); - - if (t._menu) - t._menu.hideMenu(); - - if (la) - t.editor.nodeChanged(); - } - }, - - _sendRPC : function(m, p, cb) { - var t = this, url = t.editor.getParam("spellchecker_rpc_url", "{backend}"); - - if (url == '{backend}') { - t.editor.setProgressState(0); - alert('Please specify: spellchecker_rpc_url'); - return; - } - - JSONRequest.sendRPC({ - url : url, - method : m, - params : p, - success : cb, - error : function(e, x) { - t.editor.setProgressState(0); - t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText)); - } - }); - } - }); - - // Register plugin - tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin); -})(); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM; + + tinymce.create('tinymce.plugins.SpellcheckerPlugin', { + getInfo : function() { + return { + longname : 'Spellchecker', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + }, + + init : function(ed, url) { + var t = this, cm; + + t.url = url; + t.editor = ed; + t.rpcUrl = ed.getParam("spellchecker_rpc_url", "{backend}"); + + if (t.rpcUrl == '{backend}') { + // Sniff if the browser supports native spellchecking (Don't know of a better way) + if (tinymce.isIE) + return; + + t.hasSupport = true; + + // Disable the context menu when spellchecking is active + ed.onContextMenu.addToTop(function(ed, e) { + if (t.active) + return false; + }); + } + + // Register commands + ed.addCommand('mceSpellCheck', function() { + if (t.rpcUrl == '{backend}') { + // Enable/disable native spellchecker + t.editor.getBody().spellcheck = t.active = !t.active; + return; + } + + if (!t.active) { + ed.setProgressState(1); + t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) { + if (r.length > 0) { + t.active = 1; + t._markWords(r); + ed.setProgressState(0); + ed.nodeChanged(); + } else { + ed.setProgressState(0); + + if (ed.getParam('spellchecker_report_no_misspellings', true)) + ed.windowManager.alert('spellchecker.no_mpell'); + } + }); + } else + t._done(); + }); + + if (ed.settings.content_css !== false) + ed.contentCSS.push(url + '/css/content.css'); + + ed.onClick.add(t._showMenu, t); + ed.onContextMenu.add(t._showMenu, t); + ed.onBeforeGetContent.add(function() { + if (t.active) + t._removeWords(); + }); + + ed.onNodeChange.add(function(ed, cm) { + cm.setActive('spellchecker', t.active); + }); + + ed.onSetContent.add(function() { + t._done(); + }); + + ed.onBeforeGetContent.add(function() { + t._done(); + }); + + ed.onBeforeExecCommand.add(function(ed, cmd) { + if (cmd == 'mceFullScreen') + t._done(); + }); + + // Find selected language + t.languages = {}; + each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) { + if (k.indexOf('+') === 0) { + k = k.substring(1); + t.selectedLang = v; + } + + t.languages[k] = v; + }); + }, + + createControl : function(n, cm) { + var t = this, c, ed = t.editor; + + if (n == 'spellchecker') { + // Use basic button if we use the native spellchecker + if (t.rpcUrl == '{backend}') { + // Create simple toggle button if we have native support + if (t.hasSupport) + c = cm.createButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + return c; + } + + c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t}); + + c.onRenderMenu.add(function(c, m) { + m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + each(t.languages, function(v, k) { + var o = {icon : 1}, mi; + + o.onclick = function() { + if (v == t.selectedLang) { + return; + } + mi.setSelected(1); + t.selectedItem.setSelected(0); + t.selectedItem = mi; + t.selectedLang = v; + }; + + o.title = k; + mi = m.add(o); + mi.setSelected(v == t.selectedLang); + + if (v == t.selectedLang) + t.selectedItem = mi; + }) + }); + + return c; + } + }, + + // Internal functions + + _walk : function(n, f) { + var d = this.editor.getDoc(), w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(this, n); + } else + tinymce.walk(n, f, 'childNodes'); + }, + + _getSeparators : function() { + var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}����������������\u201d\u201c'); + + // Build word separator regexp + for (i=0; i elements content is broken after spellchecking. + // Bug #1408: Preceding whitespace characters are removed + // @TODO: I'm not sure that both are still issues on IE9. + if (tinymce.isIE) { + // Enclose mispelled words with temporal tag + v = v.replace(rx, '$1$2'); + // Loop over the content finding mispelled words + while ((pos = v.indexOf('')) != -1) { + // Add text node for the content before the word + txt = v.substring(0, pos); + if (txt.length) { + node = doc.createTextNode(dom.decode(txt)); + elem.appendChild(node); + } + v = v.substring(pos+10); + pos = v.indexOf(''); + txt = v.substring(0, pos); + v = v.substring(pos+11); + // Add span element for the word + elem.appendChild(dom.create('span', {'class' : 'mceItemHiddenSpellWord'}, txt)); + } + // Add text node for the rest of the content + if (v.length) { + node = doc.createTextNode(dom.decode(v)); + elem.appendChild(node); + } + } else { + // Other browsers preserve whitespace characters on innerHTML usage + elem.innerHTML = v.replace(rx, '$1$2'); + } + + // Finally, replace the node with the container + dom.replace(elem, n); + } + }); + + se.moveToBookmark(b); + }, + + _showMenu : function(ed, e) { + var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin()), wordSpan = e.target; + + e = 0; // Fixes IE memory leak + + if (!m) { + m = ed.controlManager.createDropMenu('spellcheckermenu', {'class' : 'mceNoIcons'}); + t._menu = m; + } + + if (dom.hasClass(wordSpan, 'mceItemHiddenSpellWord')) { + m.removeAll(); + m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(wordSpan.innerHTML)], function(r) { + var ignoreRpc; + + m.removeAll(); + + if (r.length > 0) { + m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + each(r, function(v) { + m.add({title : v, onclick : function() { + dom.replace(ed.getDoc().createTextNode(v), wordSpan); + t._checkDone(); + }}); + }); + + m.addSeparator(); + } else + m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1); + + if (ed.getParam('show_ignore_words', true)) { + ignoreRpc = t.editor.getParam("spellchecker_enable_ignore_rpc", ''); + m.add({ + title : 'spellchecker.ignore_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + + m.add({ + title : 'spellchecker.ignore_words', + onclick : function() { + var word = wordSpan.innerHTML; + + t._removeWords(dom.decode(word)); + t._checkDone(); + + // tell the server if we need to + if (ignoreRpc) { + ed.setProgressState(1); + t._sendRPC('ignoreWords', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + } + }); + } + + if (t.editor.getParam("spellchecker_enable_learn_rpc")) { + m.add({ + title : 'spellchecker.learn_word', + onclick : function() { + var word = wordSpan.innerHTML; + + dom.remove(wordSpan, 1); + t._checkDone(); + + ed.setProgressState(1); + t._sendRPC('learnWord', [t.selectedLang, word], function(r) { + ed.setProgressState(0); + }); + } + }); + } + + m.update(); + }); + + p1 = DOM.getPos(ed.getContentAreaContainer()); + m.settings.offset_x = p1.x; + m.settings.offset_y = p1.y; + + ed.selection.select(wordSpan); + p1 = dom.getPos(wordSpan); + m.showMenu(p1.x, p1.y + wordSpan.offsetHeight - vp.y); + + return tinymce.dom.Event.cancel(e); + } else + m.hideMenu(); + }, + + _checkDone : function() { + var t = this, ed = t.editor, dom = ed.dom, o; + + each(dom.select('span'), function(n) { + if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) { + o = true; + return false; + } + }); + + if (!o) + t._done(); + }, + + _done : function() { + var t = this, la = t.active; + + if (t.active) { + t.active = 0; + t._removeWords(); + + if (t._menu) + t._menu.hideMenu(); + + if (la) + t.editor.nodeChanged(); + } + }, + + _sendRPC : function(m, p, cb) { + var t = this; + + JSONRequest.sendRPC({ + url : t.rpcUrl, + method : m, + params : p, + success : cb, + error : function(e, x) { + t.editor.setProgressState(0); + t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText)); + } + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin); +})(); diff --git a/js/tiny_mce/plugins/style/js/props.js b/js/tiny_mce/plugins/style/js/props.js index a8dd93de..99983163 100644 --- a/js/tiny_mce/plugins/style/js/props.js +++ b/js/tiny_mce/plugins/style/js/props.js @@ -1,641 +1,635 @@ -tinyMCEPopup.requireLangPack(); - -var defaultFonts = "" + - "Arial, Helvetica, sans-serif=Arial, Helvetica, sans-serif;" + - "Times New Roman, Times, serif=Times New Roman, Times, serif;" + - "Courier New, Courier, mono=Courier New, Courier, mono;" + - "Times New Roman, Times, serif=Times New Roman, Times, serif;" + - "Georgia, Times New Roman, Times, serif=Georgia, Times New Roman, Times, serif;" + - "Verdana, Arial, Helvetica, sans-serif=Verdana, Arial, Helvetica, sans-serif;" + - "Geneva, Arial, Helvetica, sans-serif=Geneva, Arial, Helvetica, sans-serif"; - -var defaultSizes = "9;10;12;14;16;18;24;xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger"; -var defaultMeasurement = "+pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; -var defaultSpacingMeasurement = "pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;+ems=em;exs=ex;%"; -var defaultIndentMeasurement = "pixels=px;+points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; -var defaultWeight = "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900"; -var defaultTextStyle = "normal;italic;oblique"; -var defaultVariant = "normal;small-caps"; -var defaultLineHeight = "normal"; -var defaultAttachment = "fixed;scroll"; -var defaultRepeat = "no-repeat;repeat;repeat-x;repeat-y"; -var defaultPosH = "left;center;right"; -var defaultPosV = "top;center;bottom"; -var defaultVAlign = "baseline;sub;super;top;text-top;middle;bottom;text-bottom"; -var defaultDisplay = "inline;block;list-item;run-in;compact;marker;table;inline-table;table-row-group;table-header-group;table-footer-group;table-row;table-column-group;table-column;table-cell;table-caption;none"; -var defaultBorderStyle = "none;solid;dashed;dotted;double;groove;ridge;inset;outset"; -var defaultBorderWidth = "thin;medium;thick"; -var defaultListType = "disc;circle;square;decimal;lower-roman;upper-roman;lower-alpha;upper-alpha;none"; - -function init() { - var ce = document.getElementById('container'), h; - - ce.style.cssText = tinyMCEPopup.getWindowArg('style_text'); - - h = getBrowserHTML('background_image_browser','background_image','image','advimage'); - document.getElementById("background_image_browser").innerHTML = h; - - document.getElementById('text_color_pickcontainer').innerHTML = getColorPickerHTML('text_color_pick','text_color'); - document.getElementById('background_color_pickcontainer').innerHTML = getColorPickerHTML('background_color_pick','background_color'); - document.getElementById('border_color_top_pickcontainer').innerHTML = getColorPickerHTML('border_color_top_pick','border_color_top'); - document.getElementById('border_color_right_pickcontainer').innerHTML = getColorPickerHTML('border_color_right_pick','border_color_right'); - document.getElementById('border_color_bottom_pickcontainer').innerHTML = getColorPickerHTML('border_color_bottom_pick','border_color_bottom'); - document.getElementById('border_color_left_pickcontainer').innerHTML = getColorPickerHTML('border_color_left_pick','border_color_left'); - - fillSelect(0, 'text_font', 'style_font', defaultFonts, ';', true); - fillSelect(0, 'text_size', 'style_font_size', defaultSizes, ';', true); - fillSelect(0, 'text_size_measurement', 'style_font_size_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'text_case', 'style_text_case', "capitalize;uppercase;lowercase", ';', true); - fillSelect(0, 'text_weight', 'style_font_weight', defaultWeight, ';', true); - fillSelect(0, 'text_style', 'style_font_style', defaultTextStyle, ';', true); - fillSelect(0, 'text_variant', 'style_font_variant', defaultVariant, ';', true); - fillSelect(0, 'text_lineheight', 'style_font_line_height', defaultLineHeight, ';', true); - fillSelect(0, 'text_lineheight_measurement', 'style_font_line_height_measurement', defaultMeasurement, ';', true); - - fillSelect(0, 'background_attachment', 'style_background_attachment', defaultAttachment, ';', true); - fillSelect(0, 'background_repeat', 'style_background_repeat', defaultRepeat, ';', true); - - fillSelect(0, 'background_hpos_measurement', 'style_background_hpos_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'background_vpos_measurement', 'style_background_vpos_measurement', defaultMeasurement, ';', true); - - fillSelect(0, 'background_hpos', 'style_background_hpos', defaultPosH, ';', true); - fillSelect(0, 'background_vpos', 'style_background_vpos', defaultPosV, ';', true); - - fillSelect(0, 'block_wordspacing', 'style_wordspacing', 'normal', ';', true); - fillSelect(0, 'block_wordspacing_measurement', 'style_wordspacing_measurement', defaultSpacingMeasurement, ';', true); - fillSelect(0, 'block_letterspacing', 'style_letterspacing', 'normal', ';', true); - fillSelect(0, 'block_letterspacing_measurement', 'style_letterspacing_measurement', defaultSpacingMeasurement, ';', true); - fillSelect(0, 'block_vertical_alignment', 'style_vertical_alignment', defaultVAlign, ';', true); - fillSelect(0, 'block_text_align', 'style_text_align', "left;right;center;justify", ';', true); - fillSelect(0, 'block_whitespace', 'style_whitespace', "normal;pre;nowrap", ';', true); - fillSelect(0, 'block_display', 'style_display', defaultDisplay, ';', true); - fillSelect(0, 'block_text_indent_measurement', 'style_text_indent_measurement', defaultIndentMeasurement, ';', true); - - fillSelect(0, 'box_width_measurement', 'style_box_width_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_height_measurement', 'style_box_height_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_float', 'style_float', 'left;right;none', ';', true); - fillSelect(0, 'box_clear', 'style_clear', 'left;right;both;none', ';', true); - fillSelect(0, 'box_padding_left_measurement', 'style_padding_left_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_padding_top_measurement', 'style_padding_top_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_padding_bottom_measurement', 'style_padding_bottom_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_padding_right_measurement', 'style_padding_right_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_margin_left_measurement', 'style_margin_left_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_margin_top_measurement', 'style_margin_top_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_margin_bottom_measurement', 'style_margin_bottom_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'box_margin_right_measurement', 'style_margin_right_measurement', defaultMeasurement, ';', true); - - fillSelect(0, 'border_style_top', 'style_border_style_top', defaultBorderStyle, ';', true); - fillSelect(0, 'border_style_right', 'style_border_style_right', defaultBorderStyle, ';', true); - fillSelect(0, 'border_style_bottom', 'style_border_style_bottom', defaultBorderStyle, ';', true); - fillSelect(0, 'border_style_left', 'style_border_style_left', defaultBorderStyle, ';', true); - - fillSelect(0, 'border_width_top', 'style_border_width_top', defaultBorderWidth, ';', true); - fillSelect(0, 'border_width_right', 'style_border_width_right', defaultBorderWidth, ';', true); - fillSelect(0, 'border_width_bottom', 'style_border_width_bottom', defaultBorderWidth, ';', true); - fillSelect(0, 'border_width_left', 'style_border_width_left', defaultBorderWidth, ';', true); - - fillSelect(0, 'border_width_top_measurement', 'style_border_width_top_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'border_width_right_measurement', 'style_border_width_right_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'border_width_bottom_measurement', 'style_border_width_bottom_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'border_width_left_measurement', 'style_border_width_left_measurement', defaultMeasurement, ';', true); - - fillSelect(0, 'list_type', 'style_list_type', defaultListType, ';', true); - fillSelect(0, 'list_position', 'style_list_position', "inside;outside", ';', true); - - fillSelect(0, 'positioning_type', 'style_positioning_type', "absolute;relative;static", ';', true); - fillSelect(0, 'positioning_visibility', 'style_positioning_visibility', "inherit;visible;hidden", ';', true); - - fillSelect(0, 'positioning_width_measurement', 'style_positioning_width_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_height_measurement', 'style_positioning_height_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_overflow', 'style_positioning_overflow', "visible;hidden;scroll;auto", ';', true); - - fillSelect(0, 'positioning_placement_top_measurement', 'style_positioning_placement_top_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_placement_right_measurement', 'style_positioning_placement_right_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_placement_bottom_measurement', 'style_positioning_placement_bottom_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_placement_left_measurement', 'style_positioning_placement_left_measurement', defaultMeasurement, ';', true); - - fillSelect(0, 'positioning_clip_top_measurement', 'style_positioning_clip_top_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_clip_right_measurement', 'style_positioning_clip_right_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_clip_bottom_measurement', 'style_positioning_clip_bottom_measurement', defaultMeasurement, ';', true); - fillSelect(0, 'positioning_clip_left_measurement', 'style_positioning_clip_left_measurement', defaultMeasurement, ';', true); - - TinyMCE_EditableSelects.init(); - setupFormData(); - showDisabledControls(); -} - -function setupFormData() { - var ce = document.getElementById('container'), f = document.forms[0], s, b, i; - - // Setup text fields - - selectByValue(f, 'text_font', ce.style.fontFamily, true, true); - selectByValue(f, 'text_size', getNum(ce.style.fontSize), true, true); - selectByValue(f, 'text_size_measurement', getMeasurement(ce.style.fontSize)); - selectByValue(f, 'text_weight', ce.style.fontWeight, true, true); - selectByValue(f, 'text_style', ce.style.fontStyle, true, true); - selectByValue(f, 'text_lineheight', getNum(ce.style.lineHeight), true, true); - selectByValue(f, 'text_lineheight_measurement', getMeasurement(ce.style.lineHeight)); - selectByValue(f, 'text_case', ce.style.textTransform, true, true); - selectByValue(f, 'text_variant', ce.style.fontVariant, true, true); - f.text_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.color); - updateColor('text_color_pick', 'text_color'); - f.text_underline.checked = inStr(ce.style.textDecoration, 'underline'); - f.text_overline.checked = inStr(ce.style.textDecoration, 'overline'); - f.text_linethrough.checked = inStr(ce.style.textDecoration, 'line-through'); - f.text_blink.checked = inStr(ce.style.textDecoration, 'blink'); - - // Setup background fields - - f.background_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.backgroundColor); - updateColor('background_color_pick', 'background_color'); - f.background_image.value = ce.style.backgroundImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); - selectByValue(f, 'background_repeat', ce.style.backgroundRepeat, true, true); - selectByValue(f, 'background_attachment', ce.style.backgroundAttachment, true, true); - selectByValue(f, 'background_hpos', getNum(getVal(ce.style.backgroundPosition, 0)), true, true); - selectByValue(f, 'background_hpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 0))); - selectByValue(f, 'background_vpos', getNum(getVal(ce.style.backgroundPosition, 1)), true, true); - selectByValue(f, 'background_vpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 1))); - - // Setup block fields - - selectByValue(f, 'block_wordspacing', getNum(ce.style.wordSpacing), true, true); - selectByValue(f, 'block_wordspacing_measurement', getMeasurement(ce.style.wordSpacing)); - selectByValue(f, 'block_letterspacing', getNum(ce.style.letterSpacing), true, true); - selectByValue(f, 'block_letterspacing_measurement', getMeasurement(ce.style.letterSpacing)); - selectByValue(f, 'block_vertical_alignment', ce.style.verticalAlign, true, true); - selectByValue(f, 'block_text_align', ce.style.textAlign, true, true); - f.block_text_indent.value = getNum(ce.style.textIndent); - selectByValue(f, 'block_text_indent_measurement', getMeasurement(ce.style.textIndent)); - selectByValue(f, 'block_whitespace', ce.style.whiteSpace, true, true); - selectByValue(f, 'block_display', ce.style.display, true, true); - - // Setup box fields - - f.box_width.value = getNum(ce.style.width); - selectByValue(f, 'box_width_measurement', getMeasurement(ce.style.width)); - - f.box_height.value = getNum(ce.style.height); - selectByValue(f, 'box_height_measurement', getMeasurement(ce.style.height)); - - if (tinymce.isGecko) - selectByValue(f, 'box_float', ce.style.cssFloat, true, true); - else - selectByValue(f, 'box_float', ce.style.styleFloat, true, true); - - selectByValue(f, 'box_clear', ce.style.clear, true, true); - - setupBox(f, ce, 'box_padding', 'padding', ''); - setupBox(f, ce, 'box_margin', 'margin', ''); - - // Setup border fields - - setupBox(f, ce, 'border_style', 'border', 'Style'); - setupBox(f, ce, 'border_width', 'border', 'Width'); - setupBox(f, ce, 'border_color', 'border', 'Color'); - - updateColor('border_color_top_pick', 'border_color_top'); - updateColor('border_color_right_pick', 'border_color_right'); - updateColor('border_color_bottom_pick', 'border_color_bottom'); - updateColor('border_color_left_pick', 'border_color_left'); - - f.elements.border_color_top.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_top.value); - f.elements.border_color_right.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_right.value); - f.elements.border_color_bottom.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_bottom.value); - f.elements.border_color_left.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_left.value); - - // Setup list fields - - selectByValue(f, 'list_type', ce.style.listStyleType, true, true); - selectByValue(f, 'list_position', ce.style.listStylePosition, true, true); - f.list_bullet_image.value = ce.style.listStyleImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); - - // Setup box fields - - selectByValue(f, 'positioning_type', ce.style.position, true, true); - selectByValue(f, 'positioning_visibility', ce.style.visibility, true, true); - selectByValue(f, 'positioning_overflow', ce.style.overflow, true, true); - f.positioning_zindex.value = ce.style.zIndex ? ce.style.zIndex : ""; - - f.positioning_width.value = getNum(ce.style.width); - selectByValue(f, 'positioning_width_measurement', getMeasurement(ce.style.width)); - - f.positioning_height.value = getNum(ce.style.height); - selectByValue(f, 'positioning_height_measurement', getMeasurement(ce.style.height)); - - setupBox(f, ce, 'positioning_placement', '', '', ['top', 'right', 'bottom', 'left']); - - s = ce.style.clip.replace(new RegExp("rect\\('?([^']*)'?\\)", 'gi'), "$1"); - s = s.replace(/,/g, ' '); - - if (!hasEqualValues([getVal(s, 0), getVal(s, 1), getVal(s, 2), getVal(s, 3)])) { - f.positioning_clip_top.value = getNum(getVal(s, 0)); - selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); - f.positioning_clip_right.value = getNum(getVal(s, 1)); - selectByValue(f, 'positioning_clip_right_measurement', getMeasurement(getVal(s, 1))); - f.positioning_clip_bottom.value = getNum(getVal(s, 2)); - selectByValue(f, 'positioning_clip_bottom_measurement', getMeasurement(getVal(s, 2))); - f.positioning_clip_left.value = getNum(getVal(s, 3)); - selectByValue(f, 'positioning_clip_left_measurement', getMeasurement(getVal(s, 3))); - } else { - f.positioning_clip_top.value = getNum(getVal(s, 0)); - selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); - f.positioning_clip_right.value = f.positioning_clip_bottom.value = f.positioning_clip_left.value; - } - -// setupBox(f, ce, '', 'border', 'Color'); -} - -function getMeasurement(s) { - return s.replace(/^([0-9.]+)(.*)$/, "$2"); -} - -function getNum(s) { - if (new RegExp('^(?:[0-9.]+)(?:[a-z%]+)$', 'gi').test(s)) - return s.replace(/[^0-9.]/g, ''); - - return s; -} - -function inStr(s, n) { - return new RegExp(n, 'gi').test(s); -} - -function getVal(s, i) { - var a = s.split(' '); - - if (a.length > 1) - return a[i]; - - return ""; -} - -function setValue(f, n, v) { - if (f.elements[n].type == "text") - f.elements[n].value = v; - else - selectByValue(f, n, v, true, true); -} - -function setupBox(f, ce, fp, pr, sf, b) { - if (typeof(b) == "undefined") - b = ['Top', 'Right', 'Bottom', 'Left']; - - if (isSame(ce, pr, sf, b)) { - f.elements[fp + "_same"].checked = true; - - setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); - f.elements[fp + "_top"].disabled = false; - - f.elements[fp + "_right"].value = ""; - f.elements[fp + "_right"].disabled = true; - f.elements[fp + "_bottom"].value = ""; - f.elements[fp + "_bottom"].disabled = true; - f.elements[fp + "_left"].value = ""; - f.elements[fp + "_left"].disabled = true; - - if (f.elements[fp + "_top_measurement"]) { - selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); - f.elements[fp + "_left_measurement"].disabled = true; - f.elements[fp + "_bottom_measurement"].disabled = true; - f.elements[fp + "_right_measurement"].disabled = true; - } - } else { - f.elements[fp + "_same"].checked = false; - - setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); - f.elements[fp + "_top"].disabled = false; - - setValue(f, fp + "_right", getNum(ce.style[pr + b[1] + sf])); - f.elements[fp + "_right"].disabled = false; - - setValue(f, fp + "_bottom", getNum(ce.style[pr + b[2] + sf])); - f.elements[fp + "_bottom"].disabled = false; - - setValue(f, fp + "_left", getNum(ce.style[pr + b[3] + sf])); - f.elements[fp + "_left"].disabled = false; - - if (f.elements[fp + "_top_measurement"]) { - selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); - selectByValue(f, fp + '_right_measurement', getMeasurement(ce.style[pr + b[1] + sf])); - selectByValue(f, fp + '_bottom_measurement', getMeasurement(ce.style[pr + b[2] + sf])); - selectByValue(f, fp + '_left_measurement', getMeasurement(ce.style[pr + b[3] + sf])); - f.elements[fp + "_left_measurement"].disabled = false; - f.elements[fp + "_bottom_measurement"].disabled = false; - f.elements[fp + "_right_measurement"].disabled = false; - } - } -} - -function isSame(e, pr, sf, b) { - var a = [], i, x; - - if (typeof(b) == "undefined") - b = ['Top', 'Right', 'Bottom', 'Left']; - - if (typeof(sf) == "undefined" || sf == null) - sf = ""; - - a[0] = e.style[pr + b[0] + sf]; - a[1] = e.style[pr + b[1] + sf]; - a[2] = e.style[pr + b[2] + sf]; - a[3] = e.style[pr + b[3] + sf]; - - for (i=0; i 0 ? s.substring(1) : s; - - if (f.text_none.checked) - s = "none"; - - ce.style.textDecoration = s; - - // Build background styles - - ce.style.backgroundColor = f.background_color.value; - ce.style.backgroundImage = f.background_image.value != "" ? "url(" + f.background_image.value + ")" : ""; - ce.style.backgroundRepeat = f.background_repeat.value; - ce.style.backgroundAttachment = f.background_attachment.value; - - if (f.background_hpos.value != "") { - s = ""; - s += f.background_hpos.value + (isNum(f.background_hpos.value) ? f.background_hpos_measurement.value : "") + " "; - s += f.background_vpos.value + (isNum(f.background_vpos.value) ? f.background_vpos_measurement.value : ""); - ce.style.backgroundPosition = s; - } - - // Build block styles - - ce.style.wordSpacing = f.block_wordspacing.value + (isNum(f.block_wordspacing.value) ? f.block_wordspacing_measurement.value : ""); - ce.style.letterSpacing = f.block_letterspacing.value + (isNum(f.block_letterspacing.value) ? f.block_letterspacing_measurement.value : ""); - ce.style.verticalAlign = f.block_vertical_alignment.value; - ce.style.textAlign = f.block_text_align.value; - ce.style.textIndent = f.block_text_indent.value + (isNum(f.block_text_indent.value) ? f.block_text_indent_measurement.value : ""); - ce.style.whiteSpace = f.block_whitespace.value; - ce.style.display = f.block_display.value; - - // Build box styles - - ce.style.width = f.box_width.value + (isNum(f.box_width.value) ? f.box_width_measurement.value : ""); - ce.style.height = f.box_height.value + (isNum(f.box_height.value) ? f.box_height_measurement.value : ""); - ce.style.styleFloat = f.box_float.value; - - if (tinymce.isGecko) - ce.style.cssFloat = f.box_float.value; - - ce.style.clear = f.box_clear.value; - - if (!f.box_padding_same.checked) { - ce.style.paddingTop = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); - ce.style.paddingRight = f.box_padding_right.value + (isNum(f.box_padding_right.value) ? f.box_padding_right_measurement.value : ""); - ce.style.paddingBottom = f.box_padding_bottom.value + (isNum(f.box_padding_bottom.value) ? f.box_padding_bottom_measurement.value : ""); - ce.style.paddingLeft = f.box_padding_left.value + (isNum(f.box_padding_left.value) ? f.box_padding_left_measurement.value : ""); - } else - ce.style.padding = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); - - if (!f.box_margin_same.checked) { - ce.style.marginTop = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); - ce.style.marginRight = f.box_margin_right.value + (isNum(f.box_margin_right.value) ? f.box_margin_right_measurement.value : ""); - ce.style.marginBottom = f.box_margin_bottom.value + (isNum(f.box_margin_bottom.value) ? f.box_margin_bottom_measurement.value : ""); - ce.style.marginLeft = f.box_margin_left.value + (isNum(f.box_margin_left.value) ? f.box_margin_left_measurement.value : ""); - } else - ce.style.margin = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); - - // Build border styles - - if (!f.border_style_same.checked) { - ce.style.borderTopStyle = f.border_style_top.value; - ce.style.borderRightStyle = f.border_style_right.value; - ce.style.borderBottomStyle = f.border_style_bottom.value; - ce.style.borderLeftStyle = f.border_style_left.value; - } else - ce.style.borderStyle = f.border_style_top.value; - - if (!f.border_width_same.checked) { - ce.style.borderTopWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); - ce.style.borderRightWidth = f.border_width_right.value + (isNum(f.border_width_right.value) ? f.border_width_right_measurement.value : ""); - ce.style.borderBottomWidth = f.border_width_bottom.value + (isNum(f.border_width_bottom.value) ? f.border_width_bottom_measurement.value : ""); - ce.style.borderLeftWidth = f.border_width_left.value + (isNum(f.border_width_left.value) ? f.border_width_left_measurement.value : ""); - } else - ce.style.borderWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); - - if (!f.border_color_same.checked) { - ce.style.borderTopColor = f.border_color_top.value; - ce.style.borderRightColor = f.border_color_right.value; - ce.style.borderBottomColor = f.border_color_bottom.value; - ce.style.borderLeftColor = f.border_color_left.value; - } else - ce.style.borderColor = f.border_color_top.value; - - // Build list styles - - ce.style.listStyleType = f.list_type.value; - ce.style.listStylePosition = f.list_position.value; - ce.style.listStyleImage = f.list_bullet_image.value != "" ? "url(" + f.list_bullet_image.value + ")" : ""; - - // Build positioning styles - - ce.style.position = f.positioning_type.value; - ce.style.visibility = f.positioning_visibility.value; - - if (ce.style.width == "") - ce.style.width = f.positioning_width.value + (isNum(f.positioning_width.value) ? f.positioning_width_measurement.value : ""); - - if (ce.style.height == "") - ce.style.height = f.positioning_height.value + (isNum(f.positioning_height.value) ? f.positioning_height_measurement.value : ""); - - ce.style.zIndex = f.positioning_zindex.value; - ce.style.overflow = f.positioning_overflow.value; - - if (!f.positioning_placement_same.checked) { - ce.style.top = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); - ce.style.right = f.positioning_placement_right.value + (isNum(f.positioning_placement_right.value) ? f.positioning_placement_right_measurement.value : ""); - ce.style.bottom = f.positioning_placement_bottom.value + (isNum(f.positioning_placement_bottom.value) ? f.positioning_placement_bottom_measurement.value : ""); - ce.style.left = f.positioning_placement_left.value + (isNum(f.positioning_placement_left.value) ? f.positioning_placement_left_measurement.value : ""); - } else { - s = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); - ce.style.top = s; - ce.style.right = s; - ce.style.bottom = s; - ce.style.left = s; - } - - if (!f.positioning_clip_same.checked) { - s = "rect("; - s += (isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto") + " "; - s += (isNum(f.positioning_clip_right.value) ? f.positioning_clip_right.value + f.positioning_clip_right_measurement.value : "auto") + " "; - s += (isNum(f.positioning_clip_bottom.value) ? f.positioning_clip_bottom.value + f.positioning_clip_bottom_measurement.value : "auto") + " "; - s += (isNum(f.positioning_clip_left.value) ? f.positioning_clip_left.value + f.positioning_clip_left_measurement.value : "auto"); - s += ")"; - - if (s != "rect(auto auto auto auto)") - ce.style.clip = s; - } else { - s = "rect("; - t = isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto"; - s += t + " "; - s += t + " "; - s += t + " "; - s += t + ")"; - - if (s != "rect(auto auto auto auto)") - ce.style.clip = s; - } - - ce.style.cssText = ce.style.cssText; -} - -function isNum(s) { - return new RegExp('[0-9]+', 'g').test(s); -} - -function showDisabledControls() { - var f = document.forms, i, a; - - for (i=0; i 1) { - addSelectValue(f, s, p[0], p[1]); - - if (se) - selectByValue(f, s, p[1]); - } else { - addSelectValue(f, s, p[0], p[0]); - - if (se) - selectByValue(f, s, p[0]); - } - } -} - -function toggleSame(ce, pre) { - var el = document.forms[0].elements, i; - - if (ce.checked) { - el[pre + "_top"].disabled = false; - el[pre + "_right"].disabled = true; - el[pre + "_bottom"].disabled = true; - el[pre + "_left"].disabled = true; - - if (el[pre + "_top_measurement"]) { - el[pre + "_top_measurement"].disabled = false; - el[pre + "_right_measurement"].disabled = true; - el[pre + "_bottom_measurement"].disabled = true; - el[pre + "_left_measurement"].disabled = true; - } - } else { - el[pre + "_top"].disabled = false; - el[pre + "_right"].disabled = false; - el[pre + "_bottom"].disabled = false; - el[pre + "_left"].disabled = false; - - if (el[pre + "_top_measurement"]) { - el[pre + "_top_measurement"].disabled = false; - el[pre + "_right_measurement"].disabled = false; - el[pre + "_bottom_measurement"].disabled = false; - el[pre + "_left_measurement"].disabled = false; - } - } - - showDisabledControls(); -} - -function synch(fr, to) { - var f = document.forms[0]; - - f.elements[to].value = f.elements[fr].value; - - if (f.elements[fr + "_measurement"]) - selectByValue(f, to + "_measurement", f.elements[fr + "_measurement"].value); -} - -tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); + +var defaultFonts = "" + + "Arial, Helvetica, sans-serif=Arial, Helvetica, sans-serif;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Courier New, Courier, mono=Courier New, Courier, mono;" + + "Times New Roman, Times, serif=Times New Roman, Times, serif;" + + "Georgia, Times New Roman, Times, serif=Georgia, Times New Roman, Times, serif;" + + "Verdana, Arial, Helvetica, sans-serif=Verdana, Arial, Helvetica, sans-serif;" + + "Geneva, Arial, Helvetica, sans-serif=Geneva, Arial, Helvetica, sans-serif"; + +var defaultSizes = "9;10;12;14;16;18;24;xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger"; +var defaultMeasurement = "+pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultSpacingMeasurement = "pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;+ems=em;exs=ex;%"; +var defaultIndentMeasurement = "pixels=px;+points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%"; +var defaultWeight = "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900"; +var defaultTextStyle = "normal;italic;oblique"; +var defaultVariant = "normal;small-caps"; +var defaultLineHeight = "normal"; +var defaultAttachment = "fixed;scroll"; +var defaultRepeat = "no-repeat;repeat;repeat-x;repeat-y"; +var defaultPosH = "left;center;right"; +var defaultPosV = "top;center;bottom"; +var defaultVAlign = "baseline;sub;super;top;text-top;middle;bottom;text-bottom"; +var defaultDisplay = "inline;block;list-item;run-in;compact;marker;table;inline-table;table-row-group;table-header-group;table-footer-group;table-row;table-column-group;table-column;table-cell;table-caption;none"; +var defaultBorderStyle = "none;solid;dashed;dotted;double;groove;ridge;inset;outset"; +var defaultBorderWidth = "thin;medium;thick"; +var defaultListType = "disc;circle;square;decimal;lower-roman;upper-roman;lower-alpha;upper-alpha;none"; + +function init() { + var ce = document.getElementById('container'), h; + + ce.style.cssText = tinyMCEPopup.getWindowArg('style_text'); + + h = getBrowserHTML('background_image_browser','background_image','image','advimage'); + document.getElementById("background_image_browser").innerHTML = h; + + document.getElementById('text_color_pickcontainer').innerHTML = getColorPickerHTML('text_color_pick','text_color'); + document.getElementById('background_color_pickcontainer').innerHTML = getColorPickerHTML('background_color_pick','background_color'); + document.getElementById('border_color_top_pickcontainer').innerHTML = getColorPickerHTML('border_color_top_pick','border_color_top'); + document.getElementById('border_color_right_pickcontainer').innerHTML = getColorPickerHTML('border_color_right_pick','border_color_right'); + document.getElementById('border_color_bottom_pickcontainer').innerHTML = getColorPickerHTML('border_color_bottom_pick','border_color_bottom'); + document.getElementById('border_color_left_pickcontainer').innerHTML = getColorPickerHTML('border_color_left_pick','border_color_left'); + + fillSelect(0, 'text_font', 'style_font', defaultFonts, ';', true); + fillSelect(0, 'text_size', 'style_font_size', defaultSizes, ';', true); + fillSelect(0, 'text_size_measurement', 'style_font_size_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'text_case', 'style_text_case', "capitalize;uppercase;lowercase", ';', true); + fillSelect(0, 'text_weight', 'style_font_weight', defaultWeight, ';', true); + fillSelect(0, 'text_style', 'style_font_style', defaultTextStyle, ';', true); + fillSelect(0, 'text_variant', 'style_font_variant', defaultVariant, ';', true); + fillSelect(0, 'text_lineheight', 'style_font_line_height', defaultLineHeight, ';', true); + fillSelect(0, 'text_lineheight_measurement', 'style_font_line_height_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_attachment', 'style_background_attachment', defaultAttachment, ';', true); + fillSelect(0, 'background_repeat', 'style_background_repeat', defaultRepeat, ';', true); + + fillSelect(0, 'background_hpos_measurement', 'style_background_hpos_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'background_vpos_measurement', 'style_background_vpos_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'background_hpos', 'style_background_hpos', defaultPosH, ';', true); + fillSelect(0, 'background_vpos', 'style_background_vpos', defaultPosV, ';', true); + + fillSelect(0, 'block_wordspacing', 'style_wordspacing', 'normal', ';', true); + fillSelect(0, 'block_wordspacing_measurement', 'style_wordspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_letterspacing', 'style_letterspacing', 'normal', ';', true); + fillSelect(0, 'block_letterspacing_measurement', 'style_letterspacing_measurement', defaultSpacingMeasurement, ';', true); + fillSelect(0, 'block_vertical_alignment', 'style_vertical_alignment', defaultVAlign, ';', true); + fillSelect(0, 'block_text_align', 'style_text_align', "left;right;center;justify", ';', true); + fillSelect(0, 'block_whitespace', 'style_whitespace', "normal;pre;nowrap", ';', true); + fillSelect(0, 'block_display', 'style_display', defaultDisplay, ';', true); + fillSelect(0, 'block_text_indent_measurement', 'style_text_indent_measurement', defaultIndentMeasurement, ';', true); + + fillSelect(0, 'box_width_measurement', 'style_box_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_height_measurement', 'style_box_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_float', 'style_float', 'left;right;none', ';', true); + fillSelect(0, 'box_clear', 'style_clear', 'left;right;both;none', ';', true); + fillSelect(0, 'box_padding_left_measurement', 'style_padding_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_top_measurement', 'style_padding_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_bottom_measurement', 'style_padding_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_padding_right_measurement', 'style_padding_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_left_measurement', 'style_margin_left_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_top_measurement', 'style_margin_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_bottom_measurement', 'style_margin_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'box_margin_right_measurement', 'style_margin_right_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'border_style_top', 'style_border_style_top', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_right', 'style_border_style_right', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_bottom', 'style_border_style_bottom', defaultBorderStyle, ';', true); + fillSelect(0, 'border_style_left', 'style_border_style_left', defaultBorderStyle, ';', true); + + fillSelect(0, 'border_width_top', 'style_border_width_top', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_right', 'style_border_width_right', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_bottom', 'style_border_width_bottom', defaultBorderWidth, ';', true); + fillSelect(0, 'border_width_left', 'style_border_width_left', defaultBorderWidth, ';', true); + + fillSelect(0, 'border_width_top_measurement', 'style_border_width_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_right_measurement', 'style_border_width_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_bottom_measurement', 'style_border_width_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'border_width_left_measurement', 'style_border_width_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'list_type', 'style_list_type', defaultListType, ';', true); + fillSelect(0, 'list_position', 'style_list_position', "inside;outside", ';', true); + + fillSelect(0, 'positioning_type', 'style_positioning_type', "absolute;relative;static", ';', true); + fillSelect(0, 'positioning_visibility', 'style_positioning_visibility', "inherit;visible;hidden", ';', true); + + fillSelect(0, 'positioning_width_measurement', 'style_positioning_width_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_height_measurement', 'style_positioning_height_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_overflow', 'style_positioning_overflow', "visible;hidden;scroll;auto", ';', true); + + fillSelect(0, 'positioning_placement_top_measurement', 'style_positioning_placement_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_right_measurement', 'style_positioning_placement_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_bottom_measurement', 'style_positioning_placement_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_placement_left_measurement', 'style_positioning_placement_left_measurement', defaultMeasurement, ';', true); + + fillSelect(0, 'positioning_clip_top_measurement', 'style_positioning_clip_top_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_right_measurement', 'style_positioning_clip_right_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_bottom_measurement', 'style_positioning_clip_bottom_measurement', defaultMeasurement, ';', true); + fillSelect(0, 'positioning_clip_left_measurement', 'style_positioning_clip_left_measurement', defaultMeasurement, ';', true); + + TinyMCE_EditableSelects.init(); + setupFormData(); + showDisabledControls(); +} + +function setupFormData() { + var ce = document.getElementById('container'), f = document.forms[0], s, b, i; + + // Setup text fields + + selectByValue(f, 'text_font', ce.style.fontFamily, true, true); + selectByValue(f, 'text_size', getNum(ce.style.fontSize), true, true); + selectByValue(f, 'text_size_measurement', getMeasurement(ce.style.fontSize)); + selectByValue(f, 'text_weight', ce.style.fontWeight, true, true); + selectByValue(f, 'text_style', ce.style.fontStyle, true, true); + selectByValue(f, 'text_lineheight', getNum(ce.style.lineHeight), true, true); + selectByValue(f, 'text_lineheight_measurement', getMeasurement(ce.style.lineHeight)); + selectByValue(f, 'text_case', ce.style.textTransform, true, true); + selectByValue(f, 'text_variant', ce.style.fontVariant, true, true); + f.text_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.color); + updateColor('text_color_pick', 'text_color'); + f.text_underline.checked = inStr(ce.style.textDecoration, 'underline'); + f.text_overline.checked = inStr(ce.style.textDecoration, 'overline'); + f.text_linethrough.checked = inStr(ce.style.textDecoration, 'line-through'); + f.text_blink.checked = inStr(ce.style.textDecoration, 'blink'); + + // Setup background fields + + f.background_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.backgroundColor); + updateColor('background_color_pick', 'background_color'); + f.background_image.value = ce.style.backgroundImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + selectByValue(f, 'background_repeat', ce.style.backgroundRepeat, true, true); + selectByValue(f, 'background_attachment', ce.style.backgroundAttachment, true, true); + selectByValue(f, 'background_hpos', getNum(getVal(ce.style.backgroundPosition, 0)), true, true); + selectByValue(f, 'background_hpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 0))); + selectByValue(f, 'background_vpos', getNum(getVal(ce.style.backgroundPosition, 1)), true, true); + selectByValue(f, 'background_vpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 1))); + + // Setup block fields + + selectByValue(f, 'block_wordspacing', getNum(ce.style.wordSpacing), true, true); + selectByValue(f, 'block_wordspacing_measurement', getMeasurement(ce.style.wordSpacing)); + selectByValue(f, 'block_letterspacing', getNum(ce.style.letterSpacing), true, true); + selectByValue(f, 'block_letterspacing_measurement', getMeasurement(ce.style.letterSpacing)); + selectByValue(f, 'block_vertical_alignment', ce.style.verticalAlign, true, true); + selectByValue(f, 'block_text_align', ce.style.textAlign, true, true); + f.block_text_indent.value = getNum(ce.style.textIndent); + selectByValue(f, 'block_text_indent_measurement', getMeasurement(ce.style.textIndent)); + selectByValue(f, 'block_whitespace', ce.style.whiteSpace, true, true); + selectByValue(f, 'block_display', ce.style.display, true, true); + + // Setup box fields + + f.box_width.value = getNum(ce.style.width); + selectByValue(f, 'box_width_measurement', getMeasurement(ce.style.width)); + + f.box_height.value = getNum(ce.style.height); + selectByValue(f, 'box_height_measurement', getMeasurement(ce.style.height)); + selectByValue(f, 'box_float', ce.style.cssFloat || ce.style.styleFloat, true, true); + + selectByValue(f, 'box_clear', ce.style.clear, true, true); + + setupBox(f, ce, 'box_padding', 'padding', ''); + setupBox(f, ce, 'box_margin', 'margin', ''); + + // Setup border fields + + setupBox(f, ce, 'border_style', 'border', 'Style'); + setupBox(f, ce, 'border_width', 'border', 'Width'); + setupBox(f, ce, 'border_color', 'border', 'Color'); + + updateColor('border_color_top_pick', 'border_color_top'); + updateColor('border_color_right_pick', 'border_color_right'); + updateColor('border_color_bottom_pick', 'border_color_bottom'); + updateColor('border_color_left_pick', 'border_color_left'); + + f.elements.border_color_top.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_top.value); + f.elements.border_color_right.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_right.value); + f.elements.border_color_bottom.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_bottom.value); + f.elements.border_color_left.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_left.value); + + // Setup list fields + + selectByValue(f, 'list_type', ce.style.listStyleType, true, true); + selectByValue(f, 'list_position', ce.style.listStylePosition, true, true); + f.list_bullet_image.value = ce.style.listStyleImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); + + // Setup box fields + + selectByValue(f, 'positioning_type', ce.style.position, true, true); + selectByValue(f, 'positioning_visibility', ce.style.visibility, true, true); + selectByValue(f, 'positioning_overflow', ce.style.overflow, true, true); + f.positioning_zindex.value = ce.style.zIndex ? ce.style.zIndex : ""; + + f.positioning_width.value = getNum(ce.style.width); + selectByValue(f, 'positioning_width_measurement', getMeasurement(ce.style.width)); + + f.positioning_height.value = getNum(ce.style.height); + selectByValue(f, 'positioning_height_measurement', getMeasurement(ce.style.height)); + + setupBox(f, ce, 'positioning_placement', '', '', ['top', 'right', 'bottom', 'left']); + + s = ce.style.clip.replace(new RegExp("rect\\('?([^']*)'?\\)", 'gi'), "$1"); + s = s.replace(/,/g, ' '); + + if (!hasEqualValues([getVal(s, 0), getVal(s, 1), getVal(s, 2), getVal(s, 3)])) { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = getNum(getVal(s, 1)); + selectByValue(f, 'positioning_clip_right_measurement', getMeasurement(getVal(s, 1))); + f.positioning_clip_bottom.value = getNum(getVal(s, 2)); + selectByValue(f, 'positioning_clip_bottom_measurement', getMeasurement(getVal(s, 2))); + f.positioning_clip_left.value = getNum(getVal(s, 3)); + selectByValue(f, 'positioning_clip_left_measurement', getMeasurement(getVal(s, 3))); + } else { + f.positioning_clip_top.value = getNum(getVal(s, 0)); + selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0))); + f.positioning_clip_right.value = f.positioning_clip_bottom.value = f.positioning_clip_left.value; + } + +// setupBox(f, ce, '', 'border', 'Color'); +} + +function getMeasurement(s) { + return s.replace(/^([0-9.]+)(.*)$/, "$2"); +} + +function getNum(s) { + if (new RegExp('^(?:[0-9.]+)(?:[a-z%]+)$', 'gi').test(s)) + return s.replace(/[^0-9.]/g, ''); + + return s; +} + +function inStr(s, n) { + return new RegExp(n, 'gi').test(s); +} + +function getVal(s, i) { + var a = s.split(' '); + + if (a.length > 1) + return a[i]; + + return ""; +} + +function setValue(f, n, v) { + if (f.elements[n].type == "text") + f.elements[n].value = v; + else + selectByValue(f, n, v, true, true); +} + +function setupBox(f, ce, fp, pr, sf, b) { + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (isSame(ce, pr, sf, b)) { + f.elements[fp + "_same"].checked = true; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + f.elements[fp + "_right"].value = ""; + f.elements[fp + "_right"].disabled = true; + f.elements[fp + "_bottom"].value = ""; + f.elements[fp + "_bottom"].disabled = true; + f.elements[fp + "_left"].value = ""; + f.elements[fp + "_left"].disabled = true; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + f.elements[fp + "_left_measurement"].disabled = true; + f.elements[fp + "_bottom_measurement"].disabled = true; + f.elements[fp + "_right_measurement"].disabled = true; + } + } else { + f.elements[fp + "_same"].checked = false; + + setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf])); + f.elements[fp + "_top"].disabled = false; + + setValue(f, fp + "_right", getNum(ce.style[pr + b[1] + sf])); + f.elements[fp + "_right"].disabled = false; + + setValue(f, fp + "_bottom", getNum(ce.style[pr + b[2] + sf])); + f.elements[fp + "_bottom"].disabled = false; + + setValue(f, fp + "_left", getNum(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left"].disabled = false; + + if (f.elements[fp + "_top_measurement"]) { + selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf])); + selectByValue(f, fp + '_right_measurement', getMeasurement(ce.style[pr + b[1] + sf])); + selectByValue(f, fp + '_bottom_measurement', getMeasurement(ce.style[pr + b[2] + sf])); + selectByValue(f, fp + '_left_measurement', getMeasurement(ce.style[pr + b[3] + sf])); + f.elements[fp + "_left_measurement"].disabled = false; + f.elements[fp + "_bottom_measurement"].disabled = false; + f.elements[fp + "_right_measurement"].disabled = false; + } + } +} + +function isSame(e, pr, sf, b) { + var a = [], i, x; + + if (typeof(b) == "undefined") + b = ['Top', 'Right', 'Bottom', 'Left']; + + if (typeof(sf) == "undefined" || sf == null) + sf = ""; + + a[0] = e.style[pr + b[0] + sf]; + a[1] = e.style[pr + b[1] + sf]; + a[2] = e.style[pr + b[2] + sf]; + a[3] = e.style[pr + b[3] + sf]; + + for (i=0; i 0 ? s.substring(1) : s; + + if (f.text_none.checked) + s = "none"; + + ce.style.textDecoration = s; + + // Build background styles + + ce.style.backgroundColor = f.background_color.value; + ce.style.backgroundImage = f.background_image.value != "" ? "url(" + f.background_image.value + ")" : ""; + ce.style.backgroundRepeat = f.background_repeat.value; + ce.style.backgroundAttachment = f.background_attachment.value; + + if (f.background_hpos.value != "") { + s = ""; + s += f.background_hpos.value + (isNum(f.background_hpos.value) ? f.background_hpos_measurement.value : "") + " "; + s += f.background_vpos.value + (isNum(f.background_vpos.value) ? f.background_vpos_measurement.value : ""); + ce.style.backgroundPosition = s; + } + + // Build block styles + + ce.style.wordSpacing = f.block_wordspacing.value + (isNum(f.block_wordspacing.value) ? f.block_wordspacing_measurement.value : ""); + ce.style.letterSpacing = f.block_letterspacing.value + (isNum(f.block_letterspacing.value) ? f.block_letterspacing_measurement.value : ""); + ce.style.verticalAlign = f.block_vertical_alignment.value; + ce.style.textAlign = f.block_text_align.value; + ce.style.textIndent = f.block_text_indent.value + (isNum(f.block_text_indent.value) ? f.block_text_indent_measurement.value : ""); + ce.style.whiteSpace = f.block_whitespace.value; + ce.style.display = f.block_display.value; + + // Build box styles + + ce.style.width = f.box_width.value + (isNum(f.box_width.value) ? f.box_width_measurement.value : ""); + ce.style.height = f.box_height.value + (isNum(f.box_height.value) ? f.box_height_measurement.value : ""); + ce.style.styleFloat = f.box_float.value; + ce.style.cssFloat = f.box_float.value; + + ce.style.clear = f.box_clear.value; + + if (!f.box_padding_same.checked) { + ce.style.paddingTop = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + ce.style.paddingRight = f.box_padding_right.value + (isNum(f.box_padding_right.value) ? f.box_padding_right_measurement.value : ""); + ce.style.paddingBottom = f.box_padding_bottom.value + (isNum(f.box_padding_bottom.value) ? f.box_padding_bottom_measurement.value : ""); + ce.style.paddingLeft = f.box_padding_left.value + (isNum(f.box_padding_left.value) ? f.box_padding_left_measurement.value : ""); + } else + ce.style.padding = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : ""); + + if (!f.box_margin_same.checked) { + ce.style.marginTop = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + ce.style.marginRight = f.box_margin_right.value + (isNum(f.box_margin_right.value) ? f.box_margin_right_measurement.value : ""); + ce.style.marginBottom = f.box_margin_bottom.value + (isNum(f.box_margin_bottom.value) ? f.box_margin_bottom_measurement.value : ""); + ce.style.marginLeft = f.box_margin_left.value + (isNum(f.box_margin_left.value) ? f.box_margin_left_measurement.value : ""); + } else + ce.style.margin = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : ""); + + // Build border styles + + if (!f.border_style_same.checked) { + ce.style.borderTopStyle = f.border_style_top.value; + ce.style.borderRightStyle = f.border_style_right.value; + ce.style.borderBottomStyle = f.border_style_bottom.value; + ce.style.borderLeftStyle = f.border_style_left.value; + } else + ce.style.borderStyle = f.border_style_top.value; + + if (!f.border_width_same.checked) { + ce.style.borderTopWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + ce.style.borderRightWidth = f.border_width_right.value + (isNum(f.border_width_right.value) ? f.border_width_right_measurement.value : ""); + ce.style.borderBottomWidth = f.border_width_bottom.value + (isNum(f.border_width_bottom.value) ? f.border_width_bottom_measurement.value : ""); + ce.style.borderLeftWidth = f.border_width_left.value + (isNum(f.border_width_left.value) ? f.border_width_left_measurement.value : ""); + } else + ce.style.borderWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : ""); + + if (!f.border_color_same.checked) { + ce.style.borderTopColor = f.border_color_top.value; + ce.style.borderRightColor = f.border_color_right.value; + ce.style.borderBottomColor = f.border_color_bottom.value; + ce.style.borderLeftColor = f.border_color_left.value; + } else + ce.style.borderColor = f.border_color_top.value; + + // Build list styles + + ce.style.listStyleType = f.list_type.value; + ce.style.listStylePosition = f.list_position.value; + ce.style.listStyleImage = f.list_bullet_image.value != "" ? "url(" + f.list_bullet_image.value + ")" : ""; + + // Build positioning styles + + ce.style.position = f.positioning_type.value; + ce.style.visibility = f.positioning_visibility.value; + + if (ce.style.width == "") + ce.style.width = f.positioning_width.value + (isNum(f.positioning_width.value) ? f.positioning_width_measurement.value : ""); + + if (ce.style.height == "") + ce.style.height = f.positioning_height.value + (isNum(f.positioning_height.value) ? f.positioning_height_measurement.value : ""); + + ce.style.zIndex = f.positioning_zindex.value; + ce.style.overflow = f.positioning_overflow.value; + + if (!f.positioning_placement_same.checked) { + ce.style.top = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.right = f.positioning_placement_right.value + (isNum(f.positioning_placement_right.value) ? f.positioning_placement_right_measurement.value : ""); + ce.style.bottom = f.positioning_placement_bottom.value + (isNum(f.positioning_placement_bottom.value) ? f.positioning_placement_bottom_measurement.value : ""); + ce.style.left = f.positioning_placement_left.value + (isNum(f.positioning_placement_left.value) ? f.positioning_placement_left_measurement.value : ""); + } else { + s = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : ""); + ce.style.top = s; + ce.style.right = s; + ce.style.bottom = s; + ce.style.left = s; + } + + if (!f.positioning_clip_same.checked) { + s = "rect("; + s += (isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_right.value) ? f.positioning_clip_right.value + f.positioning_clip_right_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_bottom.value) ? f.positioning_clip_bottom.value + f.positioning_clip_bottom_measurement.value : "auto") + " "; + s += (isNum(f.positioning_clip_left.value) ? f.positioning_clip_left.value + f.positioning_clip_left_measurement.value : "auto"); + s += ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } else { + s = "rect("; + t = isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto"; + s += t + " "; + s += t + " "; + s += t + " "; + s += t + ")"; + + if (s != "rect(auto auto auto auto)") + ce.style.clip = s; + } + + ce.style.cssText = ce.style.cssText; +} + +function isNum(s) { + return new RegExp('[0-9]+', 'g').test(s); +} + +function showDisabledControls() { + var f = document.forms, i, a; + + for (i=0; i 1) { + addSelectValue(f, s, p[0], p[1]); + + if (se) + selectByValue(f, s, p[1]); + } else { + addSelectValue(f, s, p[0], p[0]); + + if (se) + selectByValue(f, s, p[0]); + } + } +} + +function toggleSame(ce, pre) { + var el = document.forms[0].elements, i; + + if (ce.checked) { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = true; + el[pre + "_bottom"].disabled = true; + el[pre + "_left"].disabled = true; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = true; + el[pre + "_bottom_measurement"].disabled = true; + el[pre + "_left_measurement"].disabled = true; + } + } else { + el[pre + "_top"].disabled = false; + el[pre + "_right"].disabled = false; + el[pre + "_bottom"].disabled = false; + el[pre + "_left"].disabled = false; + + if (el[pre + "_top_measurement"]) { + el[pre + "_top_measurement"].disabled = false; + el[pre + "_right_measurement"].disabled = false; + el[pre + "_bottom_measurement"].disabled = false; + el[pre + "_left_measurement"].disabled = false; + } + } + + showDisabledControls(); +} + +function synch(fr, to) { + var f = document.forms[0]; + + f.elements[to].value = f.elements[fr].value; + + if (f.elements[fr + "_measurement"]) + selectByValue(f, to + "_measurement", f.elements[fr + "_measurement"].value); +} + +tinyMCEPopup.onInit.add(init); diff --git a/js/tiny_mce/plugins/style/langs/en_dlg.js b/js/tiny_mce/plugins/style/langs/en_dlg.js index 5026313e..9a1d4a22 100644 --- a/js/tiny_mce/plugins/style/langs/en_dlg.js +++ b/js/tiny_mce/plugins/style/langs/en_dlg.js @@ -1,63 +1 @@ -tinyMCE.addI18n('en.style_dlg',{ -title:"Edit CSS Style", -apply:"Apply", -text_tab:"Text", -background_tab:"Background", -block_tab:"Block", -box_tab:"Box", -border_tab:"Border", -list_tab:"List", -positioning_tab:"Positioning", -text_props:"Text", -text_font:"Font", -text_size:"Size", -text_weight:"Weight", -text_style:"Style", -text_variant:"Variant", -text_lineheight:"Line height", -text_case:"Case", -text_color:"Color", -text_decoration:"Decoration", -text_overline:"overline", -text_underline:"underline", -text_striketrough:"strikethrough", -text_blink:"blink", -text_none:"none", -background_color:"Background color", -background_image:"Background image", -background_repeat:"Repeat", -background_attachment:"Attachment", -background_hpos:"Horizontal position", -background_vpos:"Vertical position", -block_wordspacing:"Word spacing", -block_letterspacing:"Letter spacing", -block_vertical_alignment:"Vertical alignment", -block_text_align:"Text align", -block_text_indent:"Text indent", -block_whitespace:"Whitespace", -block_display:"Display", -box_width:"Width", -box_height:"Height", -box_float:"Float", -box_clear:"Clear", -padding:"Padding", -same:"Same for all", -top:"Top", -right:"Right", -bottom:"Bottom", -left:"Left", -margin:"Margin", -style:"Style", -width:"Width", -height:"Height", -color:"Color", -list_type:"Type", -bullet_image:"Bullet image", -position:"Position", -positioning_type:"Type", -visibility:"Visibility", -zindex:"Z-index", -overflow:"Overflow", -placement:"Placement", -clip:"Clip" -}); \ No newline at end of file +tinyMCE.addI18n('en.style_dlg',{"text_lineheight":"Line Height","text_variant":"Variant","text_style":"Style","text_weight":"Weight","text_size":"Size","text_font":"Font","text_props":"Text","positioning_tab":"Positioning","list_tab":"List","border_tab":"Border","box_tab":"Box","block_tab":"Block","background_tab":"Background","text_tab":"Text",apply:"Apply",title:"Edit CSS Style",clip:"Clip",placement:"Placement",overflow:"Overflow",zindex:"Z-index",visibility:"Visibility","positioning_type":"Type",position:"Position","bullet_image":"Bullet Image","list_type":"Type",color:"Color",height:"Height",width:"Width",style:"Style",margin:"Margin",left:"Left",bottom:"Bottom",right:"Right",top:"Top",same:"Same for All",padding:"Padding","box_clear":"Clear","box_float":"Float","box_height":"Height","box_width":"Width","block_display":"Display","block_whitespace":"Whitespace","block_text_indent":"Text Indent","block_text_align":"Text Align","block_vertical_alignment":"Vertical Alignment","block_letterspacing":"Letter Spacing","block_wordspacing":"Word Spacing","background_vpos":"Vertical Position","background_hpos":"Horizontal Position","background_attachment":"Attachment","background_repeat":"Repeat","background_image":"Background Image","background_color":"Background Color","text_none":"None","text_blink":"Blink","text_case":"Case","text_striketrough":"Strikethrough","text_underline":"Underline","text_overline":"Overline","text_decoration":"Decoration","text_color":"Color",text:"Text",background:"Background",block:"Block",box:"Box",border:"Border",list:"List"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/style/props.htm b/js/tiny_mce/plugins/style/props.htm index 08e02f16..76ab68d8 100644 --- a/js/tiny_mce/plugins/style/props.htm +++ b/js/tiny_mce/plugins/style/props.htm @@ -10,277 +10,319 @@ - + +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - - -
     
    -
    - -
    - - - -
    - - - - - - -
    - -  
    -
    - -
    - - - - - -
     
    -
    {#style_dlg.text_decoration} - - - - - - - - - - - - - - - - - - - - - -
    -
    +
    + {#style_dlg.text} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + + +
      + + +
    +
    + +
    + + + +
    + + + + + + +
    + +   + + +
    +
    + +
    + + + + + +
     
    +
    {#style_dlg.text_decoration} + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    - - - - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + +
    - - - - +
    + {#style_dlg.background} +
     
    + + + + + + + + - +
    + + + + + +
     
    +
    + + + -
     
    -
    +
    - - - - -
     
    -
    + + + + + + +
      + + +
    +
    - - - - - - -
     
    -
    + + + + + - - - -
      -
    - - - - - - -
     
    -
    + +
    + + + +
    - - - - - +
    + {#style_dlg.block} +
    - - - - - - -
     
    -
    + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - -
    + + + + + + +
      + + +
    +
    - - - - - - -
     
    -
    + + + + + + +
      + + +
    +
    - - - - - - -
     
    -
    + + + + + - - - + + + +
      + -
    +
    + + + + + + + + + + +
    - - - - - - - +
    + {#style_dlg.box} +
    - - - - - - -
     
    -
       
    + + + + + + + + + + + + + +
    + + + + + + +
      + + +
    +
       
    + + + + + + +
      + + +
    +
       
    + - - - - - - - - - -
     
    - -     - - -
    {#style_dlg.padding} - +
    @@ -288,11 +330,14 @@ @@ -300,11 +345,14 @@ @@ -312,11 +360,14 @@ @@ -324,11 +375,14 @@ @@ -341,7 +395,7 @@
    {#style_dlg.margin} -
     
    - +
    - +
      + + +
    - +
    - +
      + + +
    - +
    - +
      + + +
    - +
    - +
      + + +
    +
    @@ -349,11 +403,14 @@ @@ -361,11 +418,14 @@ @@ -373,11 +433,14 @@ @@ -385,11 +448,14 @@ @@ -401,131 +467,148 @@
    -
     
    - +
    - +
      + + +
    - +
    - +
      + + +
    - +
    - +
      + + +
    - +
    - +
      + + +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      {#style_dlg.style} {#style_dlg.width} {#style_dlg.color}
          
    {#style_dlg.top}   - - - - - - -
     
    -
      - - - - - -
     
    -
    {#style_dlg.right}   - - - - - - -
     
    -
      - - - - - -
     
    -
    {#style_dlg.bottom}   - - - - - - -
     
    -
      - - - - - -
     
    -
    {#style_dlg.left}   - - - - - - -
     
    -
      - - - - - +
    + {#style_dlg.border} +
     
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {#style_dlg.style} {#style_dlg.width} {#style_dlg.color}
          
    {#style_dlg.top}   + + + + + + +
      + + +
    +
      + + + + + +
     
    +
    {#style_dlg.right}   + + + + + + +
      + + +
    +
      + + + + + +
     
    +
    {#style_dlg.bottom}   + + + + + + +
      + + +
    +
      + + + + + +
     
    +
    {#style_dlg.left}   + + + + + + +
      + + +
    +
      + + + + + +
     
    +
    -
    +
    - +
    + {#style_dlg.list} +
    @@ -541,10 +624,13 @@
    +
    - +
    + {#style_dlg.position} +
    @@ -555,11 +641,14 @@ @@ -570,11 +659,14 @@ @@ -582,12 +674,13 @@
    - +
    - +
      + + +
    - +
    - +
      + + +
    +
    {#style_dlg.placement} - +
    @@ -595,11 +688,14 @@ @@ -607,11 +703,14 @@ @@ -619,11 +718,14 @@ @@ -631,11 +733,14 @@ @@ -648,7 +753,7 @@
    {#style_dlg.clip} -
     
    {#style_dlg.top} - +
    - +
      + + +
    {#style_dlg.right} - +
    - +
      + + +
    {#style_dlg.bottom} - +
    - +
      + + +
    {#style_dlg.left} - +
    - +
      + + +
    +
    @@ -656,11 +761,14 @@ @@ -668,11 +776,14 @@ @@ -680,11 +791,14 @@ @@ -692,11 +806,14 @@ @@ -709,11 +826,8 @@
    -
    - - -
    - + +
    diff --git a/js/tiny_mce/plugins/tabfocus/editor_plugin.js b/js/tiny_mce/plugins/tabfocus/editor_plugin.js index 27d24402..42a82d11 100644 --- a/js/tiny_mce/plugins/tabfocus/editor_plugin.js +++ b/js/tiny_mce/plugins/tabfocus/editor_plugin.js @@ -1 +1 @@ -(function(){var c=tinymce.DOM,a=tinymce.dom.Event,d=tinymce.each,b=tinymce.explode;tinymce.create("tinymce.plugins.TabFocusPlugin",{init:function(f,g){function e(i,j){if(j.keyCode===9){return a.cancel(j)}}function h(l,p){var j,m,o,n,k;function q(i){o=c.getParent(l.id,"form");n=o.elements;if(o){d(n,function(s,r){if(s.id==l.id){j=r;return false}});if(i>0){for(m=j+1;m=0;m--){if(n[m].type!="hidden"){return n[m]}}}}return null}if(p.keyCode===9){k=b(l.getParam("tab_focus",l.getParam("tabfocus_elements",":prev,:next")));if(k.length==1){k[1]=k[0];k[0]=":prev"}if(p.shiftKey){if(k[0]==":prev"){n=q(-1)}else{n=c.get(k[0])}}else{if(k[1]==":next"){n=q(1)}else{n=c.get(k[1])}}if(n){if(l=tinymce.get(n.id||n.name)){l.focus()}else{window.setTimeout(function(){window.focus();n.focus()},10)}return a.cancel(p)}}}f.onKeyUp.add(e);if(tinymce.isGecko){f.onKeyPress.add(h);f.onKeyDown.add(e)}else{f.onKeyDown.add(h)}f.onInit.add(function(){d(c.select("a:first,a:last",f.getContainer()),function(i){a.add(i,"focus",function(){f.focus()})})})},getInfo:function(){return{longname:"Tabfocus",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("tabfocus",tinymce.plugins.TabFocusPlugin)})(); \ No newline at end of file +(function(){var c=tinymce.DOM,a=tinymce.dom.Event,d=tinymce.each,b=tinymce.explode;tinymce.create("tinymce.plugins.TabFocusPlugin",{init:function(f,g){function e(i,j){if(j.keyCode===9){return a.cancel(j)}}function h(l,p){var j,m,o,n,k;function q(t){n=c.select(":input:enabled,*[tabindex]");function s(v){return v.nodeName==="BODY"||(v.type!="hidden"&&!(v.style.display=="none")&&!(v.style.visibility=="hidden")&&s(v.parentNode))}function i(v){return v.attributes.tabIndex.specified||v.nodeName=="INPUT"||v.nodeName=="TEXTAREA"}function u(){return tinymce.isIE6||tinymce.isIE7}function r(v){return((!u()||i(v)))&&v.getAttribute("tabindex")!="-1"&&s(v)}d(n,function(w,v){if(w.id==l.id){j=v;return false}});if(t>0){for(m=j+1;m=0;m--){if(r(n[m])){return n[m]}}}return null}if(p.keyCode===9){k=b(l.getParam("tab_focus",l.getParam("tabfocus_elements",":prev,:next")));if(k.length==1){k[1]=k[0];k[0]=":prev"}if(p.shiftKey){if(k[0]==":prev"){n=q(-1)}else{n=c.get(k[0])}}else{if(k[1]==":next"){n=q(1)}else{n=c.get(k[1])}}if(n){if(n.id&&(l=tinymce.get(n.id||n.name))){l.focus()}else{window.setTimeout(function(){if(!tinymce.isWebKit){window.focus()}n.focus()},10)}return a.cancel(p)}}}f.onKeyUp.add(e);if(tinymce.isGecko){f.onKeyPress.add(h);f.onKeyDown.add(e)}else{f.onKeyDown.add(h)}},getInfo:function(){return{longname:"Tabfocus",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("tabfocus",tinymce.plugins.TabFocusPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/tabfocus/editor_plugin_src.js b/js/tiny_mce/plugins/tabfocus/editor_plugin_src.js index c2be2f40..a1579c85 100644 --- a/js/tiny_mce/plugins/tabfocus/editor_plugin_src.js +++ b/js/tiny_mce/plugins/tabfocus/editor_plugin_src.js @@ -1,112 +1,122 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode; - - tinymce.create('tinymce.plugins.TabFocusPlugin', { - init : function(ed, url) { - function tabCancel(ed, e) { - if (e.keyCode === 9) - return Event.cancel(e); - }; - - function tabHandler(ed, e) { - var x, i, f, el, v; - - function find(d) { - f = DOM.getParent(ed.id, 'form'); - el = f.elements; - - if (f) { - each(el, function(e, i) { - if (e.id == ed.id) { - x = i; - return false; - } - }); - - if (d > 0) { - for (i = x + 1; i < el.length; i++) { - if (el[i].type != 'hidden') - return el[i]; - } - } else { - for (i = x - 1; i >= 0; i--) { - if (el[i].type != 'hidden') - return el[i]; - } - } - } - - return null; - }; - - if (e.keyCode === 9) { - v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next'))); - - if (v.length == 1) { - v[1] = v[0]; - v[0] = ':prev'; - } - - // Find element to focus - if (e.shiftKey) { - if (v[0] == ':prev') - el = find(-1); - else - el = DOM.get(v[0]); - } else { - if (v[1] == ':next') - el = find(1); - else - el = DOM.get(v[1]); - } - - if (el) { - if (ed = tinymce.get(el.id || el.name)) - ed.focus(); - else - window.setTimeout(function() {window.focus();el.focus();}, 10); - - return Event.cancel(e); - } - } - }; - - ed.onKeyUp.add(tabCancel); - - if (tinymce.isGecko) { - ed.onKeyPress.add(tabHandler); - ed.onKeyDown.add(tabCancel); - } else - ed.onKeyDown.add(tabHandler); - - ed.onInit.add(function() { - each(DOM.select('a:first,a:last', ed.getContainer()), function(n) { - Event.add(n, 'focus', function() {ed.focus();}); - }); - }); - }, - - getInfo : function() { - return { - longname : 'Tabfocus', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin); -})(); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode; + + tinymce.create('tinymce.plugins.TabFocusPlugin', { + init : function(ed, url) { + function tabCancel(ed, e) { + if (e.keyCode === 9) + return Event.cancel(e); + } + + function tabHandler(ed, e) { + var x, i, f, el, v; + + function find(d) { + el = DOM.select(':input:enabled,*[tabindex]'); + + function canSelectRecursive(e) { + return e.nodeName==="BODY" || (e.type != 'hidden' && + !(e.style.display == "none") && + !(e.style.visibility == "hidden") && canSelectRecursive(e.parentNode)); + } + function canSelectInOldIe(el) { + return el.attributes["tabIndex"].specified || el.nodeName == "INPUT" || el.nodeName == "TEXTAREA"; + } + function isOldIe() { + return tinymce.isIE6 || tinymce.isIE7; + } + function canSelect(el) { + return ((!isOldIe() || canSelectInOldIe(el))) && el.getAttribute("tabindex") != '-1' && canSelectRecursive(el); + } + + each(el, function(e, i) { + if (e.id == ed.id) { + x = i; + return false; + } + }); + if (d > 0) { + for (i = x + 1; i < el.length; i++) { + if (canSelect(el[i])) + return el[i]; + } + } else { + for (i = x - 1; i >= 0; i--) { + if (canSelect(el[i])) + return el[i]; + } + } + + return null; + } + + if (e.keyCode === 9) { + v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next'))); + + if (v.length == 1) { + v[1] = v[0]; + v[0] = ':prev'; + } + + // Find element to focus + if (e.shiftKey) { + if (v[0] == ':prev') + el = find(-1); + else + el = DOM.get(v[0]); + } else { + if (v[1] == ':next') + el = find(1); + else + el = DOM.get(v[1]); + } + + if (el) { + if (el.id && (ed = tinymce.get(el.id || el.name))) + ed.focus(); + else + window.setTimeout(function() { + if (!tinymce.isWebKit) + window.focus(); + el.focus(); + }, 10); + + return Event.cancel(e); + } + } + } + + ed.onKeyUp.add(tabCancel); + + if (tinymce.isGecko) { + ed.onKeyPress.add(tabHandler); + ed.onKeyDown.add(tabCancel); + } else + ed.onKeyDown.add(tabHandler); + + }, + + getInfo : function() { + return { + longname : 'Tabfocus', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin); +})(); diff --git a/js/tiny_mce/plugins/table/cell.htm b/js/tiny_mce/plugins/table/cell.htm index d243e1d8..2922f7a2 100644 --- a/js/tiny_mce/plugins/table/cell.htm +++ b/js/tiny_mce/plugins/table/cell.htm @@ -1,178 +1,180 @@ - - - - {#table_dlg.cell_title} - - - - - - - - -
    - - -
    -
    -
    - {#table_dlg.general_props} - -
     
    {#style_dlg.top} - +
    - +
      + + +
    {#style_dlg.right} - +
    - +
      + + +
    {#style_dlg.bottom} - +
    - +
      + + +
    {#style_dlg.left} - +
    - +
      + + +
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - -
    - -
    -
    -
    - -
    -
    - {#table_dlg.advanced_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - - - - - -
     
    -
    - - - - - -
     
    -
    - - - - - -
     
    -
    -
    -
    -
    - -
    -
    - -
    - - - -
    - - - + + + + {#table_dlg.cell_title} + + + + + + + + + +
    + + +
    +
    +
    + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + +
    + +
    +
    +
    + +
    +
    + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + + + + + +
     
    +
    + + + + + +
     
    +
    + + + + + +
     
    +
    +
    +
    +
    + +
    +
    + +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/table/editor_plugin.js b/js/tiny_mce/plugins/table/editor_plugin.js index 39f2c694..2f3b0e2d 100644 --- a/js/tiny_mce/plugins/table/editor_plugin.js +++ b/js/tiny_mce/plugins/table/editor_plugin.js @@ -1 +1 @@ -(function(b){var c=b.each;function a(E,D,H){var e,I,A,n;r();n=D.getParent(H.getStart(),"th,td");if(n){I=C(n);A=F();n=v(I.x,I.y)}function r(){var J=0;e=[];c(["thead","tbody","tfoot"],function(K){var L=D.select(K+" tr",E);c(L,function(M,N){N+=J;c(D.select("td,th",M),function(T,O){var P,Q,R,S;if(e[N]){while(e[N][O]){O++}}R=g(T,"rowspan");S=g(T,"colspan");for(Q=N;Q'}return false}},"childNodes");J=J.cloneNode(false);J.rowSpan=J.colSpan=1;if(K){J.appendChild(K)}else{if(!b.isIE){J.innerHTML='
    '}}return J}function p(){var J=D.createRng();c(D.select("tr",E),function(K){if(K.cells.length==0){D.remove(K)}});if(D.select("tr",E).length==0){J.setStartAfter(E);J.setEndAfter(E);H.setRng(J);D.remove(E);return}c(D.select("thead,tbody,tfoot",E),function(K){if(K.rows.length==0){D.remove(K)}});r();row=e[Math.min(e.length-1,I.y)];if(row){H.select(row[Math.min(row.length-1,I.x)].elm,true);H.collapse(true)}}function s(P,N,R,O){var M,K,J,L,Q;M=e[N][P].elm.parentNode;for(J=1;J<=R;J++){M=D.getNext(M,"tr");if(M){for(K=P;K>=0;K--){Q=e[N+J][K].elm;if(Q.parentNode==M){for(L=1;L<=O;L++){D.insertAfter(d(Q),Q)}break}}if(K==-1){for(L=1;L<=O;L++){M.insertBefore(d(M.cells[0]),M.cells[0])}}}}}function z(){c(e,function(J,K){c(J,function(M,L){var P,O,Q,N;if(h(M)){M=M.elm;P=g(M,"colspan");O=g(M,"rowspan");if(P>1||O>1){M.colSpan=M.rowSpan=1;for(N=0;N1){O.rowSpan=rowSpan+1;continue}}else{if(J>0&&e[J-1][N]){R=e[J-1][N].elm;rowSpan=g(R,"rowspan");if(rowSpan>1){R.rowSpan=rowSpan+1;continue}}}K=d(O);K.colSpan=O.colSpan;Q.appendChild(K);L=O}}if(Q.hasChildNodes()){if(!M){D.insertAfter(Q,P)}else{P.parentNode.insertBefore(Q,P)}}}function f(K){var L,J;c(e,function(M,N){c(M,function(P,O){if(h(P)){L=O;if(K){return false}}});if(K){return !L}});c(e,function(P,Q){var M=P[L].elm,N,O;if(M!=J){O=g(M,"colspan");N=g(M,"rowspan");if(O==1){if(!K){D.insertAfter(d(M),M);s(L,Q,N-1,O)}else{M.parentNode.insertBefore(d(M),M);s(L,Q,N-1,O)}}else{M.colSpan++}J=M}})}function m(){var J=[];c(e,function(K,L){c(K,function(N,M){if(h(N)&&b.inArray(J,M)===-1){c(e,function(Q){var O=Q[M].elm,P;P=g(O,"colspan");if(P>1){O.colSpan=P-1}else{D.remove(O)}});J.push(M)}})});p()}function l(){var K;function J(N){var M,O,L;M=D.getNext(N,"tr");c(N.cells,function(P){var Q=g(P,"rowspan");if(Q>1){P.rowSpan=Q-1;O=C(P);s(O.x,O.y,1,1)}});O=C(N.cells[0]);c(e[O.y],function(P){var Q;P=P.elm;if(P!=L){Q=g(P,"rowspan");if(Q<=1){D.remove(P)}else{P.rowSpan=Q-1}L=P}})}K=j();c(K.reverse(),function(L){J(L)});p()}function B(){var J=j();D.remove(J);p();return J}function G(){var J=j();c(J,function(L,K){J[K]=L.cloneNode(true)});return J}function w(L,K){var M=j(),J=M[K?0:M.length-1],N=J.cells.length;c(e,function(P){var O;N=0;c(P,function(R,Q){if(R.real){N+=R.colspan}if(R.elm.parentNode==J){O=1}});if(O){return false}});if(!K){L.reverse()}c(L,function(Q){var P=Q.cells.length,O;for(i=0;iK){K=O}if(N>J){J=N}if(P.real){R=P.colspan-1;Q=P.rowspan-1;if(R){if(O+R>K){K=O+R}}if(Q){if(N+Q>J){J=N+Q}}}}})});return{x:K,y:J}}function t(P){var M,L,R,Q,K,J,N,O;A=C(P);if(I&&A){M=Math.min(I.x,A.x);L=Math.min(I.y,A.y);R=Math.max(I.x,A.x);Q=Math.max(I.y,A.y);K=R;J=Q;for(y=L;y<=J;y++){P=e[y][M];if(!P.real){if(M-(P.colspan-1)K){K=x+N}}if(O){if(y+O>J){J=y+O}}}}}D.removeClass(D.select("td.mceSelected,th.mceSelected"),"mceSelected");for(y=L;y<=J;y++){for(x=M;x<=K;x++){D.addClass(e[y][x].elm,"mceSelected")}}}}b.extend(this,{deleteTable:q,split:z,merge:o,insertRow:k,insertCol:f,deleteCols:m,deleteRows:l,cutRows:B,copyRows:G,pasteRows:w,getPos:C,setStartCell:u,setEndCell:t})}b.create("tinymce.plugins.TablePlugin",{init:function(e,f){var d,j;function h(m){var l=e.selection,k=e.dom.getParent(m||l.getNode(),"table");if(k){return new a(k,e.dom,l)}}function g(){e.getBody().style.webkitUserSelect="";e.dom.removeClass(e.dom.select("td.mceSelected,th.mceSelected"),"mceSelected")}c([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mceTableDelete"],["delete_col","table.delete_col_desc","mceTableDeleteCol"],["delete_row","table.delete_row_desc","mceTableDeleteRow"],["col_after","table.col_after_desc","mceTableInsertColAfter"],["col_before","table.col_before_desc","mceTableInsertColBefore"],["row_after","table.row_after_desc","mceTableInsertRowAfter"],["row_before","table.row_before_desc","mceTableInsertRowBefore"],["row_props","table.row_desc","mceTableRowProps",true],["cell_props","table.cell_desc","mceTableCellProps",true],["split_cells","table.split_cells_desc","mceTableSplitCells",true],["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(k){e.addButton(k[0],{title:k[1],cmd:k[2],ui:k[3]})});if(!b.isIE){e.onClick.add(function(k,l){l=l.target;if(l.nodeName==="TABLE"){k.selection.select(l)}})}e.onNodeChange.add(function(l,k,o){var m;o=l.selection.getStart();m=l.dom.getParent(o,"td,th,caption");k.setActive("table",o.nodeName==="TABLE"||!!m);if(m&&m.nodeName==="CAPTION"){m=0}k.setDisabled("delete_table",!m);k.setDisabled("delete_col",!m);k.setDisabled("delete_table",!m);k.setDisabled("delete_row",!m);k.setDisabled("col_after",!m);k.setDisabled("col_before",!m);k.setDisabled("row_after",!m);k.setDisabled("row_before",!m);k.setDisabled("row_props",!m);k.setDisabled("cell_props",!m);k.setDisabled("split_cells",!m);k.setDisabled("merge_cells",!m)});e.onInit.add(function(l){var k,o,p=l.dom,m;d=l.windowManager;l.onMouseDown.add(function(q,r){if(r.button!=2){g();o=p.getParent(r.target,"td,th");k=p.getParent(o,"table")}});p.bind(l.getDoc(),"mouseover",function(t){var r,q,s=t.target;if(o&&(m||s!=o)&&(s.nodeName=="TD"||s.nodeName=="TH")){q=p.getParent(s,"table");if(q==k){if(!m){m=h(q);m.setStartCell(o);l.getBody().style.webkitUserSelect="none"}m.setEndCell(s)}r=l.selection.getSel();if(r.removeAllRanges){r.removeAllRanges()}else{r.empty()}t.preventDefault()}});l.onMouseUp.add(function(z,A){var r,t=z.selection,B,C=t.getSel(),q,u,s,w;if(o){if(m){z.getBody().style.webkitUserSelect=""}function v(D,F){var E=new b.dom.TreeWalker(D,D);do{if(D.nodeType==3&&b.trim(D.nodeValue).length!=0){if(F){r.setStart(D,0)}else{r.setEnd(D,D.nodeValue.length)}return}if(D.nodeName=="BR"){if(F){r.setStartBefore(D)}else{r.setEndBefore(D)}return}}while(D=(F?E.next():E.prev()))}B=p.select("td.mceSelected,th.mceSelected");if(B.length>0){r=p.createRng();u=B[0];w=B[B.length-1];v(u,1);q=new b.dom.TreeWalker(u,p.getParent(B[0],"table"));do{if(u.nodeName=="TD"||u.nodeName=="TH"){if(!p.hasClass(u,"mceSelected")){break}s=u}}while(u=q.next());v(s);t.setRng(r)}z.nodeChanged();o=m=k=null}});l.onKeyUp.add(function(q,r){g()});if(l&&l.plugins.contextmenu){l.plugins.contextmenu.onContextMenu.add(function(s,q,u){var v,t=l.selection,r=t.getNode()||l.getBody();if(l.dom.getParent(u,"td")||l.dom.getParent(u,"th")){q.removeAll();if(r.nodeName=="A"&&!l.dom.getAttrib(r,"name")){q.add({title:"advanced.link_desc",icon:"link",cmd:l.plugins.advlink?"mceAdvLink":"mceLink",ui:true});q.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"});q.addSeparator()}if(r.nodeName=="IMG"&&r.className.indexOf("mceItem")==-1){q.add({title:"advanced.image_desc",icon:"image",cmd:l.plugins.advimage?"mceAdvImage":"mceImage",ui:true});q.addSeparator()}q.add({title:"table.desc",icon:"table",cmd:"mceInsertTable",value:{action:"insert"}});q.add({title:"table.props_desc",icon:"table_props",cmd:"mceInsertTable"});q.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});q.addSeparator();v=q.addMenu({title:"table.cell"});v.add({title:"table.cell_desc",icon:"cell_props",cmd:"mceTableCellProps"});v.add({title:"table.split_cells_desc",icon:"split_cells",cmd:"mceTableSplitCells"});v.add({title:"table.merge_cells_desc",icon:"merge_cells",cmd:"mceTableMergeCells"});v=q.addMenu({title:"table.row"});v.add({title:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});v.add({title:"table.row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});v.add({title:"table.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});v.add({title:"table.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});v.addSeparator();v.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});v.add({title:"table.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});v.add({title:"table.paste_row_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!j);v.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfter"}).setDisabled(!j);v=q.addMenu({title:"table.col"});v.add({title:"table.col_before_desc",icon:"col_before",cmd:"mceTableInsertColBefore"});v.add({title:"table.col_after_desc",icon:"col_after",cmd:"mceTableInsertColAfter"});v.add({title:"table.delete_col_desc",icon:"delete_col",cmd:"mceTableDeleteCol"})}else{q.add({title:"table.desc",icon:"table",cmd:"mceInsertTable"})}})}if(!b.isIE){function n(){var q;for(q=l.getBody().lastChild;q&&q.nodeType==3&&!q.nodeValue.length;q=q.previousSibling){}if(q&&q.nodeName=="TABLE"){l.dom.add(l.getBody(),"p",null,'
    ')}}if(b.isGecko){l.onKeyDown.add(function(r,t){var q,s,u=r.dom;if(t.keyCode==37||t.keyCode==38){q=r.selection.getRng();s=u.getParent(q.startContainer,"table");if(s&&r.getBody().firstChild==s){if(isAtStart(q,s)){q=u.createRng();q.setStartBefore(s);q.setEndBefore(s);r.selection.setRng(q);t.preventDefault()}}}})}l.onKeyUp.add(n);l.onSetContent.add(n);l.onVisualAid.add(n);l.onPreProcess.add(function(q,s){var r=s.node.lastChild;if(r&&r.childNodes.length==1&&r.firstChild.nodeName=="BR"){q.dom.remove(r)}});n()}});c({mceTableSplitCells:function(k){k.split()},mceTableMergeCells:function(l){var m,n,k;k=e.dom.getParent(e.selection.getNode(),"th,td");if(k){m=k.rowSpan;n=k.colSpan}if(!e.dom.select("td.mceSelected,th.mceSelected").length){d.open({url:f+"/merge_cells.htm",width:240+parseInt(e.getLang("table.merge_cells_delta_width",0)),height:110+parseInt(e.getLang("table.merge_cells_delta_height",0)),inline:1},{rows:m,cols:n,onaction:function(o){l.merge(k,o.cols,o.rows)},plugin_url:f})}else{l.merge()}},mceTableInsertRowBefore:function(k){k.insertRow(true)},mceTableInsertRowAfter:function(k){k.insertRow()},mceTableInsertColBefore:function(k){k.insertCol(true)},mceTableInsertColAfter:function(k){k.insertCol()},mceTableDeleteCol:function(k){k.deleteCols()},mceTableDeleteRow:function(k){k.deleteRows()},mceTableCutRow:function(k){j=k.cutRows()},mceTableCopyRow:function(k){j=k.copyRows()},mceTablePasteRowBefore:function(k){k.pasteRows(j,true)},mceTablePasteRowAfter:function(k){k.pasteRows(j)},mceTableDelete:function(k){k.deleteTable()}},function(l,k){e.addCommand(k,function(){var m=h();if(m){l(m);e.execCommand("mceRepaint");g()}})});c({mceInsertTable:function(k){d.open({url:f+"/table.htm",width:400+parseInt(e.getLang("table.table_delta_width",0)),height:320+parseInt(e.getLang("table.table_delta_height",0)),inline:1},{plugin_url:f,action:k?k.action:0})},mceTableRowProps:function(){d.open({url:f+"/row.htm",width:400+parseInt(e.getLang("table.rowprops_delta_width",0)),height:295+parseInt(e.getLang("table.rowprops_delta_height",0)),inline:1},{plugin_url:f})},mceTableCellProps:function(){d.open({url:f+"/cell.htm",width:400+parseInt(e.getLang("table.cellprops_delta_width",0)),height:295+parseInt(e.getLang("table.cellprops_delta_height",0)),inline:1},{plugin_url:f})}},function(l,k){e.addCommand(k,function(m,n){l(n)})})}});b.PluginManager.add("table",b.plugins.TablePlugin)})(tinymce); \ No newline at end of file +(function(d){var e=d.each;function c(g,h){var j=h.ownerDocument,f=j.createRange(),k;f.setStartBefore(h);f.setEnd(g.endContainer,g.endOffset);k=j.createElement("body");k.appendChild(f.cloneContents());return k.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}function a(g,f){return parseInt(g.getAttribute(f)||1)}function b(H,G,K){var g,L,D,o;t();o=G.getParent(K.getStart(),"th,td");if(o){L=F(o);D=I();o=z(L.x,L.y)}function A(N,M){N=N.cloneNode(M);N.removeAttribute("id");return N}function t(){var M=0;g=[];e(["thead","tbody","tfoot"],function(N){var O=G.select("> "+N+" tr",H);e(O,function(P,Q){Q+=M;e(G.select("> td, > th",P),function(W,R){var S,T,U,V;if(g[Q]){while(g[Q][R]){R++}}U=a(W,"rowspan");V=a(W,"colspan");for(T=Q;T'}return false}},"childNodes");M=A(M,false);s(M,"rowSpan",1);s(M,"colSpan",1);if(N){M.appendChild(N)}else{if(!d.isIE){M.innerHTML='
    '}}return M}function q(){var M=G.createRng();e(G.select("tr",H),function(N){if(N.cells.length==0){G.remove(N)}});if(G.select("tr",H).length==0){M.setStartAfter(H);M.setEndAfter(H);K.setRng(M);G.remove(H);return}e(G.select("thead,tbody,tfoot",H),function(N){if(N.rows.length==0){G.remove(N)}});t();row=g[Math.min(g.length-1,L.y)];if(row){K.select(row[Math.min(row.length-1,L.x)].elm,true);K.collapse(true)}}function u(S,Q,U,R){var P,N,M,O,T;P=g[Q][S].elm.parentNode;for(M=1;M<=U;M++){P=G.getNext(P,"tr");if(P){for(N=S;N>=0;N--){T=g[Q+M][N].elm;if(T.parentNode==P){for(O=1;O<=R;O++){G.insertAfter(f(T),T)}break}}if(N==-1){for(O=1;O<=R;O++){P.insertBefore(f(P.cells[0]),P.cells[0])}}}}}function C(){e(g,function(M,N){e(M,function(P,O){var S,R,T,Q;if(j(P)){P=P.elm;S=a(P,"colspan");R=a(P,"rowspan");if(S>1||R>1){s(P,"rowSpan",1);s(P,"colSpan",1);for(Q=0;Q1){s(S,"rowSpan",O+1);continue}}else{if(M>0&&g[M-1][R]){V=g[M-1][R].elm;O=a(V,"rowSpan");if(O>1){s(V,"rowSpan",O+1);continue}}}N=f(S);s(N,"colSpan",S.colSpan);U.appendChild(N);P=S}}if(U.hasChildNodes()){if(!Q){G.insertAfter(U,T)}else{T.parentNode.insertBefore(U,T)}}}function h(N){var O,M;e(g,function(P,Q){e(P,function(S,R){if(j(S)){O=R;if(N){return false}}});if(N){return !O}});e(g,function(S,T){var P,Q,R;if(!S[O]){return}P=S[O].elm;if(P!=M){R=a(P,"colspan");Q=a(P,"rowspan");if(R==1){if(!N){G.insertAfter(f(P),P);u(O,T,Q-1,R)}else{P.parentNode.insertBefore(f(P),P);u(O,T,Q-1,R)}}else{s(P,"colSpan",P.colSpan+1)}M=P}})}function n(){var M=[];e(g,function(N,O){e(N,function(Q,P){if(j(Q)&&d.inArray(M,P)===-1){e(g,function(T){var R=T[P].elm,S;S=a(R,"colSpan");if(S>1){s(R,"colSpan",S-1)}else{G.remove(R)}});M.push(P)}})});q()}function m(){var N;function M(Q){var P,R,O;P=G.getNext(Q,"tr");e(Q.cells,function(S){var T=a(S,"rowSpan");if(T>1){s(S,"rowSpan",T-1);R=F(S);u(R.x,R.y,1,1)}});R=F(Q.cells[0]);e(g[R.y],function(S){var T;S=S.elm;if(S!=O){T=a(S,"rowSpan");if(T<=1){G.remove(S)}else{s(S,"rowSpan",T-1)}O=S}})}N=k();e(N.reverse(),function(O){M(O)});q()}function E(){var M=k();G.remove(M);q();return M}function J(){var M=k();e(M,function(O,N){M[N]=A(O,true)});return M}function B(O,N){var P=k(),M=P[N?0:P.length-1],Q=M.cells.length;e(g,function(S){var R;Q=0;e(S,function(U,T){if(U.real){Q+=U.colspan}if(U.elm.parentNode==M){R=1}});if(R){return false}});if(!N){O.reverse()}e(O,function(T){var S=T.cells.length,R;for(i=0;iN){N=R}if(Q>M){M=Q}if(S.real){U=S.colspan-1;T=S.rowspan-1;if(U){if(R+U>N){N=R+U}}if(T){if(Q+T>M){M=Q+T}}}}})});return{x:N,y:M}}function v(S){var P,O,U,T,N,M,Q,R;D=F(S);if(L&&D){P=Math.min(L.x,D.x);O=Math.min(L.y,D.y);U=Math.max(L.x,D.x);T=Math.max(L.y,D.y);N=U;M=T;for(y=O;y<=M;y++){S=g[y][P];if(!S.real){if(P-(S.colspan-1)N){N=x+Q}}if(R){if(y+R>M){M=y+R}}}}}G.removeClass(G.select("td.mceSelected,th.mceSelected"),"mceSelected");for(y=O;y<=M;y++){for(x=P;x<=N;x++){if(g[y][x]){G.addClass(g[y][x].elm,"mceSelected")}}}}}d.extend(this,{deleteTable:r,split:C,merge:p,insertRow:l,insertCol:h,deleteCols:n,deleteRows:m,cutRows:E,copyRows:J,pasteRows:B,getPos:F,setStartCell:w,setEndCell:v})}d.create("tinymce.plugins.TablePlugin",{init:function(g,h){var f,m,j=true;function l(p){var o=g.selection,n=g.dom.getParent(p||o.getNode(),"table");if(n){return new b(n,g.dom,o)}}function k(){g.getBody().style.webkitUserSelect="";if(j){g.dom.removeClass(g.dom.select("td.mceSelected,th.mceSelected"),"mceSelected");j=false}}e([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mceTableDelete"],["delete_col","table.delete_col_desc","mceTableDeleteCol"],["delete_row","table.delete_row_desc","mceTableDeleteRow"],["col_after","table.col_after_desc","mceTableInsertColAfter"],["col_before","table.col_before_desc","mceTableInsertColBefore"],["row_after","table.row_after_desc","mceTableInsertRowAfter"],["row_before","table.row_before_desc","mceTableInsertRowBefore"],["row_props","table.row_desc","mceTableRowProps",true],["cell_props","table.cell_desc","mceTableCellProps",true],["split_cells","table.split_cells_desc","mceTableSplitCells",true],["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(n){g.addButton(n[0],{title:n[1],cmd:n[2],ui:n[3]})});if(!d.isIE){g.onClick.add(function(n,o){o=o.target;if(o.nodeName==="TABLE"){n.selection.select(o);n.nodeChanged()}})}g.onPreProcess.add(function(o,p){var n,q,r,t=o.dom,s;n=t.select("table",p.node);q=n.length;while(q--){r=n[q];t.setAttrib(r,"data-mce-style","");if((s=t.getAttrib(r,"width"))){t.setStyle(r,"width",s);t.setAttrib(r,"width","")}if((s=t.getAttrib(r,"height"))){t.setStyle(r,"height",s);t.setAttrib(r,"height","")}}});g.onNodeChange.add(function(q,o,s){var r;s=q.selection.getStart();r=q.dom.getParent(s,"td,th,caption");o.setActive("table",s.nodeName==="TABLE"||!!r);if(r&&r.nodeName==="CAPTION"){r=0}o.setDisabled("delete_table",!r);o.setDisabled("delete_col",!r);o.setDisabled("delete_table",!r);o.setDisabled("delete_row",!r);o.setDisabled("col_after",!r);o.setDisabled("col_before",!r);o.setDisabled("row_after",!r);o.setDisabled("row_before",!r);o.setDisabled("row_props",!r);o.setDisabled("cell_props",!r);o.setDisabled("split_cells",!r);o.setDisabled("merge_cells",!r)});g.onInit.add(function(r){var p,t,q=r.dom,u;f=r.windowManager;r.onMouseDown.add(function(w,z){if(z.button!=2){k();t=q.getParent(z.target,"td,th");p=q.getParent(t,"table")}});q.bind(r.getDoc(),"mouseover",function(C){var A,z,B=C.target;if(t&&(u||B!=t)&&(B.nodeName=="TD"||B.nodeName=="TH")){z=q.getParent(B,"table");if(z==p){if(!u){u=l(z);u.setStartCell(t);r.getBody().style.webkitUserSelect="none"}u.setEndCell(B);j=true}A=r.selection.getSel();try{if(A.removeAllRanges){A.removeAllRanges()}else{A.empty()}}catch(w){}C.preventDefault()}});r.onMouseUp.add(function(F,G){var z,B=F.selection,H,I=B.getSel(),w,C,A,E;if(t){if(u){F.getBody().style.webkitUserSelect=""}function D(J,L){var K=new d.dom.TreeWalker(J,J);do{if(J.nodeType==3&&d.trim(J.nodeValue).length!=0){if(L){z.setStart(J,0)}else{z.setEnd(J,J.nodeValue.length)}return}if(J.nodeName=="BR"){if(L){z.setStartBefore(J)}else{z.setEndBefore(J)}return}}while(J=(L?K.next():K.prev()))}H=q.select("td.mceSelected,th.mceSelected");if(H.length>0){z=q.createRng();C=H[0];E=H[H.length-1];z.setStartBefore(C);z.setEndAfter(C);D(C,1);w=new d.dom.TreeWalker(C,q.getParent(H[0],"table"));do{if(C.nodeName=="TD"||C.nodeName=="TH"){if(!q.hasClass(C,"mceSelected")){break}A=C}}while(C=w.next());D(A);B.setRng(z)}F.nodeChanged();t=u=p=null}});r.onKeyUp.add(function(w,z){k()});r.onKeyDown.add(function(w,z){n(w)});r.onMouseDown.add(function(w,z){if(z.button!=2){n(w)}});function o(D,z,A,F){var B=3,G=D.dom.getParent(z.startContainer,"TABLE"),C,w,E;if(G){C=G.parentNode}w=z.startContainer.nodeType==B&&z.startOffset==0&&z.endOffset==0&&F&&(A.nodeName=="TR"||A==C);E=(A.nodeName=="TD"||A.nodeName=="TH")&&!F;return w||E}function n(A){if(!d.isWebKit){return}var z=A.selection.getRng();var C=A.selection.getNode();var B=A.dom.getParent(z.startContainer,"TD,TH");if(!o(A,z,C,B)){return}if(!B){B=C}var w=B.lastChild;while(w.lastChild){w=w.lastChild}z.setEnd(w,w.nodeValue.length);A.selection.setRng(z)}r.plugins.table.fixTableCellSelection=n;if(r&&r.plugins.contextmenu){r.plugins.contextmenu.onContextMenu.add(function(A,w,C){var D,B=r.selection,z=B.getNode()||r.getBody();if(r.dom.getParent(C,"td")||r.dom.getParent(C,"th")||r.dom.select("td.mceSelected,th.mceSelected").length){w.removeAll();if(z.nodeName=="A"&&!r.dom.getAttrib(z,"name")){w.add({title:"advanced.link_desc",icon:"link",cmd:r.plugins.advlink?"mceAdvLink":"mceLink",ui:true});w.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"});w.addSeparator()}if(z.nodeName=="IMG"&&z.className.indexOf("mceItem")==-1){w.add({title:"advanced.image_desc",icon:"image",cmd:r.plugins.advimage?"mceAdvImage":"mceImage",ui:true});w.addSeparator()}w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable",value:{action:"insert"}});w.add({title:"table.props_desc",icon:"table_props",cmd:"mceInsertTable"});w.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});w.addSeparator();D=w.addMenu({title:"table.cell"});D.add({title:"table.cell_desc",icon:"cell_props",cmd:"mceTableCellProps"});D.add({title:"table.split_cells_desc",icon:"split_cells",cmd:"mceTableSplitCells"});D.add({title:"table.merge_cells_desc",icon:"merge_cells",cmd:"mceTableMergeCells"});D=w.addMenu({title:"table.row"});D.add({title:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});D.add({title:"table.row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});D.add({title:"table.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});D.add({title:"table.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});D.addSeparator();D.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});D.add({title:"table.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});D.add({title:"table.paste_row_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!m);D.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfter"}).setDisabled(!m);D=w.addMenu({title:"table.col"});D.add({title:"table.col_before_desc",icon:"col_before",cmd:"mceTableInsertColBefore"});D.add({title:"table.col_after_desc",icon:"col_after",cmd:"mceTableInsertColAfter"});D.add({title:"table.delete_col_desc",icon:"delete_col",cmd:"mceTableDeleteCol"})}else{w.add({title:"table.desc",icon:"table",cmd:"mceInsertTable"})}})}if(d.isWebKit){function v(C,N){var L=d.VK;var Q=N.keyCode;function O(Y,U,S){var T=Y?"previousSibling":"nextSibling";var Z=C.dom.getParent(U,"tr");var X=Z[T];if(X){z(C,U,X,Y);d.dom.Event.cancel(S);return true}else{var aa=C.dom.getParent(Z,"table");var W=Z.parentNode;var R=W.nodeName.toLowerCase();if(R==="tbody"||R===(Y?"tfoot":"thead")){var V=w(Y,aa,W,"tbody");if(V!==null){return K(Y,V,U,S)}}return M(Y,Z,T,aa,S)}}function w(V,T,U,X){var S=C.dom.select(">"+X,T);var R=S.indexOf(U);if(V&&R===0||!V&&R===S.length-1){return B(V,T)}else{if(R===-1){var W=U.tagName.toLowerCase()==="thead"?0:S.length-1;return S[W]}else{return S[R+(V?-1:1)]}}}function B(U,T){var S=U?"thead":"tfoot";var R=C.dom.select(">"+S,T);return R.length!==0?R[0]:null}function K(V,T,S,U){var R=J(T,V);R&&z(C,S,R,V);d.dom.Event.cancel(U);return true}function M(Y,U,R,X,W){var S=X[R];if(S){F(S);return true}else{var V=C.dom.getParent(X,"td,th");if(V){return O(Y,V,W)}else{var T=J(U,!Y);F(T);return d.dom.Event.cancel(W)}}}function J(S,R){return S&&S[R?"lastChild":"firstChild"]}function F(R){C.selection.setCursorLocation(R,0)}function A(){return Q==L.UP||Q==L.DOWN}function D(R){var T=R.selection.getNode();var S=R.dom.getParent(T,"tr");return S!==null}function P(S){var R=0;var T=S;while(T.previousSibling){T=T.previousSibling;R=R+a(T,"colspan")}return R}function E(T,R){var U=0;var S=0;e(T.children,function(V,W){U=U+a(V,"colspan");S=W;if(U>R){return false}});return S}function z(T,W,Y,V){var X=P(T.dom.getParent(W,"td,th"));var S=E(Y,X);var R=Y.childNodes[S];var U=J(R,V);F(U||R)}function H(R){var T=C.selection.getNode();var U=C.dom.getParent(T,"td,th");var S=C.dom.getParent(R,"td,th");return U&&U!==S&&I(U,S)}function I(S,R){return C.dom.getParent(S,"TABLE")===C.dom.getParent(R,"TABLE")}if(A()&&D(C)){var G=C.selection.getNode();setTimeout(function(){if(H(G)){O(!N.shiftKey&&Q===L.UP,G,N)}},0)}}r.onKeyDown.add(v)}if(!d.isIE){function s(){var w;for(w=r.getBody().lastChild;w&&w.nodeType==3&&!w.nodeValue.length;w=w.previousSibling){}if(w&&w.nodeName=="TABLE"){r.dom.add(r.getBody(),"p",null,'
    ')}}if(d.isGecko){r.onKeyDown.add(function(z,B){var w,A,C=z.dom;if(B.keyCode==37||B.keyCode==38){w=z.selection.getRng();A=C.getParent(w.startContainer,"table");if(A&&z.getBody().firstChild==A){if(c(w,A)){w=C.createRng();w.setStartBefore(A);w.setEndBefore(A);z.selection.setRng(w);B.preventDefault()}}}})}r.onKeyUp.add(s);r.onSetContent.add(s);r.onVisualAid.add(s);r.onPreProcess.add(function(w,A){var z=A.node.lastChild;if(z&&z.childNodes.length==1&&z.firstChild.nodeName=="BR"){w.dom.remove(z)}});s();r.startContent=r.getContent({format:"raw"})}});e({mceTableSplitCells:function(n){n.split()},mceTableMergeCells:function(o){var p,q,n;n=g.dom.getParent(g.selection.getNode(),"th,td");if(n){p=n.rowSpan;q=n.colSpan}if(!g.dom.select("td.mceSelected,th.mceSelected").length){f.open({url:h+"/merge_cells.htm",width:240+parseInt(g.getLang("table.merge_cells_delta_width",0)),height:110+parseInt(g.getLang("table.merge_cells_delta_height",0)),inline:1},{rows:p,cols:q,onaction:function(r){o.merge(n,r.cols,r.rows)},plugin_url:h})}else{o.merge()}},mceTableInsertRowBefore:function(n){n.insertRow(true)},mceTableInsertRowAfter:function(n){n.insertRow()},mceTableInsertColBefore:function(n){n.insertCol(true)},mceTableInsertColAfter:function(n){n.insertCol()},mceTableDeleteCol:function(n){n.deleteCols()},mceTableDeleteRow:function(n){n.deleteRows()},mceTableCutRow:function(n){m=n.cutRows()},mceTableCopyRow:function(n){m=n.copyRows()},mceTablePasteRowBefore:function(n){n.pasteRows(m,true)},mceTablePasteRowAfter:function(n){n.pasteRows(m)},mceTableDelete:function(n){n.deleteTable()}},function(o,n){g.addCommand(n,function(){var p=l();if(p){o(p);g.execCommand("mceRepaint");k()}})});e({mceInsertTable:function(n){f.open({url:h+"/table.htm",width:400+parseInt(g.getLang("table.table_delta_width",0)),height:320+parseInt(g.getLang("table.table_delta_height",0)),inline:1},{plugin_url:h,action:n?n.action:0})},mceTableRowProps:function(){f.open({url:h+"/row.htm",width:400+parseInt(g.getLang("table.rowprops_delta_width",0)),height:295+parseInt(g.getLang("table.rowprops_delta_height",0)),inline:1},{plugin_url:h})},mceTableCellProps:function(){f.open({url:h+"/cell.htm",width:400+parseInt(g.getLang("table.cellprops_delta_width",0)),height:295+parseInt(g.getLang("table.cellprops_delta_height",0)),inline:1},{plugin_url:h})}},function(o,n){g.addCommand(n,function(p,q){o(q)})})}});d.PluginManager.add("table",d.plugins.TablePlugin)})(tinymce); \ No newline at end of file diff --git a/js/tiny_mce/plugins/table/editor_plugin_src.js b/js/tiny_mce/plugins/table/editor_plugin_src.js index 2260f34a..8170e4ed 100644 --- a/js/tiny_mce/plugins/table/editor_plugin_src.js +++ b/js/tiny_mce/plugins/table/editor_plugin_src.js @@ -1,1118 +1,1408 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var each = tinymce.each; - - /** - * Table Grid class. - */ - function TableGrid(table, dom, selection) { - var grid, startPos, endPos, selectedCell; - - buildGrid(); - selectedCell = dom.getParent(selection.getStart(), 'th,td'); - if (selectedCell) { - startPos = getPos(selectedCell); - endPos = findEndPos(); - selectedCell = getCell(startPos.x, startPos.y); - } - - function buildGrid() { - var startY = 0; - - grid = []; - - each(['thead', 'tbody', 'tfoot'], function(part) { - var rows = dom.select(part + ' tr', table); - - each(rows, function(tr, y) { - y += startY; - - each(dom.select('td,th', tr), function(td, x) { - var x2, y2, rowspan, colspan; - - // Skip over existing cells produced by rowspan - if (grid[y]) { - while (grid[y][x]) - x++; - } - - // Get col/rowspan from cell - rowspan = getSpanVal(td, 'rowspan'); - colspan = getSpanVal(td, 'colspan'); - - // Fill out rowspan/colspan right and down - for (y2 = y; y2 < y + rowspan; y2++) { - if (!grid[y2]) - grid[y2] = []; - - for (x2 = x; x2 < x + colspan; x2++) { - grid[y2][x2] = { - part : part, - real : y2 == y && x2 == x, - elm : td, - rowspan : rowspan, - colspan : colspan - }; - } - } - }); - }); - - startY += rows.length; - }); - }; - - function getCell(x, y) { - var row; - - row = grid[y]; - if (row) - return row[x]; - }; - - function getSpanVal(td, name) { - return parseInt(td.getAttribute(name) || 1); - }; - - function isCellSelected(cell) { - return dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell; - }; - - function getSelectedRows() { - var rows = []; - - each(table.rows, function(row) { - each(row.cells, function(cell) { - if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) { - rows.push(row); - return false; - } - }); - }); - - return rows; - }; - - function deleteTable() { - var rng = dom.createRng(); - - rng.setStartAfter(table); - rng.setEndAfter(table); - - selection.setRng(rng); - - dom.remove(table); - }; - - function cloneCell(cell) { - var formatNode; - - // Clone formats - tinymce.walk(cell, function(node) { - var curNode; - - if (node.nodeType == 3) { - each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { - node = node.cloneNode(false); - - if (!formatNode) - formatNode = curNode = node; - else if (curNode) - curNode.appendChild(node); - - curNode = node; - }); - - // Add something to the inner node - if (curNode) - curNode.innerHTML = tinymce.isIE ? ' ' : '
    '; - - return false; - } - }, 'childNodes'); - - cell = cell.cloneNode(false); - cell.rowSpan = cell.colSpan = 1; - - if (formatNode) { - cell.appendChild(formatNode); - } else { - if (!tinymce.isIE) - cell.innerHTML = '
    '; - } - - return cell; - }; - - function cleanup() { - var rng = dom.createRng(); - - // Empty rows - each(dom.select('tr', table), function(tr) { - if (tr.cells.length == 0) - dom.remove(tr); - }); - - // Empty table - if (dom.select('tr', table).length == 0) { - rng.setStartAfter(table); - rng.setEndAfter(table); - selection.setRng(rng); - dom.remove(table); - return; - } - - // Empty header/body/footer - each(dom.select('thead,tbody,tfoot', table), function(part) { - if (part.rows.length == 0) - dom.remove(part); - }); - - // Restore selection to start position if it still exists - buildGrid(); - - // Restore the selection to the closest table position - row = grid[Math.min(grid.length - 1, startPos.y)]; - if (row) { - selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); - selection.collapse(true); - } - }; - - function fillLeftDown(x, y, rows, cols) { - var tr, x2, r, c, cell; - - tr = grid[y][x].elm.parentNode; - for (r = 1; r <= rows; r++) { - tr = dom.getNext(tr, 'tr'); - - if (tr) { - // Loop left to find real cell - for (x2 = x; x2 >= 0; x2--) { - cell = grid[y + r][x2].elm; - - if (cell.parentNode == tr) { - // Append clones after - for (c = 1; c <= cols; c++) - dom.insertAfter(cloneCell(cell), cell); - - break; - } - } - - if (x2 == -1) { - // Insert nodes before first cell - for (c = 1; c <= cols; c++) - tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); - } - } - } - }; - - function split() { - each(grid, function(row, y) { - each(row, function(cell, x) { - var colSpan, rowSpan, newCell, i; - - if (isCellSelected(cell)) { - cell = cell.elm; - colSpan = getSpanVal(cell, 'colspan'); - rowSpan = getSpanVal(cell, 'rowspan'); - - if (colSpan > 1 || rowSpan > 1) { - cell.colSpan = cell.rowSpan = 1; - - // Insert cells right - for (i = 0; i < colSpan - 1; i++) - dom.insertAfter(cloneCell(cell), cell); - - fillLeftDown(x, y, rowSpan - 1, colSpan); - } - } - }); - }); - }; - - function merge(cell, cols, rows) { - var startX, startY, endX, endY, x, y, startCell, endCell, cell, children; - - // Use specified cell and cols/rows - if (cell) { - pos = getPos(cell); - startX = pos.x; - startY = pos.y; - endX = startX + (cols - 1); - endY = startY + (rows - 1); - } else { - // Use selection - startX = startPos.x; - startY = startPos.y; - endX = endPos.x; - endY = endPos.y; - } - - // Find start/end cells - startCell = getCell(startX, startY); - endCell = getCell(endX, endY); - - // Check if the cells exists and if they are of the same part for example tbody = tbody - if (startCell && endCell && startCell.part == endCell.part) { - // Split and rebuild grid - split(); - buildGrid(); - - // Set row/col span to start cell - startCell = getCell(startX, startY).elm; - startCell.colSpan = (endX - startX) + 1; - startCell.rowSpan = (endY - startY) + 1; - - // Remove other cells and add it's contents to the start cell - for (y = startY; y <= endY; y++) { - for (x = startX; x <= endX; x++) { - cell = grid[y][x].elm; - - if (cell != startCell) { - // Move children to startCell - children = tinymce.grep(cell.childNodes); - each(children, function(node, i) { - // Jump over last BR element - if (node.nodeName != 'BR' || i != children.length - 1) - startCell.appendChild(node); - }); - - // Remove cell - dom.remove(cell); - } - } - } - - // Remove empty rows etc and restore caret location - cleanup(); - } - }; - - function insertRow(before) { - var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell; - - // Find first/last row - each(grid, function(row, y) { - each(row, function(cell, x) { - if (isCellSelected(cell)) { - cell = cell.elm; - rowElm = cell.parentNode; - newRow = rowElm.cloneNode(false); - posY = y; - - if (before) - return false; - } - }); - - if (before) - return !posY; - }); - - for (x = 0; x < grid[0].length; x++) { - cell = grid[posY][x].elm; - - if (cell != lastCell) { - if (!before) { - rowSpan = getSpanVal(cell, 'rowspan'); - if (rowSpan > 1) { - cell.rowSpan = rowSpan + 1; - continue; - } - } else { - // Check if cell above can be expanded - if (posY > 0 && grid[posY - 1][x]) { - otherCell = grid[posY - 1][x].elm; - rowSpan = getSpanVal(otherCell, 'rowspan'); - if (rowSpan > 1) { - otherCell.rowSpan = rowSpan + 1; - continue; - } - } - } - - // Insert new cell into new row - newCell = cloneCell(cell) - newCell.colSpan = cell.colSpan; - newRow.appendChild(newCell); - - lastCell = cell; - } - } - - if (newRow.hasChildNodes()) { - if (!before) - dom.insertAfter(newRow, rowElm); - else - rowElm.parentNode.insertBefore(newRow, rowElm); - } - }; - - function insertCol(before) { - var posX, lastCell; - - // Find first/last column - each(grid, function(row, y) { - each(row, function(cell, x) { - if (isCellSelected(cell)) { - posX = x; - - if (before) - return false; - } - }); - - if (before) - return !posX; - }); - - each(grid, function(row, y) { - var cell = row[posX].elm, rowSpan, colSpan; - - if (cell != lastCell) { - colSpan = getSpanVal(cell, 'colspan'); - rowSpan = getSpanVal(cell, 'rowspan'); - - if (colSpan == 1) { - if (!before) { - dom.insertAfter(cloneCell(cell), cell); - fillLeftDown(posX, y, rowSpan - 1, colSpan); - } else { - cell.parentNode.insertBefore(cloneCell(cell), cell); - fillLeftDown(posX, y, rowSpan - 1, colSpan); - } - } else - cell.colSpan++; - - lastCell = cell; - } - }); - }; - - function deleteCols() { - var cols = []; - - // Get selected column indexes - each(grid, function(row, y) { - each(row, function(cell, x) { - if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) { - each(grid, function(row) { - var cell = row[x].elm, colSpan; - - colSpan = getSpanVal(cell, 'colspan'); - - if (colSpan > 1) - cell.colSpan = colSpan - 1; - else - dom.remove(cell); - }); - - cols.push(x); - } - }); - }); - - cleanup(); - }; - - function deleteRows() { - var rows; - - function deleteRow(tr) { - var nextTr, pos, lastCell; - - nextTr = dom.getNext(tr, 'tr'); - - // Move down row spanned cells - each(tr.cells, function(cell) { - var rowSpan = getSpanVal(cell, 'rowspan'); - - if (rowSpan > 1) { - cell.rowSpan = rowSpan - 1; - pos = getPos(cell); - fillLeftDown(pos.x, pos.y, 1, 1); - } - }); - - // Delete cells - pos = getPos(tr.cells[0]); - each(grid[pos.y], function(cell) { - var rowSpan; - - cell = cell.elm; - - if (cell != lastCell) { - rowSpan = getSpanVal(cell, 'rowspan'); - - if (rowSpan <= 1) - dom.remove(cell); - else - cell.rowSpan = rowSpan - 1; - - lastCell = cell; - } - }); - }; - - // Get selected rows and move selection out of scope - rows = getSelectedRows(); - - // Delete all selected rows - each(rows.reverse(), function(tr) { - deleteRow(tr); - }); - - cleanup(); - }; - - function cutRows() { - var rows = getSelectedRows(); - - dom.remove(rows); - cleanup(); - - return rows; - }; - - function copyRows() { - var rows = getSelectedRows(); - - each(rows, function(row, i) { - rows[i] = row.cloneNode(true); - }); - - return rows; - }; - - function pasteRows(rows, before) { - var selectedRows = getSelectedRows(), - targetRow = selectedRows[before ? 0 : selectedRows.length - 1], - targetCellCount = targetRow.cells.length; - - // Calc target cell count - each(grid, function(row) { - var match; - - targetCellCount = 0; - each(row, function(cell, x) { - if (cell.real) - targetCellCount += cell.colspan; - - if (cell.elm.parentNode == targetRow) - match = 1; - }); - - if (match) - return false; - }); - - if (!before) - rows.reverse(); - - each(rows, function(row) { - var cellCount = row.cells.length, cell; - - // Remove col/rowspans - for (i = 0; i < cellCount; i++) { - cell = row.cells[i]; - cell.colSpan = cell.rowSpan = 1; - } - - // Needs more cells - for (i = cellCount; i < targetCellCount; i++) - row.appendChild(cloneCell(row.cells[cellCount - 1])); - - // Needs less cells - for (i = targetCellCount; i < cellCount; i++) - dom.remove(row.cells[i]); - - // Add before/after - if (before) - targetRow.parentNode.insertBefore(row, targetRow); - else - dom.insertAfter(row, targetRow); - }); - }; - - function getPos(target) { - var pos; - - each(grid, function(row, y) { - each(row, function(cell, x) { - if (cell.elm == target) { - pos = {x : x, y : y}; - return false; - } - }); - - return !pos; - }); - - return pos; - }; - - function setStartCell(cell) { - startPos = getPos(cell); - }; - - function findEndPos() { - var pos, maxX, maxY; - - maxX = maxY = 0; - - each(grid, function(row, y) { - each(row, function(cell, x) { - var colSpan, rowSpan; - - if (isCellSelected(cell)) { - cell = grid[y][x]; - - if (x > maxX) - maxX = x; - - if (y > maxY) - maxY = y; - - if (cell.real) { - colSpan = cell.colspan - 1; - rowSpan = cell.rowspan - 1; - - if (colSpan) { - if (x + colSpan > maxX) - maxX = x + colSpan; - } - - if (rowSpan) { - if (y + rowSpan > maxY) - maxY = y + rowSpan; - } - } - } - }); - }); - - return {x : maxX, y : maxY}; - }; - - function setEndCell(cell) { - var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan; - - endPos = getPos(cell); - - if (startPos && endPos) { - // Get start/end positions - startX = Math.min(startPos.x, endPos.x); - startY = Math.min(startPos.y, endPos.y); - endX = Math.max(startPos.x, endPos.x); - endY = Math.max(startPos.y, endPos.y); - - // Expand end positon to include spans - maxX = endX; - maxY = endY; - - // Expand startX - for (y = startY; y <= maxY; y++) { - cell = grid[y][startX]; - - if (!cell.real) { - if (startX - (cell.colspan - 1) < startX) - startX -= cell.colspan - 1; - } - } - - // Expand startY - for (x = startX; x <= maxX; x++) { - cell = grid[startY][x]; - - if (!cell.real) { - if (startY - (cell.rowspan - 1) < startY) - startY -= cell.rowspan - 1; - } - } - - // Find max X, Y - for (y = startY; y <= endY; y++) { - for (x = startX; x <= endX; x++) { - cell = grid[y][x]; - - if (cell.real) { - colSpan = cell.colspan - 1; - rowSpan = cell.rowspan - 1; - - if (colSpan) { - if (x + colSpan > maxX) - maxX = x + colSpan; - } - - if (rowSpan) { - if (y + rowSpan > maxY) - maxY = y + rowSpan; - } - } - } - } - - // Remove current selection - dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); - - // Add new selection - for (y = startY; y <= maxY; y++) { - for (x = startX; x <= maxX; x++) - dom.addClass(grid[y][x].elm, 'mceSelected'); - } - } - }; - - // Expose to public - tinymce.extend(this, { - deleteTable : deleteTable, - split : split, - merge : merge, - insertRow : insertRow, - insertCol : insertCol, - deleteCols : deleteCols, - deleteRows : deleteRows, - cutRows : cutRows, - copyRows : copyRows, - pasteRows : pasteRows, - getPos : getPos, - setStartCell : setStartCell, - setEndCell : setEndCell - }); - }; - - tinymce.create('tinymce.plugins.TablePlugin', { - init : function(ed, url) { - var winMan, clipboardRows; - - function createTableGrid(node) { - var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table'); - - if (tblElm) - return new TableGrid(tblElm, ed.dom, selection); - }; - - function cleanup() { - // Restore selection possibilities - ed.getBody().style.webkitUserSelect = ''; - ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); - }; - - // Register buttons - each([ - ['table', 'table.desc', 'mceInsertTable', true], - ['delete_table', 'table.del', 'mceTableDelete'], - ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'], - ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'], - ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'], - ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'], - ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'], - ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'], - ['row_props', 'table.row_desc', 'mceTableRowProps', true], - ['cell_props', 'table.cell_desc', 'mceTableCellProps', true], - ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true], - ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] - ], function(c) { - ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); - }); - - // Select whole table is a table border is clicked - if (!tinymce.isIE) { - ed.onClick.add(function(ed, e) { - e = e.target; - - if (e.nodeName === 'TABLE') - ed.selection.select(e); - }); - } - - // Handle node change updates - ed.onNodeChange.add(function(ed, cm, n) { - var p; - - n = ed.selection.getStart(); - p = ed.dom.getParent(n, 'td,th,caption'); - cm.setActive('table', n.nodeName === 'TABLE' || !!p); - - // Disable table tools if we are in caption - if (p && p.nodeName === 'CAPTION') - p = 0; - - cm.setDisabled('delete_table', !p); - cm.setDisabled('delete_col', !p); - cm.setDisabled('delete_table', !p); - cm.setDisabled('delete_row', !p); - cm.setDisabled('col_after', !p); - cm.setDisabled('col_before', !p); - cm.setDisabled('row_after', !p); - cm.setDisabled('row_before', !p); - cm.setDisabled('row_props', !p); - cm.setDisabled('cell_props', !p); - cm.setDisabled('split_cells', !p); - cm.setDisabled('merge_cells', !p); - }); - - ed.onInit.add(function(ed) { - var startTable, startCell, dom = ed.dom, tableGrid; - - winMan = ed.windowManager; - - // Add cell selection logic - ed.onMouseDown.add(function(ed, e) { - if (e.button != 2) { - cleanup(); - - startCell = dom.getParent(e.target, 'td,th'); - startTable = dom.getParent(startCell, 'table'); - } - }); - - dom.bind(ed.getDoc(), 'mouseover', function(e) { - var sel, table, target = e.target; - - if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { - table = dom.getParent(target, 'table'); - if (table == startTable) { - if (!tableGrid) { - tableGrid = createTableGrid(table); - tableGrid.setStartCell(startCell); - - ed.getBody().style.webkitUserSelect = 'none'; - } - - tableGrid.setEndCell(target); - } - - // Remove current selection - sel = ed.selection.getSel(); - - if (sel.removeAllRanges) - sel.removeAllRanges(); - else - sel.empty(); - - e.preventDefault(); - } - }); - - ed.onMouseUp.add(function(ed, e) { - var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode; - - // Move selection to startCell - if (startCell) { - if (tableGrid) - ed.getBody().style.webkitUserSelect = ''; - - function setPoint(node, start) { - var walker = new tinymce.dom.TreeWalker(node, node); - - do { - // Text node - if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { - if (start) - rng.setStart(node, 0); - else - rng.setEnd(node, node.nodeValue.length); - - return; - } - - // BR element - if (node.nodeName == 'BR') { - if (start) - rng.setStartBefore(node); - else - rng.setEndBefore(node); - - return; - } - } while (node = (start ? walker.next() : walker.prev())); - }; - - // Try to expand text selection as much as we can only Gecko supports cell selection - selectedCells = dom.select('td.mceSelected,th.mceSelected'); - if (selectedCells.length > 0) { - rng = dom.createRng(); - node = selectedCells[0]; - endNode = selectedCells[selectedCells.length - 1]; - - setPoint(node, 1); - walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table')); - - do { - if (node.nodeName == 'TD' || node.nodeName == 'TH') { - if (!dom.hasClass(node, 'mceSelected')) - break; - - lastNode = node; - } - } while (node = walker.next()); - - setPoint(lastNode); - - sel.setRng(rng); - } - - ed.nodeChanged(); - startCell = tableGrid = startTable = null; - } - }); - - ed.onKeyUp.add(function(ed, e) { - cleanup(); - }); - - // Add context menu - if (ed && ed.plugins.contextmenu) { - ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) { - var sm, se = ed.selection, el = se.getNode() || ed.getBody(); - - if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th')) { - m.removeAll(); - - if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) { - m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); - m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); - m.addSeparator(); - } - - if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) { - m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); - m.addSeparator(); - } - - m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}}); - m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'}); - m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'}); - m.addSeparator(); - - // Cell menu - sm = m.addMenu({title : 'table.cell'}); - sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'}); - sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'}); - sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'}); - - // Row menu - sm = m.addMenu({title : 'table.row'}); - sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'}); - sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'}); - sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'}); - sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'}); - sm.addSeparator(); - sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'}); - sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'}); - sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows); - sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows); - - // Column menu - sm = m.addMenu({title : 'table.col'}); - sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'}); - sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'}); - sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'}); - } else - m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'}); - }); - } - - // Fixes an issue on Gecko where it's impossible to place the caret behind a table - // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled - if (!tinymce.isIE) { - function fixTableCaretPos() { - var last; - - // Skip empty text nodes form the end - for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ; - - if (last && last.nodeName == 'TABLE') - ed.dom.add(ed.getBody(), 'p', null, '
    '); - }; - - // Fixes an bug where it's impossible to place the caret before a table in Gecko - // this fix solves it by detecting when the caret is at the beginning of such a table - // and then manually moves the caret infront of the table - if (tinymce.isGecko) { - ed.onKeyDown.add(function(ed, e) { - var rng, table, dom = ed.dom; - - // On gecko it's not possible to place the caret before a table - if (e.keyCode == 37 || e.keyCode == 38) { - rng = ed.selection.getRng(); - table = dom.getParent(rng.startContainer, 'table'); - - if (table && ed.getBody().firstChild == table) { - if (isAtStart(rng, table)) { - rng = dom.createRng(); - - rng.setStartBefore(table); - rng.setEndBefore(table); - - ed.selection.setRng(rng); - - e.preventDefault(); - } - } - } - }); - } - - ed.onKeyUp.add(fixTableCaretPos); - ed.onSetContent.add(fixTableCaretPos); - ed.onVisualAid.add(fixTableCaretPos); - - ed.onPreProcess.add(function(ed, o) { - var last = o.node.lastChild; - - if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR') - ed.dom.remove(last); - }); - - fixTableCaretPos(); - } - }); - - // Register action commands - each({ - mceTableSplitCells : function(grid) { - grid.split(); - }, - - mceTableMergeCells : function(grid) { - var rowSpan, colSpan, cell; - - cell = ed.dom.getParent(ed.selection.getNode(), 'th,td'); - if (cell) { - rowSpan = cell.rowSpan; - colSpan = cell.colSpan; - } - - if (!ed.dom.select('td.mceSelected,th.mceSelected').length) { - winMan.open({ - url : url + '/merge_cells.htm', - width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)), - height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)), - inline : 1 - }, { - rows : rowSpan, - cols : colSpan, - onaction : function(data) { - grid.merge(cell, data.cols, data.rows); - }, - plugin_url : url - }); - } else - grid.merge(); - }, - - mceTableInsertRowBefore : function(grid) { - grid.insertRow(true); - }, - - mceTableInsertRowAfter : function(grid) { - grid.insertRow(); - }, - - mceTableInsertColBefore : function(grid) { - grid.insertCol(true); - }, - - mceTableInsertColAfter : function(grid) { - grid.insertCol(); - }, - - mceTableDeleteCol : function(grid) { - grid.deleteCols(); - }, - - mceTableDeleteRow : function(grid) { - grid.deleteRows(); - }, - - mceTableCutRow : function(grid) { - clipboardRows = grid.cutRows(); - }, - - mceTableCopyRow : function(grid) { - clipboardRows = grid.copyRows(); - }, - - mceTablePasteRowBefore : function(grid) { - grid.pasteRows(clipboardRows, true); - }, - - mceTablePasteRowAfter : function(grid) { - grid.pasteRows(clipboardRows); - }, - - mceTableDelete : function(grid) { - grid.deleteTable(); - } - }, function(func, name) { - ed.addCommand(name, function() { - var grid = createTableGrid(); - - if (grid) { - func(grid); - ed.execCommand('mceRepaint'); - cleanup(); - } - }); - }); - - // Register dialog commands - each({ - mceInsertTable : function(val) { - winMan.open({ - url : url + '/table.htm', - width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)), - height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)), - inline : 1 - }, { - plugin_url : url, - action : val ? val.action : 0 - }); - }, - - mceTableRowProps : function() { - winMan.open({ - url : url + '/row.htm', - width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)), - height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)), - inline : 1 - }, { - plugin_url : url - }); - }, - - mceTableCellProps : function() { - winMan.open({ - url : url + '/cell.htm', - width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)), - height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)), - inline : 1 - }, { - plugin_url : url - }); - } - }, function(func, name) { - ed.addCommand(name, function(ui, val) { - func(val); - }); - }); - } - }); - - // Register plugin - tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); -})(tinymce); \ No newline at end of file +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var each = tinymce.each; + + // Checks if the selection/caret is at the start of the specified block element + function isAtStart(rng, par) { + var doc = par.ownerDocument, rng2 = doc.createRange(), elm; + + rng2.setStartBefore(par); + rng2.setEnd(rng.endContainer, rng.endOffset); + + elm = doc.createElement('body'); + elm.appendChild(rng2.cloneContents()); + + // Check for text characters of other elements that should be treated as content + return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0; + }; + + function getSpanVal(td, name) { + return parseInt(td.getAttribute(name) || 1); + } + + /** + * Table Grid class. + */ + function TableGrid(table, dom, selection) { + var grid, startPos, endPos, selectedCell; + + buildGrid(); + selectedCell = dom.getParent(selection.getStart(), 'th,td'); + if (selectedCell) { + startPos = getPos(selectedCell); + endPos = findEndPos(); + selectedCell = getCell(startPos.x, startPos.y); + } + + function cloneNode(node, children) { + node = node.cloneNode(children); + node.removeAttribute('id'); + + return node; + } + + function buildGrid() { + var startY = 0; + + grid = []; + + each(['thead', 'tbody', 'tfoot'], function(part) { + var rows = dom.select('> ' + part + ' tr', table); + + each(rows, function(tr, y) { + y += startY; + + each(dom.select('> td, > th', tr), function(td, x) { + var x2, y2, rowspan, colspan; + + // Skip over existing cells produced by rowspan + if (grid[y]) { + while (grid[y][x]) + x++; + } + + // Get col/rowspan from cell + rowspan = getSpanVal(td, 'rowspan'); + colspan = getSpanVal(td, 'colspan'); + + // Fill out rowspan/colspan right and down + for (y2 = y; y2 < y + rowspan; y2++) { + if (!grid[y2]) + grid[y2] = []; + + for (x2 = x; x2 < x + colspan; x2++) { + grid[y2][x2] = { + part : part, + real : y2 == y && x2 == x, + elm : td, + rowspan : rowspan, + colspan : colspan + }; + } + } + }); + }); + + startY += rows.length; + }); + }; + + function getCell(x, y) { + var row; + + row = grid[y]; + if (row) + return row[x]; + }; + + function setSpanVal(td, name, val) { + if (td) { + val = parseInt(val); + + if (val === 1) + td.removeAttribute(name, 1); + else + td.setAttribute(name, val, 1); + } + } + + function isCellSelected(cell) { + return cell && (dom.hasClass(cell.elm, 'mceSelected') || cell == selectedCell); + }; + + function getSelectedRows() { + var rows = []; + + each(table.rows, function(row) { + each(row.cells, function(cell) { + if (dom.hasClass(cell, 'mceSelected') || cell == selectedCell.elm) { + rows.push(row); + return false; + } + }); + }); + + return rows; + }; + + function deleteTable() { + var rng = dom.createRng(); + + rng.setStartAfter(table); + rng.setEndAfter(table); + + selection.setRng(rng); + + dom.remove(table); + }; + + function cloneCell(cell) { + var formatNode; + + // Clone formats + tinymce.walk(cell, function(node) { + var curNode; + + if (node.nodeType == 3) { + each(dom.getParents(node.parentNode, null, cell).reverse(), function(node) { + node = cloneNode(node, false); + + if (!formatNode) + formatNode = curNode = node; + else if (curNode) + curNode.appendChild(node); + + curNode = node; + }); + + // Add something to the inner node + if (curNode) + curNode.innerHTML = tinymce.isIE ? ' ' : '
    '; + + return false; + } + }, 'childNodes'); + + cell = cloneNode(cell, false); + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + if (formatNode) { + cell.appendChild(formatNode); + } else { + if (!tinymce.isIE) + cell.innerHTML = '
    '; + } + + return cell; + }; + + function cleanup() { + var rng = dom.createRng(); + + // Empty rows + each(dom.select('tr', table), function(tr) { + if (tr.cells.length == 0) + dom.remove(tr); + }); + + // Empty table + if (dom.select('tr', table).length == 0) { + rng.setStartAfter(table); + rng.setEndAfter(table); + selection.setRng(rng); + dom.remove(table); + return; + } + + // Empty header/body/footer + each(dom.select('thead,tbody,tfoot', table), function(part) { + if (part.rows.length == 0) + dom.remove(part); + }); + + // Restore selection to start position if it still exists + buildGrid(); + + // Restore the selection to the closest table position + row = grid[Math.min(grid.length - 1, startPos.y)]; + if (row) { + selection.select(row[Math.min(row.length - 1, startPos.x)].elm, true); + selection.collapse(true); + } + }; + + function fillLeftDown(x, y, rows, cols) { + var tr, x2, r, c, cell; + + tr = grid[y][x].elm.parentNode; + for (r = 1; r <= rows; r++) { + tr = dom.getNext(tr, 'tr'); + + if (tr) { + // Loop left to find real cell + for (x2 = x; x2 >= 0; x2--) { + cell = grid[y + r][x2].elm; + + if (cell.parentNode == tr) { + // Append clones after + for (c = 1; c <= cols; c++) + dom.insertAfter(cloneCell(cell), cell); + + break; + } + } + + if (x2 == -1) { + // Insert nodes before first cell + for (c = 1; c <= cols; c++) + tr.insertBefore(cloneCell(tr.cells[0]), tr.cells[0]); + } + } + } + }; + + function split() { + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan, newCell, i; + + if (isCellSelected(cell)) { + cell = cell.elm; + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan > 1 || rowSpan > 1) { + setSpanVal(cell, 'rowSpan', 1); + setSpanVal(cell, 'colSpan', 1); + + // Insert cells right + for (i = 0; i < colSpan - 1; i++) + dom.insertAfter(cloneCell(cell), cell); + + fillLeftDown(x, y, rowSpan - 1, colSpan); + } + } + }); + }); + }; + + function merge(cell, cols, rows) { + var startX, startY, endX, endY, x, y, startCell, endCell, cell, children, count; + + // Use specified cell and cols/rows + if (cell) { + pos = getPos(cell); + startX = pos.x; + startY = pos.y; + endX = startX + (cols - 1); + endY = startY + (rows - 1); + } else { + // Use selection + startX = startPos.x; + startY = startPos.y; + endX = endPos.x; + endY = endPos.y; + } + + // Find start/end cells + startCell = getCell(startX, startY); + endCell = getCell(endX, endY); + + // Check if the cells exists and if they are of the same part for example tbody = tbody + if (startCell && endCell && startCell.part == endCell.part) { + // Split and rebuild grid + split(); + buildGrid(); + + // Set row/col span to start cell + startCell = getCell(startX, startY).elm; + setSpanVal(startCell, 'colSpan', (endX - startX) + 1); + setSpanVal(startCell, 'rowSpan', (endY - startY) + 1); + + // Remove other cells and add it's contents to the start cell + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + if (!grid[y] || !grid[y][x]) + continue; + + cell = grid[y][x].elm; + + if (cell != startCell) { + // Move children to startCell + children = tinymce.grep(cell.childNodes); + each(children, function(node) { + startCell.appendChild(node); + }); + + // Remove bogus nodes if there is children in the target cell + if (children.length) { + children = tinymce.grep(startCell.childNodes); + count = 0; + each(children, function(node) { + if (node.nodeName == 'BR' && dom.getAttrib(node, 'data-mce-bogus') && count++ < children.length - 1) + startCell.removeChild(node); + }); + } + + // Remove cell + dom.remove(cell); + } + } + } + + // Remove empty rows etc and restore caret location + cleanup(); + } + }; + + function insertRow(before) { + var posY, cell, lastCell, x, rowElm, newRow, newCell, otherCell, rowSpan; + + // Find first/last row + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + cell = cell.elm; + rowElm = cell.parentNode; + newRow = cloneNode(rowElm, false); + posY = y; + + if (before) + return false; + } + }); + + if (before) + return !posY; + }); + + for (x = 0; x < grid[0].length; x++) { + // Cell not found could be because of an invalid table structure + if (!grid[posY][x]) + continue; + + cell = grid[posY][x].elm; + + if (cell != lastCell) { + if (!before) { + rowSpan = getSpanVal(cell, 'rowspan'); + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan + 1); + continue; + } + } else { + // Check if cell above can be expanded + if (posY > 0 && grid[posY - 1][x]) { + otherCell = grid[posY - 1][x].elm; + rowSpan = getSpanVal(otherCell, 'rowSpan'); + if (rowSpan > 1) { + setSpanVal(otherCell, 'rowSpan', rowSpan + 1); + continue; + } + } + } + + // Insert new cell into new row + newCell = cloneCell(cell); + setSpanVal(newCell, 'colSpan', cell.colSpan); + + newRow.appendChild(newCell); + + lastCell = cell; + } + } + + if (newRow.hasChildNodes()) { + if (!before) + dom.insertAfter(newRow, rowElm); + else + rowElm.parentNode.insertBefore(newRow, rowElm); + } + }; + + function insertCol(before) { + var posX, lastCell; + + // Find first/last column + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell)) { + posX = x; + + if (before) + return false; + } + }); + + if (before) + return !posX; + }); + + each(grid, function(row, y) { + var cell, rowSpan, colSpan; + + if (!row[posX]) + return; + + cell = row[posX].elm; + if (cell != lastCell) { + colSpan = getSpanVal(cell, 'colspan'); + rowSpan = getSpanVal(cell, 'rowspan'); + + if (colSpan == 1) { + if (!before) { + dom.insertAfter(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } else { + cell.parentNode.insertBefore(cloneCell(cell), cell); + fillLeftDown(posX, y, rowSpan - 1, colSpan); + } + } else + setSpanVal(cell, 'colSpan', cell.colSpan + 1); + + lastCell = cell; + } + }); + }; + + function deleteCols() { + var cols = []; + + // Get selected column indexes + each(grid, function(row, y) { + each(row, function(cell, x) { + if (isCellSelected(cell) && tinymce.inArray(cols, x) === -1) { + each(grid, function(row) { + var cell = row[x].elm, colSpan; + + colSpan = getSpanVal(cell, 'colSpan'); + + if (colSpan > 1) + setSpanVal(cell, 'colSpan', colSpan - 1); + else + dom.remove(cell); + }); + + cols.push(x); + } + }); + }); + + cleanup(); + }; + + function deleteRows() { + var rows; + + function deleteRow(tr) { + var nextTr, pos, lastCell; + + nextTr = dom.getNext(tr, 'tr'); + + // Move down row spanned cells + each(tr.cells, function(cell) { + var rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan > 1) { + setSpanVal(cell, 'rowSpan', rowSpan - 1); + pos = getPos(cell); + fillLeftDown(pos.x, pos.y, 1, 1); + } + }); + + // Delete cells + pos = getPos(tr.cells[0]); + each(grid[pos.y], function(cell) { + var rowSpan; + + cell = cell.elm; + + if (cell != lastCell) { + rowSpan = getSpanVal(cell, 'rowSpan'); + + if (rowSpan <= 1) + dom.remove(cell); + else + setSpanVal(cell, 'rowSpan', rowSpan - 1); + + lastCell = cell; + } + }); + }; + + // Get selected rows and move selection out of scope + rows = getSelectedRows(); + + // Delete all selected rows + each(rows.reverse(), function(tr) { + deleteRow(tr); + }); + + cleanup(); + }; + + function cutRows() { + var rows = getSelectedRows(); + + dom.remove(rows); + cleanup(); + + return rows; + }; + + function copyRows() { + var rows = getSelectedRows(); + + each(rows, function(row, i) { + rows[i] = cloneNode(row, true); + }); + + return rows; + }; + + function pasteRows(rows, before) { + var selectedRows = getSelectedRows(), + targetRow = selectedRows[before ? 0 : selectedRows.length - 1], + targetCellCount = targetRow.cells.length; + + // Calc target cell count + each(grid, function(row) { + var match; + + targetCellCount = 0; + each(row, function(cell, x) { + if (cell.real) + targetCellCount += cell.colspan; + + if (cell.elm.parentNode == targetRow) + match = 1; + }); + + if (match) + return false; + }); + + if (!before) + rows.reverse(); + + each(rows, function(row) { + var cellCount = row.cells.length, cell; + + // Remove col/rowspans + for (i = 0; i < cellCount; i++) { + cell = row.cells[i]; + setSpanVal(cell, 'colSpan', 1); + setSpanVal(cell, 'rowSpan', 1); + } + + // Needs more cells + for (i = cellCount; i < targetCellCount; i++) + row.appendChild(cloneCell(row.cells[cellCount - 1])); + + // Needs less cells + for (i = targetCellCount; i < cellCount; i++) + dom.remove(row.cells[i]); + + // Add before/after + if (before) + targetRow.parentNode.insertBefore(row, targetRow); + else + dom.insertAfter(row, targetRow); + }); + }; + + function getPos(target) { + var pos; + + each(grid, function(row, y) { + each(row, function(cell, x) { + if (cell.elm == target) { + pos = {x : x, y : y}; + return false; + } + }); + + return !pos; + }); + + return pos; + }; + + function setStartCell(cell) { + startPos = getPos(cell); + }; + + function findEndPos() { + var pos, maxX, maxY; + + maxX = maxY = 0; + + each(grid, function(row, y) { + each(row, function(cell, x) { + var colSpan, rowSpan; + + if (isCellSelected(cell)) { + cell = grid[y][x]; + + if (x > maxX) + maxX = x; + + if (y > maxY) + maxY = y; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + }); + }); + + return {x : maxX, y : maxY}; + }; + + function setEndCell(cell) { + var startX, startY, endX, endY, maxX, maxY, colSpan, rowSpan; + + endPos = getPos(cell); + + if (startPos && endPos) { + // Get start/end positions + startX = Math.min(startPos.x, endPos.x); + startY = Math.min(startPos.y, endPos.y); + endX = Math.max(startPos.x, endPos.x); + endY = Math.max(startPos.y, endPos.y); + + // Expand end positon to include spans + maxX = endX; + maxY = endY; + + // Expand startX + for (y = startY; y <= maxY; y++) { + cell = grid[y][startX]; + + if (!cell.real) { + if (startX - (cell.colspan - 1) < startX) + startX -= cell.colspan - 1; + } + } + + // Expand startY + for (x = startX; x <= maxX; x++) { + cell = grid[startY][x]; + + if (!cell.real) { + if (startY - (cell.rowspan - 1) < startY) + startY -= cell.rowspan - 1; + } + } + + // Find max X, Y + for (y = startY; y <= endY; y++) { + for (x = startX; x <= endX; x++) { + cell = grid[y][x]; + + if (cell.real) { + colSpan = cell.colspan - 1; + rowSpan = cell.rowspan - 1; + + if (colSpan) { + if (x + colSpan > maxX) + maxX = x + colSpan; + } + + if (rowSpan) { + if (y + rowSpan > maxY) + maxY = y + rowSpan; + } + } + } + } + + // Remove current selection + dom.removeClass(dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + + // Add new selection + for (y = startY; y <= maxY; y++) { + for (x = startX; x <= maxX; x++) { + if (grid[y][x]) + dom.addClass(grid[y][x].elm, 'mceSelected'); + } + } + } + }; + + // Expose to public + tinymce.extend(this, { + deleteTable : deleteTable, + split : split, + merge : merge, + insertRow : insertRow, + insertCol : insertCol, + deleteCols : deleteCols, + deleteRows : deleteRows, + cutRows : cutRows, + copyRows : copyRows, + pasteRows : pasteRows, + getPos : getPos, + setStartCell : setStartCell, + setEndCell : setEndCell + }); + }; + + tinymce.create('tinymce.plugins.TablePlugin', { + init : function(ed, url) { + var winMan, clipboardRows, hasCellSelection = true; // Might be selected cells on reload + + function createTableGrid(node) { + var selection = ed.selection, tblElm = ed.dom.getParent(node || selection.getNode(), 'table'); + + if (tblElm) + return new TableGrid(tblElm, ed.dom, selection); + }; + + function cleanup() { + // Restore selection possibilities + ed.getBody().style.webkitUserSelect = ''; + + if (hasCellSelection) { + ed.dom.removeClass(ed.dom.select('td.mceSelected,th.mceSelected'), 'mceSelected'); + hasCellSelection = false; + } + }; + + // Register buttons + each([ + ['table', 'table.desc', 'mceInsertTable', true], + ['delete_table', 'table.del', 'mceTableDelete'], + ['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'], + ['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'], + ['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'], + ['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'], + ['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'], + ['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'], + ['row_props', 'table.row_desc', 'mceTableRowProps', true], + ['cell_props', 'table.cell_desc', 'mceTableCellProps', true], + ['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true], + ['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true] + ], function(c) { + ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]}); + }); + + // Select whole table is a table border is clicked + if (!tinymce.isIE) { + ed.onClick.add(function(ed, e) { + e = e.target; + + if (e.nodeName === 'TABLE') { + ed.selection.select(e); + ed.nodeChanged(); + } + }); + } + + ed.onPreProcess.add(function(ed, args) { + var nodes, i, node, dom = ed.dom, value; + + nodes = dom.select('table', args.node); + i = nodes.length; + while (i--) { + node = nodes[i]; + dom.setAttrib(node, 'data-mce-style', ''); + + if ((value = dom.getAttrib(node, 'width'))) { + dom.setStyle(node, 'width', value); + dom.setAttrib(node, 'width', ''); + } + + if ((value = dom.getAttrib(node, 'height'))) { + dom.setStyle(node, 'height', value); + dom.setAttrib(node, 'height', ''); + } + } + }); + + // Handle node change updates + ed.onNodeChange.add(function(ed, cm, n) { + var p; + + n = ed.selection.getStart(); + p = ed.dom.getParent(n, 'td,th,caption'); + cm.setActive('table', n.nodeName === 'TABLE' || !!p); + + // Disable table tools if we are in caption + if (p && p.nodeName === 'CAPTION') + p = 0; + + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_col', !p); + cm.setDisabled('delete_table', !p); + cm.setDisabled('delete_row', !p); + cm.setDisabled('col_after', !p); + cm.setDisabled('col_before', !p); + cm.setDisabled('row_after', !p); + cm.setDisabled('row_before', !p); + cm.setDisabled('row_props', !p); + cm.setDisabled('cell_props', !p); + cm.setDisabled('split_cells', !p); + cm.setDisabled('merge_cells', !p); + }); + + ed.onInit.add(function(ed) { + var startTable, startCell, dom = ed.dom, tableGrid; + + winMan = ed.windowManager; + + // Add cell selection logic + ed.onMouseDown.add(function(ed, e) { + if (e.button != 2) { + cleanup(); + + startCell = dom.getParent(e.target, 'td,th'); + startTable = dom.getParent(startCell, 'table'); + } + }); + + dom.bind(ed.getDoc(), 'mouseover', function(e) { + var sel, table, target = e.target; + + if (startCell && (tableGrid || target != startCell) && (target.nodeName == 'TD' || target.nodeName == 'TH')) { + table = dom.getParent(target, 'table'); + if (table == startTable) { + if (!tableGrid) { + tableGrid = createTableGrid(table); + tableGrid.setStartCell(startCell); + + ed.getBody().style.webkitUserSelect = 'none'; + } + + tableGrid.setEndCell(target); + hasCellSelection = true; + } + + // Remove current selection + sel = ed.selection.getSel(); + + try { + if (sel.removeAllRanges) + sel.removeAllRanges(); + else + sel.empty(); + } catch (ex) { + // IE9 might throw errors here + } + + e.preventDefault(); + } + }); + + ed.onMouseUp.add(function(ed, e) { + var rng, sel = ed.selection, selectedCells, nativeSel = sel.getSel(), walker, node, lastNode, endNode; + + // Move selection to startCell + if (startCell) { + if (tableGrid) + ed.getBody().style.webkitUserSelect = ''; + + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + } + + // Try to expand text selection as much as we can only Gecko supports cell selection + selectedCells = dom.select('td.mceSelected,th.mceSelected'); + if (selectedCells.length > 0) { + rng = dom.createRng(); + node = selectedCells[0]; + endNode = selectedCells[selectedCells.length - 1]; + rng.setStartBefore(node); + rng.setEndAfter(node); + + setPoint(node, 1); + walker = new tinymce.dom.TreeWalker(node, dom.getParent(selectedCells[0], 'table')); + + do { + if (node.nodeName == 'TD' || node.nodeName == 'TH') { + if (!dom.hasClass(node, 'mceSelected')) + break; + + lastNode = node; + } + } while (node = walker.next()); + + setPoint(lastNode); + + sel.setRng(rng); + } + + ed.nodeChanged(); + startCell = tableGrid = startTable = null; + } + }); + + ed.onKeyUp.add(function(ed, e) { + cleanup(); + }); + + ed.onKeyDown.add(function (ed, e) { + fixTableCellSelection(ed); + }); + + ed.onMouseDown.add(function (ed, e) { + if (e.button != 2) { + fixTableCellSelection(ed); + } + }); + function tableCellSelected(ed, rng, n, currentCell) { + // The decision of when a table cell is selected is somewhat involved. The fact that this code is + // required is actually a pointer to the root cause of this bug. A cell is selected when the start + // and end offsets are 0, the start container is a text, and the selection node is either a TR (most cases) + // or the parent of the table (in the case of the selection containing the last cell of a table). + var TEXT_NODE = 3, table = ed.dom.getParent(rng.startContainer, 'TABLE'), + tableParent, allOfCellSelected, tableCellSelection; + if (table) + tableParent = table.parentNode; + allOfCellSelected =rng.startContainer.nodeType == TEXT_NODE && + rng.startOffset == 0 && + rng.endOffset == 0 && + currentCell && + (n.nodeName=="TR" || n==tableParent); + tableCellSelection = (n.nodeName=="TD"||n.nodeName=="TH")&& !currentCell; + return allOfCellSelected || tableCellSelection; + // return false; + } + + // this nasty hack is here to work around some WebKit selection bugs. + function fixTableCellSelection(ed) { + if (!tinymce.isWebKit) + return; + + var rng = ed.selection.getRng(); + var n = ed.selection.getNode(); + var currentCell = ed.dom.getParent(rng.startContainer, 'TD,TH'); + + if (!tableCellSelected(ed, rng, n, currentCell)) + return; + if (!currentCell) { + currentCell=n; + } + + // Get the very last node inside the table cell + var end = currentCell.lastChild; + while (end.lastChild) + end = end.lastChild; + + // Select the entire table cell. Nothing outside of the table cell should be selected. + rng.setEnd(end, end.nodeValue.length); + ed.selection.setRng(rng); + } + ed.plugins.table.fixTableCellSelection=fixTableCellSelection; + + // Add context menu + if (ed && ed.plugins.contextmenu) { + ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) { + var sm, se = ed.selection, el = se.getNode() || ed.getBody(); + + if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th') || ed.dom.select('td.mceSelected,th.mceSelected').length) { + m.removeAll(); + + if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) { + m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true}); + m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'}); + m.addSeparator(); + } + + if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) { + m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true}); + m.addSeparator(); + } + + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', value : {action : 'insert'}}); + m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable'}); + m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete'}); + m.addSeparator(); + + // Cell menu + sm = m.addMenu({title : 'table.cell'}); + sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps'}); + sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells'}); + sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells'}); + + // Row menu + sm = m.addMenu({title : 'table.row'}); + sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps'}); + sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'}); + sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'}); + sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'}); + sm.addSeparator(); + sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'}); + sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'}); + sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'}).setDisabled(!clipboardRows); + sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'}).setDisabled(!clipboardRows); + + // Column menu + sm = m.addMenu({title : 'table.col'}); + sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'}); + sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'}); + sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'}); + } else + m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable'}); + }); + } + + // Fix to allow navigating up and down in a table in WebKit browsers. + if (tinymce.isWebKit) { + function moveSelection(ed, e) { + var VK = tinymce.VK; + var key = e.keyCode; + + function handle(upBool, sourceNode, event) { + var siblingDirection = upBool ? 'previousSibling' : 'nextSibling'; + var currentRow = ed.dom.getParent(sourceNode, 'tr'); + var siblingRow = currentRow[siblingDirection]; + + if (siblingRow) { + moveCursorToRow(ed, sourceNode, siblingRow, upBool); + tinymce.dom.Event.cancel(event); + return true; + } else { + var tableNode = ed.dom.getParent(currentRow, 'table'); + var middleNode = currentRow.parentNode; + var parentNodeName = middleNode.nodeName.toLowerCase(); + if (parentNodeName === 'tbody' || parentNodeName === (upBool ? 'tfoot' : 'thead')) { + var targetParent = getTargetParent(upBool, tableNode, middleNode, 'tbody'); + if (targetParent !== null) { + return moveToRowInTarget(upBool, targetParent, sourceNode, event); + } + } + return escapeTable(upBool, currentRow, siblingDirection, tableNode, event); + } + } + + function getTargetParent(upBool, topNode, secondNode, nodeName) { + var tbodies = ed.dom.select('>' + nodeName, topNode); + var position = tbodies.indexOf(secondNode); + if (upBool && position === 0 || !upBool && position === tbodies.length - 1) { + return getFirstHeadOrFoot(upBool, topNode); + } else if (position === -1) { + var topOrBottom = secondNode.tagName.toLowerCase() === 'thead' ? 0 : tbodies.length - 1; + return tbodies[topOrBottom]; + } else { + return tbodies[position + (upBool ? -1 : 1)]; + } + } + + function getFirstHeadOrFoot(upBool, parent) { + var tagName = upBool ? 'thead' : 'tfoot'; + var headOrFoot = ed.dom.select('>' + tagName, parent); + return headOrFoot.length !== 0 ? headOrFoot[0] : null; + } + + function moveToRowInTarget(upBool, targetParent, sourceNode, event) { + var targetRow = getChildForDirection(targetParent, upBool); + targetRow && moveCursorToRow(ed, sourceNode, targetRow, upBool); + tinymce.dom.Event.cancel(event); + return true; + } + + function escapeTable(upBool, currentRow, siblingDirection, table, event) { + var tableSibling = table[siblingDirection]; + if (tableSibling) { + moveCursorToStartOfElement(tableSibling); + return true; + } else { + var parentCell = ed.dom.getParent(table, 'td,th'); + if (parentCell) { + return handle(upBool, parentCell, event); + } else { + var backUpSibling = getChildForDirection(currentRow, !upBool); + moveCursorToStartOfElement(backUpSibling); + return tinymce.dom.Event.cancel(event); + } + } + } + + function getChildForDirection(parent, up) { + return parent && parent[up ? 'lastChild' : 'firstChild']; + } + + function moveCursorToStartOfElement(n) { + ed.selection.setCursorLocation(n, 0); + } + + function isVerticalMovement() { + return key == VK.UP || key == VK.DOWN; + } + + function isInTable(ed) { + var node = ed.selection.getNode(); + var currentRow = ed.dom.getParent(node, 'tr'); + return currentRow !== null; + } + + function columnIndex(column) { + var colIndex = 0; + var c = column; + while (c.previousSibling) { + c = c.previousSibling; + colIndex = colIndex + getSpanVal(c, "colspan"); + } + return colIndex; + } + + function findColumn(rowElement, columnIndex) { + var c = 0; + var r = 0; + each(rowElement.children, function(cell, i) { + c = c + getSpanVal(cell, "colspan"); + r = i; + if (c > columnIndex) + return false; + }); + return r; + } + + function moveCursorToRow(ed, node, row, upBool) { + var srcColumnIndex = columnIndex(ed.dom.getParent(node, 'td,th')); + var tgtColumnIndex = findColumn(row, srcColumnIndex); + var tgtNode = row.childNodes[tgtColumnIndex]; + var rowCellTarget = getChildForDirection(tgtNode, upBool); + moveCursorToStartOfElement(rowCellTarget || tgtNode); + } + + function shouldFixCaret(preBrowserNode) { + var newNode = ed.selection.getNode(); + var newParent = ed.dom.getParent(newNode, 'td,th'); + var oldParent = ed.dom.getParent(preBrowserNode, 'td,th'); + return newParent && newParent !== oldParent && checkSameParentTable(newParent, oldParent) + } + + function checkSameParentTable(nodeOne, NodeTwo) { + return ed.dom.getParent(nodeOne, 'TABLE') === ed.dom.getParent(NodeTwo, 'TABLE'); + } + + if (isVerticalMovement() && isInTable(ed)) { + var preBrowserNode = ed.selection.getNode(); + setTimeout(function() { + if (shouldFixCaret(preBrowserNode)) { + handle(!e.shiftKey && key === VK.UP, preBrowserNode, e); + } + }, 0); + } + } + + ed.onKeyDown.add(moveSelection); + } + + // Fixes an issue on Gecko where it's impossible to place the caret behind a table + // This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled + if (!tinymce.isIE) { + function fixTableCaretPos() { + var last; + + // Skip empty text nodes form the end + for (last = ed.getBody().lastChild; last && last.nodeType == 3 && !last.nodeValue.length; last = last.previousSibling) ; + + if (last && last.nodeName == 'TABLE') + ed.dom.add(ed.getBody(), 'p', null, '
    '); + }; + + // Fixes an bug where it's impossible to place the caret before a table in Gecko + // this fix solves it by detecting when the caret is at the beginning of such a table + // and then manually moves the caret infront of the table + if (tinymce.isGecko) { + ed.onKeyDown.add(function(ed, e) { + var rng, table, dom = ed.dom; + + // On gecko it's not possible to place the caret before a table + if (e.keyCode == 37 || e.keyCode == 38) { + rng = ed.selection.getRng(); + table = dom.getParent(rng.startContainer, 'table'); + + if (table && ed.getBody().firstChild == table) { + if (isAtStart(rng, table)) { + rng = dom.createRng(); + + rng.setStartBefore(table); + rng.setEndBefore(table); + + ed.selection.setRng(rng); + + e.preventDefault(); + } + } + } + }); + } + + ed.onKeyUp.add(fixTableCaretPos); + ed.onSetContent.add(fixTableCaretPos); + ed.onVisualAid.add(fixTableCaretPos); + + ed.onPreProcess.add(function(ed, o) { + var last = o.node.lastChild; + + if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR') + ed.dom.remove(last); + }); + + fixTableCaretPos(); + ed.startContent = ed.getContent({format : 'raw'}); + } + }); + + // Register action commands + each({ + mceTableSplitCells : function(grid) { + grid.split(); + }, + + mceTableMergeCells : function(grid) { + var rowSpan, colSpan, cell; + + cell = ed.dom.getParent(ed.selection.getNode(), 'th,td'); + if (cell) { + rowSpan = cell.rowSpan; + colSpan = cell.colSpan; + } + + if (!ed.dom.select('td.mceSelected,th.mceSelected').length) { + winMan.open({ + url : url + '/merge_cells.htm', + width : 240 + parseInt(ed.getLang('table.merge_cells_delta_width', 0)), + height : 110 + parseInt(ed.getLang('table.merge_cells_delta_height', 0)), + inline : 1 + }, { + rows : rowSpan, + cols : colSpan, + onaction : function(data) { + grid.merge(cell, data.cols, data.rows); + }, + plugin_url : url + }); + } else + grid.merge(); + }, + + mceTableInsertRowBefore : function(grid) { + grid.insertRow(true); + }, + + mceTableInsertRowAfter : function(grid) { + grid.insertRow(); + }, + + mceTableInsertColBefore : function(grid) { + grid.insertCol(true); + }, + + mceTableInsertColAfter : function(grid) { + grid.insertCol(); + }, + + mceTableDeleteCol : function(grid) { + grid.deleteCols(); + }, + + mceTableDeleteRow : function(grid) { + grid.deleteRows(); + }, + + mceTableCutRow : function(grid) { + clipboardRows = grid.cutRows(); + }, + + mceTableCopyRow : function(grid) { + clipboardRows = grid.copyRows(); + }, + + mceTablePasteRowBefore : function(grid) { + grid.pasteRows(clipboardRows, true); + }, + + mceTablePasteRowAfter : function(grid) { + grid.pasteRows(clipboardRows); + }, + + mceTableDelete : function(grid) { + grid.deleteTable(); + } + }, function(func, name) { + ed.addCommand(name, function() { + var grid = createTableGrid(); + + if (grid) { + func(grid); + ed.execCommand('mceRepaint'); + cleanup(); + } + }); + }); + + // Register dialog commands + each({ + mceInsertTable : function(val) { + winMan.open({ + url : url + '/table.htm', + width : 400 + parseInt(ed.getLang('table.table_delta_width', 0)), + height : 320 + parseInt(ed.getLang('table.table_delta_height', 0)), + inline : 1 + }, { + plugin_url : url, + action : val ? val.action : 0 + }); + }, + + mceTableRowProps : function() { + winMan.open({ + url : url + '/row.htm', + width : 400 + parseInt(ed.getLang('table.rowprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.rowprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }, + + mceTableCellProps : function() { + winMan.open({ + url : url + '/cell.htm', + width : 400 + parseInt(ed.getLang('table.cellprops_delta_width', 0)), + height : 295 + parseInt(ed.getLang('table.cellprops_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + } + }, function(func, name) { + ed.addCommand(name, function(ui, val) { + func(val); + }); + }); + } + }); + + // Register plugin + tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin); +})(tinymce); diff --git a/js/tiny_mce/plugins/table/js/cell.js b/js/tiny_mce/plugins/table/js/cell.js index f2461917..53bdb54b 100644 --- a/js/tiny_mce/plugins/table/js/cell.js +++ b/js/tiny_mce/plugins/table/js/cell.js @@ -1,286 +1,319 @@ -tinyMCEPopup.requireLangPack(); - -var ed; - -function init() { - ed = tinyMCEPopup.editor; - tinyMCEPopup.resizeToInnerSize(); - - document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); - document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); - document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor') - - var inst = ed; - var tdElm = ed.dom.getParent(ed.selection.getStart(), "td,th"); - var formObj = document.forms[0]; - var st = ed.dom.parseStyle(ed.dom.getAttrib(tdElm, "style")); - - // Get table cell data - var celltype = tdElm.nodeName.toLowerCase(); - var align = ed.dom.getAttrib(tdElm, 'align'); - var valign = ed.dom.getAttrib(tdElm, 'valign'); - var width = trimSize(getStyle(tdElm, 'width', 'width')); - var height = trimSize(getStyle(tdElm, 'height', 'height')); - var bordercolor = convertRGBToHex(getStyle(tdElm, 'bordercolor', 'borderLeftColor')); - var bgcolor = convertRGBToHex(getStyle(tdElm, 'bgcolor', 'backgroundColor')); - var className = ed.dom.getAttrib(tdElm, 'class'); - var backgroundimage = getStyle(tdElm, 'background', 'backgroundImage').replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");; - var id = ed.dom.getAttrib(tdElm, 'id'); - var lang = ed.dom.getAttrib(tdElm, 'lang'); - var dir = ed.dom.getAttrib(tdElm, 'dir'); - var scope = ed.dom.getAttrib(tdElm, 'scope'); - - // Setup form - addClassesToList('class', 'table_cell_styles'); - TinyMCE_EditableSelects.init(); - - if (!ed.dom.hasClass(tdElm, 'mceSelected')) { - formObj.bordercolor.value = bordercolor; - formObj.bgcolor.value = bgcolor; - formObj.backgroundimage.value = backgroundimage; - formObj.width.value = width; - formObj.height.value = height; - formObj.id.value = id; - formObj.lang.value = lang; - formObj.style.value = ed.dom.serializeStyle(st); - selectByValue(formObj, 'align', align); - selectByValue(formObj, 'valign', valign); - selectByValue(formObj, 'class', className, true, true); - selectByValue(formObj, 'celltype', celltype); - selectByValue(formObj, 'dir', dir); - selectByValue(formObj, 'scope', scope); - - // Resize some elements - if (isVisible('backgroundimagebrowser')) - document.getElementById('backgroundimage').style.width = '180px'; - - updateColor('bordercolor_pick', 'bordercolor'); - updateColor('bgcolor_pick', 'bgcolor'); - } else - tinyMCEPopup.dom.hide('action'); -} - -function updateAction() { - var el, inst = ed, tdElm, trElm, tableElm, formObj = document.forms[0]; - - tinyMCEPopup.restoreSelection(); - el = ed.selection.getStart(); - tdElm = ed.dom.getParent(el, "td,th"); - trElm = ed.dom.getParent(el, "tr"); - tableElm = ed.dom.getParent(el, "table"); - - // Cell is selected - if (ed.dom.hasClass(tdElm, 'mceSelected')) { - // Update all selected sells - tinymce.each(ed.dom.select('td.mceSelected,th.mceSelected'), function(td) { - updateCell(td); - }); - - ed.addVisual(); - ed.nodeChanged(); - inst.execCommand('mceEndUndoLevel'); - tinyMCEPopup.close(); - return; - } - - ed.execCommand('mceBeginUndoLevel'); - - switch (getSelectValue(formObj, 'action')) { - case "cell": - var celltype = getSelectValue(formObj, 'celltype'); - var scope = getSelectValue(formObj, 'scope'); - - function doUpdate(s) { - if (s) { - updateCell(tdElm); - - ed.addVisual(); - ed.nodeChanged(); - inst.execCommand('mceEndUndoLevel'); - tinyMCEPopup.close(); - } - }; - - if (ed.getParam("accessibility_warnings", 1)) { - if (celltype == "th" && scope == "") - tinyMCEPopup.confirm(ed.getLang('table_dlg.missing_scope', '', true), doUpdate); - else - doUpdate(1); - - return; - } - - updateCell(tdElm); - break; - - case "row": - var cell = trElm.firstChild; - - if (cell.nodeName != "TD" && cell.nodeName != "TH") - cell = nextCell(cell); - - do { - cell = updateCell(cell, true); - } while ((cell = nextCell(cell)) != null); - - break; - - case "all": - var rows = tableElm.getElementsByTagName("tr"); - - for (var i=0; i 0) { - tinymce.each(tableElm.rows, function(tr) { - var i; - - for (i = 0; i < tr.cells.length; i++) { - if (dom.hasClass(tr.cells[i], 'mceSelected')) { - updateRow(tr, true); - return; - } - } - }); - - inst.addVisual(); - inst.nodeChanged(); - inst.execCommand('mceEndUndoLevel'); - tinyMCEPopup.close(); - return; - } - - inst.execCommand('mceBeginUndoLevel'); - - switch (action) { - case "row": - updateRow(trElm); - break; - - case "all": - var rows = tableElm.getElementsByTagName("tr"); - - for (var i=0; i 0) { + tinymce.each(tableElm.rows, function(tr) { + var i; + + for (i = 0; i < tr.cells.length; i++) { + if (dom.hasClass(tr.cells[i], 'mceSelected')) { + updateRow(tr, true); + return; + } + } + }); + + inst.addVisual(); + inst.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + tinyMCEPopup.close(); + return; + } + + switch (action) { + case "row": + updateRow(trElm); + break; + + case "all": + var rows = tableElm.getElementsByTagName("tr"); + + for (var i=0; i colLimit) { - tinyMCEPopup.alert(inst.getLang('table_dlg.col_limit').replace(/\{\$cols\}/g, colLimit)); - return false; - } else if (rowLimit && rows > rowLimit) { - tinyMCEPopup.alert(inst.getLang('table_dlg.row_limit').replace(/\{\$rows\}/g, rowLimit)); - return false; - } else if (cellLimit && cols * rows > cellLimit) { - tinyMCEPopup.alert(inst.getLang('table_dlg.cell_limit').replace(/\{\$cells\}/g, cellLimit)); - return false; - } - - // Update table - if (action == "update") { - inst.execCommand('mceBeginUndoLevel'); - - dom.setAttrib(elm, 'cellPadding', cellpadding, true); - dom.setAttrib(elm, 'cellSpacing', cellspacing, true); - dom.setAttrib(elm, 'border', border); - dom.setAttrib(elm, 'align', align); - dom.setAttrib(elm, 'frame', frame); - dom.setAttrib(elm, 'rules', rules); - dom.setAttrib(elm, 'class', className); - dom.setAttrib(elm, 'style', style); - dom.setAttrib(elm, 'id', id); - dom.setAttrib(elm, 'summary', summary); - dom.setAttrib(elm, 'dir', dir); - dom.setAttrib(elm, 'lang', lang); - - capEl = inst.dom.select('caption', elm)[0]; - - if (capEl && !caption) - capEl.parentNode.removeChild(capEl); - - if (!capEl && caption) { - capEl = elm.ownerDocument.createElement('caption'); - - if (!tinymce.isIE) - capEl.innerHTML = '
    '; - - elm.insertBefore(capEl, elm.firstChild); - } - - if (width && inst.settings.inline_styles) { - dom.setStyle(elm, 'width', width); - dom.setAttrib(elm, 'width', ''); - } else { - dom.setAttrib(elm, 'width', width, true); - dom.setStyle(elm, 'width', ''); - } - - // Remove these since they are not valid XHTML - dom.setAttrib(elm, 'borderColor', ''); - dom.setAttrib(elm, 'bgColor', ''); - dom.setAttrib(elm, 'background', ''); - - if (height && inst.settings.inline_styles) { - dom.setStyle(elm, 'height', height); - dom.setAttrib(elm, 'height', ''); - } else { - dom.setAttrib(elm, 'height', height, true); - dom.setStyle(elm, 'height', ''); - } - - if (background != '') - elm.style.backgroundImage = "url('" + background + "')"; - else - elm.style.backgroundImage = ''; - -/* if (tinyMCEPopup.getParam("inline_styles")) { - if (width != '') - elm.style.width = getCSSSize(width); - }*/ - - if (bordercolor != "") { - elm.style.borderColor = bordercolor; - elm.style.borderStyle = elm.style.borderStyle == "" ? "solid" : elm.style.borderStyle; - elm.style.borderWidth = border == "" ? "1px" : border; - } else - elm.style.borderColor = ''; - - elm.style.backgroundColor = bgcolor; - elm.style.height = getCSSSize(height); - - inst.addVisual(); - - // Fix for stange MSIE align bug - //elm.outerHTML = elm.outerHTML; - - inst.nodeChanged(); - inst.execCommand('mceEndUndoLevel'); - - // Repaint if dimensions changed - if (formObj.width.value != orgTableWidth || formObj.height.value != orgTableHeight) - inst.execCommand('mceRepaint'); - - tinyMCEPopup.close(); - return true; - } - - // Create new table - html += ''); - - tinymce.each('h1,h2,h3,h4,h5,h6,p'.split(','), function(n) { - if (patt) - patt += ','; - - patt += n + ' ._mce_marker'; - }); - - tinymce.each(inst.dom.select(patt), function(n) { - inst.dom.split(inst.dom.getParent(n, 'h1,h2,h3,h4,h5,h6,p'), n); - }); - - dom.setOuterHTML(dom.select('br._mce_marker')[0], html); - } else - inst.execCommand('mceInsertContent', false, html); - - tinymce.each(dom.select('table[_mce_new]'), function(node) { - var td = dom.select('td', node); - - inst.selection.select(td[0], true); - inst.selection.collapse(); - - dom.setAttrib(node, '_mce_new', ''); - }); - - inst.addVisual(); - inst.execCommand('mceEndUndoLevel'); - - tinyMCEPopup.close(); -} - -function makeAttrib(attrib, value) { - var formObj = document.forms[0]; - var valueElm = formObj.elements[attrib]; - - if (typeof(value) == "undefined" || value == null) { - value = ""; - - if (valueElm) - value = valueElm.value; - } - - if (value == "") - return ""; - - // XML encode it - value = value.replace(/&/g, '&'); - value = value.replace(/\"/g, '"'); - value = value.replace(//g, '>'); - - return ' ' + attrib + '="' + value + '"'; -} - -function init() { - tinyMCEPopup.resizeToInnerSize(); - - document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); - document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); - document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); - document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); - - var cols = 2, rows = 2, border = tinyMCEPopup.getParam('table_default_border', '0'), cellpadding = tinyMCEPopup.getParam('table_default_cellpadding', ''), cellspacing = tinyMCEPopup.getParam('table_default_cellspacing', ''); - var align = "", width = "", height = "", bordercolor = "", bgcolor = "", className = ""; - var id = "", summary = "", style = "", dir = "", lang = "", background = "", bgcolor = "", bordercolor = "", rules, frame; - var inst = tinyMCEPopup.editor, dom = inst.dom; - var formObj = document.forms[0]; - var elm = dom.getParent(inst.selection.getNode(), "table"); - - action = tinyMCEPopup.getWindowArg('action'); - - if (!action) - action = elm ? "update" : "insert"; - - if (elm && action != "insert") { - var rowsAr = elm.rows; - var cols = 0; - for (var i=0; i cols) - cols = rowsAr[i].cells.length; - - cols = cols; - rows = rowsAr.length; - - st = dom.parseStyle(dom.getAttrib(elm, "style")); - border = trimSize(getStyle(elm, 'border', 'borderWidth')); - cellpadding = dom.getAttrib(elm, 'cellpadding', ""); - cellspacing = dom.getAttrib(elm, 'cellspacing', ""); - width = trimSize(getStyle(elm, 'width', 'width')); - height = trimSize(getStyle(elm, 'height', 'height')); - bordercolor = convertRGBToHex(getStyle(elm, 'bordercolor', 'borderLeftColor')); - bgcolor = convertRGBToHex(getStyle(elm, 'bgcolor', 'backgroundColor')); - align = dom.getAttrib(elm, 'align', align); - frame = dom.getAttrib(elm, 'frame'); - rules = dom.getAttrib(elm, 'rules'); - className = tinymce.trim(dom.getAttrib(elm, 'class').replace(/mceItem.+/g, '')); - id = dom.getAttrib(elm, 'id'); - summary = dom.getAttrib(elm, 'summary'); - style = dom.serializeStyle(st); - dir = dom.getAttrib(elm, 'dir'); - lang = dom.getAttrib(elm, 'lang'); - background = getStyle(elm, 'background', 'backgroundImage').replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); - formObj.caption.checked = elm.getElementsByTagName('caption').length > 0; - - orgTableWidth = width; - orgTableHeight = height; - - action = "update"; - formObj.insert.value = inst.getLang('update'); - } - - addClassesToList('class', "table_styles"); - TinyMCE_EditableSelects.init(); - - // Update form - selectByValue(formObj, 'align', align); - selectByValue(formObj, 'tframe', frame); - selectByValue(formObj, 'rules', rules); - selectByValue(formObj, 'class', className, true, true); - formObj.cols.value = cols; - formObj.rows.value = rows; - formObj.border.value = border; - formObj.cellpadding.value = cellpadding; - formObj.cellspacing.value = cellspacing; - formObj.width.value = width; - formObj.height.value = height; - formObj.bordercolor.value = bordercolor; - formObj.bgcolor.value = bgcolor; - formObj.id.value = id; - formObj.summary.value = summary; - formObj.style.value = style; - formObj.dir.value = dir; - formObj.lang.value = lang; - formObj.backgroundimage.value = background; - - updateColor('bordercolor_pick', 'bordercolor'); - updateColor('bgcolor_pick', 'bgcolor'); - - // Resize some elements - if (isVisible('backgroundimagebrowser')) - document.getElementById('backgroundimage').style.width = '180px'; - - // Disable some fields in update mode - if (action == "update") { - formObj.cols.disabled = true; - formObj.rows.disabled = true; - } -} - -function changedSize() { - var formObj = document.forms[0]; - var st = dom.parseStyle(formObj.style.value); - -/* var width = formObj.width.value; - if (width != "") - st['width'] = tinyMCEPopup.getParam("inline_styles") ? getCSSSize(width) : ""; - else - st['width'] = "";*/ - - var height = formObj.height.value; - if (height != "") - st['height'] = getCSSSize(height); - else - st['height'] = ""; - - formObj.style.value = dom.serializeStyle(st); -} - -function changedBackgroundImage() { - var formObj = document.forms[0]; - var st = dom.parseStyle(formObj.style.value); - - st['background-image'] = "url('" + formObj.backgroundimage.value + "')"; - - formObj.style.value = dom.serializeStyle(st); -} - -function changedBorder() { - var formObj = document.forms[0]; - var st = dom.parseStyle(formObj.style.value); - - // Update border width if the element has a color - if (formObj.border.value != "" && formObj.bordercolor.value != "") - st['border-width'] = formObj.border.value + "px"; - - formObj.style.value = dom.serializeStyle(st); -} - -function changedColor() { - var formObj = document.forms[0]; - var st = dom.parseStyle(formObj.style.value); - - st['background-color'] = formObj.bgcolor.value; - - if (formObj.bordercolor.value != "") { - st['border-color'] = formObj.bordercolor.value; - - // Add border-width if it's missing - if (!st['border-width']) - st['border-width'] = formObj.border.value == "" ? "1px" : formObj.border.value + "px"; - } - - formObj.style.value = dom.serializeStyle(st); -} - -function changedStyle() { - var formObj = document.forms[0]; - var st = dom.parseStyle(formObj.style.value); - - if (st['background-image']) - formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1"); - else - formObj.backgroundimage.value = ''; - - if (st['width']) - formObj.width.value = trimSize(st['width']); - - if (st['height']) - formObj.height.value = trimSize(st['height']); - - if (st['background-color']) { - formObj.bgcolor.value = st['background-color']; - updateColor('bgcolor_pick','bgcolor'); - } - - if (st['border-color']) { - formObj.bordercolor.value = st['border-color']; - updateColor('bordercolor_pick','bordercolor'); - } -} - -tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); + +var action, orgTableWidth, orgTableHeight, dom = tinyMCEPopup.editor.dom; + +function insertTable() { + var formObj = document.forms[0]; + var inst = tinyMCEPopup.editor, dom = inst.dom; + var cols = 2, rows = 2, border = 0, cellpadding = -1, cellspacing = -1, align, width, height, className, caption, frame, rules; + var html = '', capEl, elm; + var cellLimit, rowLimit, colLimit; + + tinyMCEPopup.restoreSelection(); + + if (!AutoValidator.validate(formObj)) { + tinyMCEPopup.alert(AutoValidator.getErrorMessages(formObj).join('. ') + '.'); + return false; + } + + elm = dom.getParent(inst.selection.getNode(), 'table'); + + // Get form data + cols = formObj.elements['cols'].value; + rows = formObj.elements['rows'].value; + border = formObj.elements['border'].value != "" ? formObj.elements['border'].value : 0; + cellpadding = formObj.elements['cellpadding'].value != "" ? formObj.elements['cellpadding'].value : ""; + cellspacing = formObj.elements['cellspacing'].value != "" ? formObj.elements['cellspacing'].value : ""; + align = getSelectValue(formObj, "align"); + frame = getSelectValue(formObj, "tframe"); + rules = getSelectValue(formObj, "rules"); + width = formObj.elements['width'].value; + height = formObj.elements['height'].value; + bordercolor = formObj.elements['bordercolor'].value; + bgcolor = formObj.elements['bgcolor'].value; + className = getSelectValue(formObj, "class"); + id = formObj.elements['id'].value; + summary = formObj.elements['summary'].value; + style = formObj.elements['style'].value; + dir = formObj.elements['dir'].value; + lang = formObj.elements['lang'].value; + background = formObj.elements['backgroundimage'].value; + caption = formObj.elements['caption'].checked; + + cellLimit = tinyMCEPopup.getParam('table_cell_limit', false); + rowLimit = tinyMCEPopup.getParam('table_row_limit', false); + colLimit = tinyMCEPopup.getParam('table_col_limit', false); + + // Validate table size + if (colLimit && cols > colLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.col_limit').replace(/\{\$cols\}/g, colLimit)); + return false; + } else if (rowLimit && rows > rowLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.row_limit').replace(/\{\$rows\}/g, rowLimit)); + return false; + } else if (cellLimit && cols * rows > cellLimit) { + tinyMCEPopup.alert(inst.getLang('table_dlg.cell_limit').replace(/\{\$cells\}/g, cellLimit)); + return false; + } + + // Update table + if (action == "update") { + dom.setAttrib(elm, 'cellPadding', cellpadding, true); + dom.setAttrib(elm, 'cellSpacing', cellspacing, true); + + if (!isCssSize(border)) { + dom.setAttrib(elm, 'border', border); + } else { + dom.setAttrib(elm, 'border', ''); + } + + if (border == '') { + dom.setStyle(elm, 'border-width', ''); + dom.setStyle(elm, 'border', ''); + dom.setAttrib(elm, 'border', ''); + } + + dom.setAttrib(elm, 'align', align); + dom.setAttrib(elm, 'frame', frame); + dom.setAttrib(elm, 'rules', rules); + dom.setAttrib(elm, 'class', className); + dom.setAttrib(elm, 'style', style); + dom.setAttrib(elm, 'id', id); + dom.setAttrib(elm, 'summary', summary); + dom.setAttrib(elm, 'dir', dir); + dom.setAttrib(elm, 'lang', lang); + + capEl = inst.dom.select('caption', elm)[0]; + + if (capEl && !caption) + capEl.parentNode.removeChild(capEl); + + if (!capEl && caption) { + capEl = elm.ownerDocument.createElement('caption'); + + if (!tinymce.isIE) + capEl.innerHTML = '
    '; + + elm.insertBefore(capEl, elm.firstChild); + } + + if (width && inst.settings.inline_styles) { + dom.setStyle(elm, 'width', width); + dom.setAttrib(elm, 'width', ''); + } else { + dom.setAttrib(elm, 'width', width, true); + dom.setStyle(elm, 'width', ''); + } + + // Remove these since they are not valid XHTML + dom.setAttrib(elm, 'borderColor', ''); + dom.setAttrib(elm, 'bgColor', ''); + dom.setAttrib(elm, 'background', ''); + + if (height && inst.settings.inline_styles) { + dom.setStyle(elm, 'height', height); + dom.setAttrib(elm, 'height', ''); + } else { + dom.setAttrib(elm, 'height', height, true); + dom.setStyle(elm, 'height', ''); + } + + if (background != '') + elm.style.backgroundImage = "url('" + background + "')"; + else + elm.style.backgroundImage = ''; + +/* if (tinyMCEPopup.getParam("inline_styles")) { + if (width != '') + elm.style.width = getCSSSize(width); + }*/ + + if (bordercolor != "") { + elm.style.borderColor = bordercolor; + elm.style.borderStyle = elm.style.borderStyle == "" ? "solid" : elm.style.borderStyle; + elm.style.borderWidth = cssSize(border); + } else + elm.style.borderColor = ''; + + elm.style.backgroundColor = bgcolor; + elm.style.height = getCSSSize(height); + + inst.addVisual(); + + // Fix for stange MSIE align bug + //elm.outerHTML = elm.outerHTML; + + inst.nodeChanged(); + inst.execCommand('mceEndUndoLevel'); + + // Repaint if dimensions changed + if (formObj.width.value != orgTableWidth || formObj.height.value != orgTableHeight) + inst.execCommand('mceRepaint'); + + tinyMCEPopup.close(); + return true; + } + + // Create new table + html += ''); + + tinymce.each('h1,h2,h3,h4,h5,h6,p'.split(','), function(n) { + if (patt) + patt += ','; + + patt += n + ' ._mce_marker'; + }); + + tinymce.each(inst.dom.select(patt), function(n) { + inst.dom.split(inst.dom.getParent(n, 'h1,h2,h3,h4,h5,h6,p'), n); + }); + + dom.setOuterHTML(dom.select('br._mce_marker')[0], html); + } else + inst.execCommand('mceInsertContent', false, html); + + tinymce.each(dom.select('table[data-mce-new]'), function(node) { + var tdorth = dom.select('td,th', node); + + try { + // IE9 might fail to do this selection + inst.selection.setCursorLocation(tdorth[0], 0); + } catch (ex) { + // Ignore + } + + dom.setAttrib(node, 'data-mce-new', ''); + }); + + inst.addVisual(); + inst.execCommand('mceEndUndoLevel'); + + tinyMCEPopup.close(); +} + +function makeAttrib(attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib]; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + if (value == "") + return ""; + + // XML encode it + value = value.replace(/&/g, '&'); + value = value.replace(/\"/g, '"'); + value = value.replace(//g, '>'); + + return ' ' + attrib + '="' + value + '"'; +} + +function init() { + tinyMCEPopup.resizeToInnerSize(); + + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table'); + document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor'); + document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor'); + + var cols = 2, rows = 2, border = tinyMCEPopup.getParam('table_default_border', '0'), cellpadding = tinyMCEPopup.getParam('table_default_cellpadding', ''), cellspacing = tinyMCEPopup.getParam('table_default_cellspacing', ''); + var align = "", width = "", height = "", bordercolor = "", bgcolor = "", className = ""; + var id = "", summary = "", style = "", dir = "", lang = "", background = "", bgcolor = "", bordercolor = "", rules = "", frame = ""; + var inst = tinyMCEPopup.editor, dom = inst.dom; + var formObj = document.forms[0]; + var elm = dom.getParent(inst.selection.getNode(), "table"); + + action = tinyMCEPopup.getWindowArg('action'); + + if (!action) + action = elm ? "update" : "insert"; + + if (elm && action != "insert") { + var rowsAr = elm.rows; + var cols = 0; + for (var i=0; i cols) + cols = rowsAr[i].cells.length; + + cols = cols; + rows = rowsAr.length; + + st = dom.parseStyle(dom.getAttrib(elm, "style")); + border = trimSize(getStyle(elm, 'border', 'borderWidth')); + cellpadding = dom.getAttrib(elm, 'cellpadding', ""); + cellspacing = dom.getAttrib(elm, 'cellspacing', ""); + width = trimSize(getStyle(elm, 'width', 'width')); + height = trimSize(getStyle(elm, 'height', 'height')); + bordercolor = convertRGBToHex(getStyle(elm, 'bordercolor', 'borderLeftColor')); + bgcolor = convertRGBToHex(getStyle(elm, 'bgcolor', 'backgroundColor')); + align = dom.getAttrib(elm, 'align', align); + frame = dom.getAttrib(elm, 'frame'); + rules = dom.getAttrib(elm, 'rules'); + className = tinymce.trim(dom.getAttrib(elm, 'class').replace(/mceItem.+/g, '')); + id = dom.getAttrib(elm, 'id'); + summary = dom.getAttrib(elm, 'summary'); + style = dom.serializeStyle(st); + dir = dom.getAttrib(elm, 'dir'); + lang = dom.getAttrib(elm, 'lang'); + background = getStyle(elm, 'background', 'backgroundImage').replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + formObj.caption.checked = elm.getElementsByTagName('caption').length > 0; + + orgTableWidth = width; + orgTableHeight = height; + + action = "update"; + formObj.insert.value = inst.getLang('update'); + } + + addClassesToList('class', "table_styles"); + TinyMCE_EditableSelects.init(); + + // Update form + selectByValue(formObj, 'align', align); + selectByValue(formObj, 'tframe', frame); + selectByValue(formObj, 'rules', rules); + selectByValue(formObj, 'class', className, true, true); + formObj.cols.value = cols; + formObj.rows.value = rows; + formObj.border.value = border; + formObj.cellpadding.value = cellpadding; + formObj.cellspacing.value = cellspacing; + formObj.width.value = width; + formObj.height.value = height; + formObj.bordercolor.value = bordercolor; + formObj.bgcolor.value = bgcolor; + formObj.id.value = id; + formObj.summary.value = summary; + formObj.style.value = style; + formObj.dir.value = dir; + formObj.lang.value = lang; + formObj.backgroundimage.value = background; + + updateColor('bordercolor_pick', 'bordercolor'); + updateColor('bgcolor_pick', 'bgcolor'); + + // Resize some elements + if (isVisible('backgroundimagebrowser')) + document.getElementById('backgroundimage').style.width = '180px'; + + // Disable some fields in update mode + if (action == "update") { + formObj.cols.disabled = true; + formObj.rows.disabled = true; + } +} + +function changedSize() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + +/* var width = formObj.width.value; + if (width != "") + st['width'] = tinyMCEPopup.getParam("inline_styles") ? getCSSSize(width) : ""; + else + st['width'] = "";*/ + + var height = formObj.height.value; + if (height != "") + st['height'] = getCSSSize(height); + else + st['height'] = ""; + + formObj.style.value = dom.serializeStyle(st); +} + +function isCssSize(value) { + return /^[0-9.]+(%|in|cm|mm|em|ex|pt|pc|px)$/.test(value); +} + +function cssSize(value, def) { + value = tinymce.trim(value || def); + + if (!isCssSize(value)) { + return parseInt(value, 10) + 'px'; + } + + return value; +} + +function changedBackgroundImage() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-image'] = "url('" + formObj.backgroundimage.value + "')"; + + formObj.style.value = dom.serializeStyle(st); +} + +function changedBorder() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + // Update border width if the element has a color + if (formObj.border.value != "" && (isCssSize(formObj.border.value) || formObj.bordercolor.value != "")) + st['border-width'] = cssSize(formObj.border.value); + else { + if (!formObj.border.value) { + st['border'] = ''; + st['border-width'] = ''; + } + } + + formObj.style.value = dom.serializeStyle(st); +} + +function changedColor() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + st['background-color'] = formObj.bgcolor.value; + + if (formObj.bordercolor.value != "") { + st['border-color'] = formObj.bordercolor.value; + + // Add border-width if it's missing + if (!st['border-width']) + st['border-width'] = cssSize(formObj.border.value, 1); + } + + formObj.style.value = dom.serializeStyle(st); +} + +function changedStyle() { + var formObj = document.forms[0]; + var st = dom.parseStyle(formObj.style.value); + + if (st['background-image']) + formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\(['\"]?([^'\"]*)['\"]?\\)", 'gi'), "$1"); + else + formObj.backgroundimage.value = ''; + + if (st['width']) + formObj.width.value = trimSize(st['width']); + + if (st['height']) + formObj.height.value = trimSize(st['height']); + + if (st['background-color']) { + formObj.bgcolor.value = st['background-color']; + updateColor('bgcolor_pick','bgcolor'); + } + + if (st['border-color']) { + formObj.bordercolor.value = st['border-color']; + updateColor('bordercolor_pick','bordercolor'); + } +} + +tinyMCEPopup.onInit.add(init); diff --git a/js/tiny_mce/plugins/table/langs/en_dlg.js b/js/tiny_mce/plugins/table/langs/en_dlg.js index 000332a3..463e09ee 100644 --- a/js/tiny_mce/plugins/table/langs/en_dlg.js +++ b/js/tiny_mce/plugins/table/langs/en_dlg.js @@ -1,74 +1 @@ -tinyMCE.addI18n('en.table_dlg',{ -general_tab:"General", -advanced_tab:"Advanced", -general_props:"General properties", -advanced_props:"Advanced properties", -rowtype:"Row in table part", -title:"Insert/Modify table", -width:"Width", -height:"Height", -cols:"Cols", -rows:"Rows", -cellspacing:"Cellspacing", -cellpadding:"Cellpadding", -border:"Border", -align:"Alignment", -align_default:"Default", -align_left:"Left", -align_right:"Right", -align_middle:"Center", -row_title:"Table row properties", -cell_title:"Table cell properties", -cell_type:"Cell type", -valign:"Vertical alignment", -align_top:"Top", -align_bottom:"Bottom", -bordercolor:"Border color", -bgcolor:"Background color", -merge_cells_title:"Merge table cells", -id:"Id", -style:"Style", -langdir:"Language direction", -langcode:"Language code", -mime:"Target MIME type", -ltr:"Left to right", -rtl:"Right to left", -bgimage:"Background image", -summary:"Summary", -td:"Data", -th:"Header", -cell_cell:"Update current cell", -cell_row:"Update all cells in row", -cell_all:"Update all cells in table", -row_row:"Update current row", -row_odd:"Update odd rows in table", -row_even:"Update even rows in table", -row_all:"Update all rows in table", -thead:"Table Head", -tbody:"Table Body", -tfoot:"Table Foot", -scope:"Scope", -rowgroup:"Row Group", -colgroup:"Col Group", -col_limit:"You've exceeded the maximum number of columns of {$cols}.", -row_limit:"You've exceeded the maximum number of rows of {$rows}.", -cell_limit:"You've exceeded the maximum number of cells of {$cells}.", -missing_scope:"Are you sure you want to continue without specifying a scope for this table header cell. Without it, it may be difficult for some users with disabilities to understand the content or data displayed of the table.", -caption:"Table caption", -frame:"Frame", -frame_none:"none", -frame_groups:"groups", -frame_rows:"rows", -frame_cols:"cols", -frame_all:"all", -rules:"Rules", -rules_void:"void", -rules_above:"above", -rules_below:"below", -rules_hsides:"hsides", -rules_lhs:"lhs", -rules_rhs:"rhs", -rules_vsides:"vsides", -rules_box:"box", -rules_border:"border" -}); \ No newline at end of file +tinyMCE.addI18n('en.table_dlg',{"rules_border":"border","rules_box":"box","rules_vsides":"vsides","rules_rhs":"rhs","rules_lhs":"lhs","rules_hsides":"hsides","rules_below":"below","rules_above":"above","rules_void":"void",rules:"Rules","frame_all":"all","frame_cols":"cols","frame_rows":"rows","frame_groups":"groups","frame_none":"none",frame:"Frame",caption:"Table Caption","missing_scope":"Are you sure you want to continue without specifying a scope for this table header cell. Without it, it may be difficult for some users with disabilities to understand the content or data displayed of the table.","cell_limit":"You\'ve exceeded the maximum number of cells of {$cells}.","row_limit":"You\'ve exceeded the maximum number of rows of {$rows}.","col_limit":"You\'ve exceeded the maximum number of columns of {$cols}.",colgroup:"Col Group",rowgroup:"Row Group",scope:"Scope",tfoot:"Footer",tbody:"Body",thead:"Header","row_all":"Update All Rows in Table","row_even":"Update Even Rows in Table","row_odd":"Update Odd Rows in Table","row_row":"Update Current Row","cell_all":"Update All Cells in Table","cell_row":"Update All Cells in Row","cell_cell":"Update Current Cell",th:"Header",td:"Data",summary:"Summary",bgimage:"Background Image",rtl:"Right to Left",ltr:"Left to Right",mime:"Target MIME Type",langcode:"Language Code",langdir:"Language Direction",style:"Style",id:"ID","merge_cells_title":"Merge Table Cells",bgcolor:"Background Color",bordercolor:"Border Color","align_bottom":"Bottom","align_top":"Top",valign:"Vertical Alignment","cell_type":"Cell Type","cell_title":"Table Cell Properties","row_title":"Table Row Properties","align_middle":"Center","align_right":"Right","align_left":"Left","align_default":"Default",align:"Alignment",border:"Border",cellpadding:"Cell Padding",cellspacing:"Cell Spacing",rows:"Rows",cols:"Columns",height:"Height",width:"Width",title:"Insert/Edit Table",rowtype:"Row Type","advanced_props":"Advanced Properties","general_props":"General Properties","advanced_tab":"Advanced","general_tab":"General","cell_col":"Update all cells in column"}); \ No newline at end of file diff --git a/js/tiny_mce/plugins/table/merge_cells.htm b/js/tiny_mce/plugins/table/merge_cells.htm index 9736ed8c..788acf68 100644 --- a/js/tiny_mce/plugins/table/merge_cells.htm +++ b/js/tiny_mce/plugins/table/merge_cells.htm @@ -1,32 +1,32 @@ - - - - {#table_dlg.merge_cells_title} - - - - - - -
    -
    - {#table_dlg.merge_cells_title} - - - - - - - - - -
    {#table_dlg.cols}:
    {#table_dlg.rows}:
    -
    - -
    - - -
    -
    - - + + + + {#table_dlg.merge_cells_title} + + + + + + +
    +
    + {#table_dlg.merge_cells_title} + + + + + + + + + +
    :
    :
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/table/row.htm b/js/tiny_mce/plugins/table/row.htm index 092e6c82..e0b182b8 100644 --- a/js/tiny_mce/plugins/table/row.htm +++ b/js/tiny_mce/plugins/table/row.htm @@ -1,155 +1,158 @@ - - - - {#table_dlg.row_title} - - - - - - - - -
    - - -
    -
    -
    - {#table_dlg.general_props} - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - -
    - -
    -
    -
    - -
    -
    - {#table_dlg.advanced_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - -
    - - - - - -
     
    -
    - - - - - -
     
    -
    -
    -
    -
    - -
    -
    - -
    - - - -
    -
    - - + + + + {#table_dlg.row_title} + + + + + + + + + +
    + + +
    +
    +
    + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + +
    + +
    +
    +
    + +
    +
    + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + + + + + +
     
    +
    + + + + + + +
     
    +
    +
    +
    +
    +
    + +
    +
    + +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/table/table.htm b/js/tiny_mce/plugins/table/table.htm index f2690392..52e6bf28 100644 --- a/js/tiny_mce/plugins/table/table.htm +++ b/js/tiny_mce/plugins/table/table.htm @@ -1,187 +1,188 @@ - - - - {#table_dlg.title} - - - - - - - - - -
    - - -
    -
    -
    - {#table_dlg.general_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    - -
    -
    - {#table_dlg.advanced_props} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - - -
     
    -
    - -
    - -
    - -
    - - - - - -
     
    -
    - - - - - -
     
    -
    -
    -
    -
    - -
    - - -
    -
    - - + + + + {#table_dlg.title} + + + + + + + + + + +
    + + +
    +
    +
    + {#table_dlg.general_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    + {#table_dlg.advanced_props} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + +
     
    +
    + +
    + +
    + +
    + + + + + +
     
    +
    + + + + + +
     
    +
    +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/template/js/template.js b/js/tiny_mce/plugins/template/js/template.js index 24045d73..673395a9 100644 --- a/js/tiny_mce/plugins/template/js/template.js +++ b/js/tiny_mce/plugins/template/js/template.js @@ -1,106 +1,106 @@ -tinyMCEPopup.requireLangPack(); - -var TemplateDialog = { - preInit : function() { - var url = tinyMCEPopup.getParam("template_external_list_url"); - - if (url != null) - document.write(''); - }, - - init : function() { - var ed = tinyMCEPopup.editor, tsrc, sel, x, u; - - tsrc = ed.getParam("template_templates", false); - sel = document.getElementById('tpath'); - - // Setup external template list - if (!tsrc && typeof(tinyMCETemplateList) != 'undefined') { - for (x=0, tsrc = []; x'); - }); - }, - - selectTemplate : function(u, ti) { - var d = window.frames['templatesrc'].document, x, tsrc = this.tsrc; - - if (!u) - return; - - d.body.innerHTML = this.templateHTML = this.getFileContents(u); - - for (x=0; x'); + }, + + init : function() { + var ed = tinyMCEPopup.editor, tsrc, sel, x, u; + + tsrc = ed.getParam("template_templates", false); + sel = document.getElementById('tpath'); + + // Setup external template list + if (!tsrc && typeof(tinyMCETemplateList) != 'undefined') { + for (x=0, tsrc = []; x'); + }); + }, + + selectTemplate : function(u, ti) { + var d = window.frames['templatesrc'].document, x, tsrc = this.tsrc; + + if (!u) + return; + + d.body.innerHTML = this.templateHTML = this.getFileContents(u); + + for (x=0; x - - {#template_dlg.title} - - - - - -
    -
    -
    {#template_dlg.desc}
    -
    - -
    -
    -
    -
    - {#template_dlg.preview} - -
    -
    - -
    - - -
    -
    -
    - - + + + {#template_dlg.title} + + + + + +
    +
    +
    {#template_dlg.desc}
    +
    + +
    +
    +
    +
    + {#template_dlg.preview} + +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/visualchars/editor_plugin.js b/js/tiny_mce/plugins/visualchars/editor_plugin.js index 53d31c44..1a148e8b 100644 --- a/js/tiny_mce/plugins/visualchars/editor_plugin.js +++ b/js/tiny_mce/plugins/visualchars/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.VisualChars",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceVisualChars",c._toggleVisualChars,c);a.addButton("visualchars",{title:"visualchars.desc",cmd:"mceVisualChars"});a.onBeforeGetContent.add(function(d,e){if(c.state){c.state=true;c._toggleVisualChars()}})},getInfo:function(){return{longname:"Visual characters",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/visualchars",version:tinymce.majorVersion+"."+tinymce.minorVersion}},_toggleVisualChars:function(){var m=this,g=m.editor,a,e,f,k=g.getDoc(),l=g.getBody(),j,n=g.selection,c;m.state=!m.state;g.controlManager.setActive("visualchars",m.state);if(m.state){a=[];tinymce.walk(l,function(b){if(b.nodeType==3&&b.nodeValue&&b.nodeValue.indexOf("\u00a0")!=-1){a.push(b)}},"childNodes");for(e=0;e$1');j=j.replace(/\u00a0/g,"\u00b7");g.dom.setOuterHTML(a[e],j,k)}}else{a=tinymce.grep(g.dom.select("span",l),function(b){return g.dom.hasClass(b,"mceVisualNbsp")});for(e=0;e$1');c=k.dom.create("div",null,l);while(node=c.lastChild){k.dom.insertAfter(node,a[g])}k.dom.remove(a[g])}}else{a=k.dom.select("span.mceItemNbsp",o);for(g=a.length-1;g>=0;g--){k.dom.remove(a[g],1)}}q.moveToBookmark(f)}});tinymce.PluginManager.add("visualchars",tinymce.plugins.VisualChars)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/visualchars/editor_plugin_src.js b/js/tiny_mce/plugins/visualchars/editor_plugin_src.js index 0a5275fe..0e3572e6 100644 --- a/js/tiny_mce/plugins/visualchars/editor_plugin_src.js +++ b/js/tiny_mce/plugins/visualchars/editor_plugin_src.js @@ -1,76 +1,83 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.VisualChars', { - init : function(ed, url) { - var t = this; - - t.editor = ed; - - // Register commands - ed.addCommand('mceVisualChars', t._toggleVisualChars, t); - - // Register buttons - ed.addButton('visualchars', {title : 'visualchars.desc', cmd : 'mceVisualChars'}); - - ed.onBeforeGetContent.add(function(ed, o) { - if (t.state) { - t.state = true; - t._toggleVisualChars(); - } - }); - }, - - getInfo : function() { - return { - longname : 'Visual characters', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/visualchars', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - }, - - // Private methods - - _toggleVisualChars : function() { - var t = this, ed = t.editor, nl, i, h, d = ed.getDoc(), b = ed.getBody(), nv, s = ed.selection, bo; - - t.state = !t.state; - ed.controlManager.setActive('visualchars', t.state); - - if (t.state) { - nl = []; - tinymce.walk(b, function(n) { - if (n.nodeType == 3 && n.nodeValue && n.nodeValue.indexOf('\u00a0') != -1) - nl.push(n); - }, 'childNodes'); - - for (i=0; i$1'); - nv = nv.replace(/\u00a0/g, '\u00b7'); - ed.dom.setOuterHTML(nl[i], nv, d); - } - } else { - nl = tinymce.grep(ed.dom.select('span', b), function(n) { - return ed.dom.hasClass(n, 'mceVisualNbsp'); - }); - - for (i=0; i$1'); + + div = ed.dom.create('div', null, nv); + while (node = div.lastChild) + ed.dom.insertAfter(node, nl[i]); + + ed.dom.remove(nl[i]); + } + } else { + nl = ed.dom.select('span.mceItemNbsp', b); + + for (i = nl.length - 1; i >= 0; i--) + ed.dom.remove(nl[i], 1); + } + + s.moveToBookmark(bm); + } + }); + + // Register plugin + tinymce.PluginManager.add('visualchars', tinymce.plugins.VisualChars); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/wordcount/editor_plugin.js b/js/tiny_mce/plugins/wordcount/editor_plugin.js index f1928359..a752ad32 100644 --- a/js/tiny_mce/plugins/wordcount/editor_plugin.js +++ b/js/tiny_mce/plugins/wordcount/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.WordCount",{block:0,id:null,countre:null,cleanre:null,init:function(a,b){var c=this,d=0;c.countre=a.getParam("wordcount_countregex",/\S\s+/g);c.cleanre=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$¿'"_+=\\/-]*/g);c.id=a.id+"-word-count";a.onPostRender.add(function(f,e){var g,h;h=f.getParam("wordcount_target_id");if(!h){g=tinymce.DOM.get(f.id+"_path_row");if(g){tinymce.DOM.add(g.parentNode,"div",{style:"float: right"},f.getLang("wordcount.words","Words: ")+'0')}}else{tinymce.DOM.add(h,"span",{},'0')}});a.onInit.add(function(e){e.selection.onSetContent.add(function(){c._count(e)});c._count(e)});a.onSetContent.add(function(e){c._count(e)});a.onKeyUp.add(function(f,g){if(g.keyCode==d){return}if(13==g.keyCode||8==d||46==d){c._count(f)}d=g.keyCode})},_count:function(b){var c=this,a=0;if(c.block){return}c.block=1;setTimeout(function(){var d=b.getContent({format:"raw"});if(d){d=d.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ");d=d.replace(c.cleanre,"");d.replace(c.countre,function(){a++})}tinymce.DOM.setHTML(c.id,a.toString());setTimeout(function(){c.block=0},2000)},1)},getInfo:function(){return{longname:"Word Count plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("wordcount",tinymce.plugins.WordCount)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.WordCount",{block:0,id:null,countre:null,cleanre:null,init:function(a,b){var c=this,d=0;c.countre=a.getParam("wordcount_countregex",/[\w\u2019\'-]+/g);c.cleanre=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\'\"_+=\\\/-]*/g);c.id=a.id+"-word-count";a.onPostRender.add(function(f,e){var g,h;h=f.getParam("wordcount_target_id");if(!h){g=tinymce.DOM.get(f.id+"_path_row");if(g){tinymce.DOM.add(g.parentNode,"div",{style:"float: right"},f.getLang("wordcount.words","Words: ")+'0')}}else{tinymce.DOM.add(h,"span",{},'0')}});a.onInit.add(function(e){e.selection.onSetContent.add(function(){c._count(e)});c._count(e)});a.onSetContent.add(function(e){c._count(e)});a.onKeyUp.add(function(f,g){if(g.keyCode==d){return}if(13==g.keyCode||8==d||46==d){c._count(f)}d=g.keyCode})},_getCount:function(c){var a=0;var b=c.getContent({format:"raw"});if(b){b=b.replace(/\.\.\./g," ");b=b.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ");b=b.replace(/(\w+)(&.+?;)+(\w+)/,"$1$3").replace(/&.+?;/g," ");b=b.replace(this.cleanre,"");var d=b.match(this.countre);if(d){a=d.length}}return a},_count:function(a){var b=this;if(b.block){return}b.block=1;setTimeout(function(){if(!a.destroyed){var c=b._getCount(a);tinymce.DOM.setHTML(b.id,c.toString());setTimeout(function(){b.block=0},2000)}},1)},getInfo:function(){return{longname:"Word Count plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("wordcount",tinymce.plugins.WordCount)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/wordcount/editor_plugin_src.js b/js/tiny_mce/plugins/wordcount/editor_plugin_src.js index bdfebf1b..032a3f67 100644 --- a/js/tiny_mce/plugins/wordcount/editor_plugin_src.js +++ b/js/tiny_mce/plugins/wordcount/editor_plugin_src.js @@ -1,98 +1,114 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.WordCount', { - block : 0, - id : null, - countre : null, - cleanre : null, - - init : function(ed, url) { - var t = this, last = 0; - - t.countre = ed.getParam('wordcount_countregex', /\S\s+/g); - t.cleanre = ed.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$¿'"_+=\\/-]*/g); - t.id = ed.id + '-word-count'; - - ed.onPostRender.add(function(ed, cm) { - var row, id; - - // Add it to the specified id or the theme advanced path - id = ed.getParam('wordcount_target_id'); - if (!id) { - row = tinymce.DOM.get(ed.id + '_path_row'); - - if (row) - tinymce.DOM.add(row.parentNode, 'div', {'style': 'float: right'}, ed.getLang('wordcount.words', 'Words: ') + '0'); - } else - tinymce.DOM.add(id, 'span', {}, '0'); - }); - - ed.onInit.add(function(ed) { - ed.selection.onSetContent.add(function() { - t._count(ed); - }); - - t._count(ed); - }); - - ed.onSetContent.add(function(ed) { - t._count(ed); - }); - - ed.onKeyUp.add(function(ed, e) { - if (e.keyCode == last) - return; - - if (13 == e.keyCode || 8 == last || 46 == last) - t._count(ed); - - last = e.keyCode; - }); - }, - - _count : function(ed) { - var t = this, tc = 0; - - // Keep multiple calls from happening at the same time - if (t.block) - return; - - t.block = 1; - - setTimeout(function() { - var tx = ed.getContent({format : 'raw'}); - - if (tx) { - tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars - tx = tx.replace(t.cleanre, ''); // remove numbers and punctuation - tx.replace(t.countre, function() {tc++;}); // count the words - } - - tinymce.DOM.setHTML(t.id, tc.toString()); - - setTimeout(function() {t.block = 0;}, 2000); - }, 1); - }, - - getInfo: function() { - return { - longname : 'Word Count plugin', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - tinymce.PluginManager.add('wordcount', tinymce.plugins.WordCount); -})(); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.WordCount', { + block : 0, + id : null, + countre : null, + cleanre : null, + + init : function(ed, url) { + var t = this, last = 0; + + t.countre = ed.getParam('wordcount_countregex', /[\w\u2019\'-]+/g); // u2019 == ’ + t.cleanre = ed.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$?\'\"_+=\\\/-]*/g); + t.id = ed.id + '-word-count'; + + ed.onPostRender.add(function(ed, cm) { + var row, id; + + // Add it to the specified id or the theme advanced path + id = ed.getParam('wordcount_target_id'); + if (!id) { + row = tinymce.DOM.get(ed.id + '_path_row'); + + if (row) + tinymce.DOM.add(row.parentNode, 'div', {'style': 'float: right'}, ed.getLang('wordcount.words', 'Words: ') + '0'); + } else { + tinymce.DOM.add(id, 'span', {}, '0'); + } + }); + + ed.onInit.add(function(ed) { + ed.selection.onSetContent.add(function() { + t._count(ed); + }); + + t._count(ed); + }); + + ed.onSetContent.add(function(ed) { + t._count(ed); + }); + + ed.onKeyUp.add(function(ed, e) { + if (e.keyCode == last) + return; + + if (13 == e.keyCode || 8 == last || 46 == last) + t._count(ed); + + last = e.keyCode; + }); + }, + + _getCount : function(ed) { + var tc = 0; + var tx = ed.getContent({ format: 'raw' }); + + if (tx) { + tx = tx.replace(/\.\.\./g, ' '); // convert ellipses to spaces + tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars + + // deal with html entities + tx = tx.replace(/(\w+)(&.+?;)+(\w+)/, "$1$3").replace(/&.+?;/g, ' '); + tx = tx.replace(this.cleanre, ''); // remove numbers and punctuation + + var wordArray = tx.match(this.countre); + if (wordArray) { + tc = wordArray.length; + } + } + + return tc; + }, + + _count : function(ed) { + var t = this; + + // Keep multiple calls from happening at the same time + if (t.block) + return; + + t.block = 1; + + setTimeout(function() { + if (!ed.destroyed) { + var tc = t._getCount(ed); + tinymce.DOM.setHTML(t.id, tc.toString()); + setTimeout(function() {t.block = 0;}, 2000); + } + }, 1); + }, + + getInfo: function() { + return { + longname : 'Word Count plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + tinymce.PluginManager.add('wordcount', tinymce.plugins.WordCount); +})(); diff --git a/js/tiny_mce/plugins/xhtmlxtras/abbr.htm b/js/tiny_mce/plugins/xhtmlxtras/abbr.htm index 3aeac0de..d4102180 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/abbr.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/abbr.htm @@ -1,141 +1,142 @@ - - - - {#xhtmlxtras_dlg.title_abbr_element} - - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    : - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - - -
    -
    - - + + + + {#xhtmlxtras_dlg.title_abbr_element} + + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    : + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/acronym.htm b/js/tiny_mce/plugins/xhtmlxtras/acronym.htm index 31ee7b70..12b189b4 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/acronym.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/acronym.htm @@ -1,141 +1,142 @@ - - - - {#xhtmlxtras_dlg.title_acronym_element} - - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    : - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - - -
    -
    - - + + + + {#xhtmlxtras_dlg.title_acronym_element} + + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    : + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/attributes.htm b/js/tiny_mce/plugins/xhtmlxtras/attributes.htm index 17054da3..d84f378b 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/attributes.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/attributes.htm @@ -1,148 +1,149 @@ - - - - {#xhtmlxtras_dlg.attribs_title} - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.attribute_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.attribute_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - -
    -
    - - + + + + {#xhtmlxtras_dlg.attribs_title} + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.attribute_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.attribute_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/cite.htm b/js/tiny_mce/plugins/xhtmlxtras/cite.htm index d0a3e3a8..ab61b330 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/cite.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/cite.htm @@ -1,141 +1,142 @@ - - - - {#xhtmlxtras_dlg.title_cite_element} - - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    : - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - - -
    -
    - - + + + + {#xhtmlxtras_dlg.title_cite_element} + + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    : + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/del.htm b/js/tiny_mce/plugins/xhtmlxtras/del.htm index 8b07fa84..e3f34c7d 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/del.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/del.htm @@ -1,161 +1,162 @@ - - - - {#xhtmlxtras_dlg.title_del_element} - - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_general_tab} - - - - - - - - - -
    : - - - - - -
    -
    :
    -
    -
    - {#xhtmlxtras_dlg.fieldset_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    : - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - - -
    -
    - - + + + + {#xhtmlxtras_dlg.title_del_element} + + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_general_tab} + + + + + + + + + +
    : + + + + + +
    +
    :
    +
    +
    + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    : + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/editor_plugin.js b/js/tiny_mce/plugins/xhtmlxtras/editor_plugin.js index e5195265..9b98a515 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/editor_plugin.js +++ b/js/tiny_mce/plugins/xhtmlxtras/editor_plugin.js @@ -1 +1 @@ -(function(){tinymce.create("tinymce.plugins.XHTMLXtrasPlugin",{init:function(b,c){b.addCommand("mceCite",function(){b.windowManager.open({file:c+"/cite.htm",width:350+parseInt(b.getLang("xhtmlxtras.cite_delta_width",0)),height:250+parseInt(b.getLang("xhtmlxtras.cite_delta_height",0)),inline:1},{plugin_url:c})});b.addCommand("mceAcronym",function(){b.windowManager.open({file:c+"/acronym.htm",width:350+parseInt(b.getLang("xhtmlxtras.acronym_delta_width",0)),height:250+parseInt(b.getLang("xhtmlxtras.acronym_delta_width",0)),inline:1},{plugin_url:c})});b.addCommand("mceAbbr",function(){b.windowManager.open({file:c+"/abbr.htm",width:350+parseInt(b.getLang("xhtmlxtras.abbr_delta_width",0)),height:250+parseInt(b.getLang("xhtmlxtras.abbr_delta_width",0)),inline:1},{plugin_url:c})});b.addCommand("mceDel",function(){b.windowManager.open({file:c+"/del.htm",width:340+parseInt(b.getLang("xhtmlxtras.del_delta_width",0)),height:310+parseInt(b.getLang("xhtmlxtras.del_delta_width",0)),inline:1},{plugin_url:c})});b.addCommand("mceIns",function(){b.windowManager.open({file:c+"/ins.htm",width:340+parseInt(b.getLang("xhtmlxtras.ins_delta_width",0)),height:310+parseInt(b.getLang("xhtmlxtras.ins_delta_width",0)),inline:1},{plugin_url:c})});b.addCommand("mceAttributes",function(){b.windowManager.open({file:c+"/attributes.htm",width:380,height:370,inline:1},{plugin_url:c})});b.addButton("cite",{title:"xhtmlxtras.cite_desc",cmd:"mceCite"});b.addButton("acronym",{title:"xhtmlxtras.acronym_desc",cmd:"mceAcronym"});b.addButton("abbr",{title:"xhtmlxtras.abbr_desc",cmd:"mceAbbr"});b.addButton("del",{title:"xhtmlxtras.del_desc",cmd:"mceDel"});b.addButton("ins",{title:"xhtmlxtras.ins_desc",cmd:"mceIns"});b.addButton("attribs",{title:"xhtmlxtras.attribs_desc",cmd:"mceAttributes"});if(tinymce.isIE){function a(d,e){if(e.set){e.content=e.content.replace(/]+)>/gi,"");e.content=e.content.replace(/<\/abbr>/gi,"")}}b.onBeforeSetContent.add(a);b.onPostProcess.add(a)}b.onNodeChange.add(function(e,d,g,f){g=e.dom.getParent(g,"CITE,ACRONYM,ABBR,DEL,INS");d.setDisabled("cite",f);d.setDisabled("acronym",f);d.setDisabled("abbr",f);d.setDisabled("del",f);d.setDisabled("ins",f);d.setDisabled("attribs",g&&g.nodeName=="BODY");d.setActive("cite",0);d.setActive("acronym",0);d.setActive("abbr",0);d.setActive("del",0);d.setActive("ins",0);if(g){do{d.setDisabled(g.nodeName.toLowerCase(),0);d.setActive(g.nodeName.toLowerCase(),1)}while(g=g.parentNode)}});b.onPreInit.add(function(){b.dom.create("abbr")})},getInfo:function(){return{longname:"XHTML Xtras Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("xhtmlxtras",tinymce.plugins.XHTMLXtrasPlugin)})(); \ No newline at end of file +(function(){tinymce.create("tinymce.plugins.XHTMLXtrasPlugin",{init:function(a,b){a.addCommand("mceCite",function(){a.windowManager.open({file:b+"/cite.htm",width:350+parseInt(a.getLang("xhtmlxtras.cite_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.cite_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceAcronym",function(){a.windowManager.open({file:b+"/acronym.htm",width:350+parseInt(a.getLang("xhtmlxtras.acronym_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.acronym_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceAbbr",function(){a.windowManager.open({file:b+"/abbr.htm",width:350+parseInt(a.getLang("xhtmlxtras.abbr_delta_width",0)),height:250+parseInt(a.getLang("xhtmlxtras.abbr_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceDel",function(){a.windowManager.open({file:b+"/del.htm",width:340+parseInt(a.getLang("xhtmlxtras.del_delta_width",0)),height:310+parseInt(a.getLang("xhtmlxtras.del_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceIns",function(){a.windowManager.open({file:b+"/ins.htm",width:340+parseInt(a.getLang("xhtmlxtras.ins_delta_width",0)),height:310+parseInt(a.getLang("xhtmlxtras.ins_delta_height",0)),inline:1},{plugin_url:b})});a.addCommand("mceAttributes",function(){a.windowManager.open({file:b+"/attributes.htm",width:380+parseInt(a.getLang("xhtmlxtras.attr_delta_width",0)),height:370+parseInt(a.getLang("xhtmlxtras.attr_delta_height",0)),inline:1},{plugin_url:b})});a.addButton("cite",{title:"xhtmlxtras.cite_desc",cmd:"mceCite"});a.addButton("acronym",{title:"xhtmlxtras.acronym_desc",cmd:"mceAcronym"});a.addButton("abbr",{title:"xhtmlxtras.abbr_desc",cmd:"mceAbbr"});a.addButton("del",{title:"xhtmlxtras.del_desc",cmd:"mceDel"});a.addButton("ins",{title:"xhtmlxtras.ins_desc",cmd:"mceIns"});a.addButton("attribs",{title:"xhtmlxtras.attribs_desc",cmd:"mceAttributes"});a.onNodeChange.add(function(d,c,f,e){f=d.dom.getParent(f,"CITE,ACRONYM,ABBR,DEL,INS");c.setDisabled("cite",e);c.setDisabled("acronym",e);c.setDisabled("abbr",e);c.setDisabled("del",e);c.setDisabled("ins",e);c.setDisabled("attribs",f&&f.nodeName=="BODY");c.setActive("cite",0);c.setActive("acronym",0);c.setActive("abbr",0);c.setActive("del",0);c.setActive("ins",0);if(f){do{c.setDisabled(f.nodeName.toLowerCase(),0);c.setActive(f.nodeName.toLowerCase(),1)}while(f=f.parentNode)}});a.onPreInit.add(function(){a.dom.create("abbr")})},getInfo:function(){return{longname:"XHTML Xtras Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("xhtmlxtras",tinymce.plugins.XHTMLXtrasPlugin)})(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/xhtmlxtras/editor_plugin_src.js b/js/tiny_mce/plugins/xhtmlxtras/editor_plugin_src.js index 9b51b836..a9c12ef3 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/editor_plugin_src.js +++ b/js/tiny_mce/plugins/xhtmlxtras/editor_plugin_src.js @@ -1,144 +1,132 @@ -/** - * editor_plugin_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - tinymce.create('tinymce.plugins.XHTMLXtrasPlugin', { - init : function(ed, url) { - // Register commands - ed.addCommand('mceCite', function() { - ed.windowManager.open({ - file : url + '/cite.htm', - width : 350 + parseInt(ed.getLang('xhtmlxtras.cite_delta_width', 0)), - height : 250 + parseInt(ed.getLang('xhtmlxtras.cite_delta_height', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - ed.addCommand('mceAcronym', function() { - ed.windowManager.open({ - file : url + '/acronym.htm', - width : 350 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)), - height : 250 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - ed.addCommand('mceAbbr', function() { - ed.windowManager.open({ - file : url + '/abbr.htm', - width : 350 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)), - height : 250 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - ed.addCommand('mceDel', function() { - ed.windowManager.open({ - file : url + '/del.htm', - width : 340 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)), - height : 310 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - ed.addCommand('mceIns', function() { - ed.windowManager.open({ - file : url + '/ins.htm', - width : 340 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)), - height : 310 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)), - inline : 1 - }, { - plugin_url : url - }); - }); - - ed.addCommand('mceAttributes', function() { - ed.windowManager.open({ - file : url + '/attributes.htm', - width : 380, - height : 370, - inline : 1 - }, { - plugin_url : url - }); - }); - - // Register buttons - ed.addButton('cite', {title : 'xhtmlxtras.cite_desc', cmd : 'mceCite'}); - ed.addButton('acronym', {title : 'xhtmlxtras.acronym_desc', cmd : 'mceAcronym'}); - ed.addButton('abbr', {title : 'xhtmlxtras.abbr_desc', cmd : 'mceAbbr'}); - ed.addButton('del', {title : 'xhtmlxtras.del_desc', cmd : 'mceDel'}); - ed.addButton('ins', {title : 'xhtmlxtras.ins_desc', cmd : 'mceIns'}); - ed.addButton('attribs', {title : 'xhtmlxtras.attribs_desc', cmd : 'mceAttributes'}); - - if (tinymce.isIE) { - function fix(ed, o) { - if (o.set) { - o.content = o.content.replace(/]+)>/gi, ''); - o.content = o.content.replace(/<\/abbr>/gi, ''); - } - }; - - ed.onBeforeSetContent.add(fix); - ed.onPostProcess.add(fix); - } - - ed.onNodeChange.add(function(ed, cm, n, co) { - n = ed.dom.getParent(n, 'CITE,ACRONYM,ABBR,DEL,INS'); - - cm.setDisabled('cite', co); - cm.setDisabled('acronym', co); - cm.setDisabled('abbr', co); - cm.setDisabled('del', co); - cm.setDisabled('ins', co); - cm.setDisabled('attribs', n && n.nodeName == 'BODY'); - cm.setActive('cite', 0); - cm.setActive('acronym', 0); - cm.setActive('abbr', 0); - cm.setActive('del', 0); - cm.setActive('ins', 0); - - // Activate all - if (n) { - do { - cm.setDisabled(n.nodeName.toLowerCase(), 0); - cm.setActive(n.nodeName.toLowerCase(), 1); - } while (n = n.parentNode); - } - }); - - ed.onPreInit.add(function() { - // Fixed IE issue where it can't handle these elements correctly - ed.dom.create('abbr'); - }); - }, - - getInfo : function() { - return { - longname : 'XHTML Xtras Plugin', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras', - version : tinymce.majorVersion + "." + tinymce.minorVersion - }; - } - }); - - // Register plugin - tinymce.PluginManager.add('xhtmlxtras', tinymce.plugins.XHTMLXtrasPlugin); +/** + * editor_plugin_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + tinymce.create('tinymce.plugins.XHTMLXtrasPlugin', { + init : function(ed, url) { + // Register commands + ed.addCommand('mceCite', function() { + ed.windowManager.open({ + file : url + '/cite.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.cite_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.cite_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAcronym', function() { + ed.windowManager.open({ + file : url + '/acronym.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAbbr', function() { + ed.windowManager.open({ + file : url + '/abbr.htm', + width : 350 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)), + height : 250 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceDel', function() { + ed.windowManager.open({ + file : url + '/del.htm', + width : 340 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)), + height : 310 + parseInt(ed.getLang('xhtmlxtras.del_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceIns', function() { + ed.windowManager.open({ + file : url + '/ins.htm', + width : 340 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)), + height : 310 + parseInt(ed.getLang('xhtmlxtras.ins_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + ed.addCommand('mceAttributes', function() { + ed.windowManager.open({ + file : url + '/attributes.htm', + width : 380 + parseInt(ed.getLang('xhtmlxtras.attr_delta_width', 0)), + height : 370 + parseInt(ed.getLang('xhtmlxtras.attr_delta_height', 0)), + inline : 1 + }, { + plugin_url : url + }); + }); + + // Register buttons + ed.addButton('cite', {title : 'xhtmlxtras.cite_desc', cmd : 'mceCite'}); + ed.addButton('acronym', {title : 'xhtmlxtras.acronym_desc', cmd : 'mceAcronym'}); + ed.addButton('abbr', {title : 'xhtmlxtras.abbr_desc', cmd : 'mceAbbr'}); + ed.addButton('del', {title : 'xhtmlxtras.del_desc', cmd : 'mceDel'}); + ed.addButton('ins', {title : 'xhtmlxtras.ins_desc', cmd : 'mceIns'}); + ed.addButton('attribs', {title : 'xhtmlxtras.attribs_desc', cmd : 'mceAttributes'}); + + ed.onNodeChange.add(function(ed, cm, n, co) { + n = ed.dom.getParent(n, 'CITE,ACRONYM,ABBR,DEL,INS'); + + cm.setDisabled('cite', co); + cm.setDisabled('acronym', co); + cm.setDisabled('abbr', co); + cm.setDisabled('del', co); + cm.setDisabled('ins', co); + cm.setDisabled('attribs', n && n.nodeName == 'BODY'); + cm.setActive('cite', 0); + cm.setActive('acronym', 0); + cm.setActive('abbr', 0); + cm.setActive('del', 0); + cm.setActive('ins', 0); + + // Activate all + if (n) { + do { + cm.setDisabled(n.nodeName.toLowerCase(), 0); + cm.setActive(n.nodeName.toLowerCase(), 1); + } while (n = n.parentNode); + } + }); + + ed.onPreInit.add(function() { + // Fixed IE issue where it can't handle these elements correctly + ed.dom.create('abbr'); + }); + }, + + getInfo : function() { + return { + longname : 'XHTML Xtras Plugin', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras', + version : tinymce.majorVersion + "." + tinymce.minorVersion + }; + } + }); + + // Register plugin + tinymce.PluginManager.add('xhtmlxtras', tinymce.plugins.XHTMLXtrasPlugin); })(); \ No newline at end of file diff --git a/js/tiny_mce/plugins/xhtmlxtras/ins.htm b/js/tiny_mce/plugins/xhtmlxtras/ins.htm index 6c5470cf..226e6053 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/ins.htm +++ b/js/tiny_mce/plugins/xhtmlxtras/ins.htm @@ -1,161 +1,162 @@ - - - - {#xhtmlxtras_dlg.title_ins_element} - - - - - - - - - -
    - - -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_general_tab} - - - - - - - - - -
    : - - - - - -
    -
    :
    -
    -
    - {#xhtmlxtras_dlg.fieldset_attrib_tab} - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    : - -
    :
    : - -
    : - -
    -
    -
    -
    -
    - {#xhtmlxtras_dlg.fieldset_events_tab} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    -
    -
    -
    -
    - - - -
    -
    - - + + + + {#xhtmlxtras_dlg.title_ins_element} + + + + + + + + + + +
    + + +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_general_tab} + + + + + + + + + +
    : + + + + + +
    +
    :
    +
    +
    + {#xhtmlxtras_dlg.fieldset_attrib_tab} + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    : + +
    :
    : + +
    : + +
    +
    +
    +
    +
    + {#xhtmlxtras_dlg.fieldset_events_tab} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    :
    +
    +
    +
    +
    + + + +
    +
    + + diff --git a/js/tiny_mce/plugins/xhtmlxtras/js/attributes.js b/js/tiny_mce/plugins/xhtmlxtras/js/attributes.js index d62a219e..9e9b07e6 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/js/attributes.js +++ b/js/tiny_mce/plugins/xhtmlxtras/js/attributes.js @@ -1,126 +1,111 @@ -/** - * attributes.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -function init() { - tinyMCEPopup.resizeToInnerSize(); - var inst = tinyMCEPopup.editor; - var dom = inst.dom; - var elm = inst.selection.getNode(); - var f = document.forms[0]; - var onclick = dom.getAttrib(elm, 'onclick'); - - setFormValue('title', dom.getAttrib(elm, 'title')); - setFormValue('id', dom.getAttrib(elm, 'id')); - setFormValue('style', dom.getAttrib(elm, "style")); - setFormValue('dir', dom.getAttrib(elm, 'dir')); - setFormValue('lang', dom.getAttrib(elm, 'lang')); - setFormValue('tabindex', dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); - setFormValue('accesskey', dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); - setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); - setFormValue('onblur', dom.getAttrib(elm, 'onblur')); - setFormValue('onclick', onclick); - setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); - setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); - setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); - setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); - setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); - setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); - setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); - setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); - setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); - className = dom.getAttrib(elm, 'class'); - - addClassesToList('classlist', 'advlink_styles'); - selectByValue(f, 'classlist', className, true); - - TinyMCE_EditableSelects.init(); -} - -function setFormValue(name, value) { - if(value && document.forms[0].elements[name]){ - document.forms[0].elements[name].value = value; - } -} - -function insertAction() { - var inst = tinyMCEPopup.editor; - var elm = inst.selection.getNode(); - - tinyMCEPopup.execCommand("mceBeginUndoLevel"); - setAllAttribs(elm); - tinyMCEPopup.execCommand("mceEndUndoLevel"); - tinyMCEPopup.close(); -} - -function setAttrib(elm, attrib, value) { - var formObj = document.forms[0]; - var valueElm = formObj.elements[attrib.toLowerCase()]; - var inst = tinyMCEPopup.editor; - var dom = inst.dom; - - if (typeof(value) == "undefined" || value == null) { - value = ""; - - if (valueElm) - value = valueElm.value; - } - - if (value != "") { - dom.setAttrib(elm, attrib.toLowerCase(), value); - - if (attrib == "style") - attrib = "style.cssText"; - - if (attrib.substring(0, 2) == 'on') - value = 'return true;' + value; - - if (attrib == "class") - attrib = "className"; - - elm[attrib]=value; - } else - elm.removeAttribute(attrib); -} - -function setAllAttribs(elm) { - var f = document.forms[0]; - - setAttrib(elm, 'title'); - setAttrib(elm, 'id'); - setAttrib(elm, 'style'); - setAttrib(elm, 'class', getSelectValue(f, 'classlist')); - setAttrib(elm, 'dir'); - setAttrib(elm, 'lang'); - setAttrib(elm, 'tabindex'); - setAttrib(elm, 'accesskey'); - setAttrib(elm, 'onfocus'); - setAttrib(elm, 'onblur'); - setAttrib(elm, 'onclick'); - setAttrib(elm, 'ondblclick'); - setAttrib(elm, 'onmousedown'); - setAttrib(elm, 'onmouseup'); - setAttrib(elm, 'onmouseover'); - setAttrib(elm, 'onmousemove'); - setAttrib(elm, 'onmouseout'); - setAttrib(elm, 'onkeypress'); - setAttrib(elm, 'onkeydown'); - setAttrib(elm, 'onkeyup'); - - // Refresh in old MSIE -// if (tinyMCE.isMSIE5) -// elm.outerHTML = elm.outerHTML; -} - -function insertAttribute() { - tinyMCEPopup.close(); -} - -tinyMCEPopup.onInit.add(init); -tinyMCEPopup.requireLangPack(); +/** + * attributes.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +function init() { + tinyMCEPopup.resizeToInnerSize(); + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + var elm = inst.selection.getNode(); + var f = document.forms[0]; + var onclick = dom.getAttrib(elm, 'onclick'); + + setFormValue('title', dom.getAttrib(elm, 'title')); + setFormValue('id', dom.getAttrib(elm, 'id')); + setFormValue('style', dom.getAttrib(elm, "style")); + setFormValue('dir', dom.getAttrib(elm, 'dir')); + setFormValue('lang', dom.getAttrib(elm, 'lang')); + setFormValue('tabindex', dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : "")); + setFormValue('accesskey', dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : "")); + setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', onclick); + setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); + className = dom.getAttrib(elm, 'class'); + + addClassesToList('classlist', 'advlink_styles'); + selectByValue(f, 'classlist', className, true); + + TinyMCE_EditableSelects.init(); +} + +function setFormValue(name, value) { + if(value && document.forms[0].elements[name]){ + document.forms[0].elements[name].value = value; + } +} + +function insertAction() { + var inst = tinyMCEPopup.editor; + var elm = inst.selection.getNode(); + + setAllAttribs(elm); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); +} + +function setAttrib(elm, attrib, value) { + var formObj = document.forms[0]; + var valueElm = formObj.elements[attrib.toLowerCase()]; + var inst = tinyMCEPopup.editor; + var dom = inst.dom; + + if (typeof(value) == "undefined" || value == null) { + value = ""; + + if (valueElm) + value = valueElm.value; + } + + dom.setAttrib(elm, attrib.toLowerCase(), value); +} + +function setAllAttribs(elm) { + var f = document.forms[0]; + + setAttrib(elm, 'title'); + setAttrib(elm, 'id'); + setAttrib(elm, 'style'); + setAttrib(elm, 'class', getSelectValue(f, 'classlist')); + setAttrib(elm, 'dir'); + setAttrib(elm, 'lang'); + setAttrib(elm, 'tabindex'); + setAttrib(elm, 'accesskey'); + setAttrib(elm, 'onfocus'); + setAttrib(elm, 'onblur'); + setAttrib(elm, 'onclick'); + setAttrib(elm, 'ondblclick'); + setAttrib(elm, 'onmousedown'); + setAttrib(elm, 'onmouseup'); + setAttrib(elm, 'onmouseover'); + setAttrib(elm, 'onmousemove'); + setAttrib(elm, 'onmouseout'); + setAttrib(elm, 'onkeypress'); + setAttrib(elm, 'onkeydown'); + setAttrib(elm, 'onkeyup'); + + // Refresh in old MSIE +// if (tinyMCE.isMSIE5) +// elm.outerHTML = elm.outerHTML; +} + +function insertAttribute() { + tinyMCEPopup.close(); +} + +tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); diff --git a/js/tiny_mce/plugins/xhtmlxtras/js/del.js b/js/tiny_mce/plugins/xhtmlxtras/js/del.js index 9e5d8c57..a5397f7e 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/js/del.js +++ b/js/tiny_mce/plugins/xhtmlxtras/js/del.js @@ -1,63 +1,53 @@ -/** - * del.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -function init() { - SXE.initElementDialog('del'); - if (SXE.currentAction == "update") { - setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime')); - setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite')); - SXE.showRemoveButton(); - } -} - -function setElementAttribs(elm) { - setAllCommonAttribs(elm); - setAttrib(elm, 'datetime'); - setAttrib(elm, 'cite'); -} - -function insertDel() { - var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'DEL'); - - tinyMCEPopup.execCommand('mceBeginUndoLevel'); - if (elm == null) { - var s = SXE.inst.selection.getContent(); - if(s.length > 0) { - insertInlineElement('del'); - var elementArray = tinymce.grep(SXE.inst.dom.select('del'), function(n) {return n.id == '#sxe_temp_del#';}); - for (var i=0; i 0) { + insertInlineElement('del'); + var elementArray = SXE.inst.dom.select('del[data-mce-new]'); + for (var i=0; i 0) { - tagName = element_name; - - insertInlineElement(element_name); - var elementArray = tinymce.grep(SXE.inst.dom.select(element_name)); - for (var i=0; i -1) ? true : false; -} - -SXE.removeClass = function(elm,cl) { - if(elm.className == null || elm.className == "" || !SXE.containsClass(elm,cl)) { - return true; - } - var classNames = elm.className.split(" "); - var newClassNames = ""; - for (var x = 0, cnl = classNames.length; x < cnl; x++) { - if (classNames[x] != cl) { - newClassNames += (classNames[x] + " "); - } - } - elm.className = newClassNames.substring(0,newClassNames.length-1); //removes extra space at the end -} - -SXE.addClass = function(elm,cl) { - if(!SXE.containsClass(elm,cl)) elm.className ? elm.className += " " + cl : elm.className = cl; - return true; -} - -function insertInlineElement(en) { - var ed = tinyMCEPopup.editor, dom = ed.dom; - - ed.getDoc().execCommand('FontName', false, 'mceinline'); - tinymce.each(dom.select('span,font'), function(n) { - if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') - dom.replace(dom.create(en, {_mce_new : 1}), n, 1); - }); -} +/** + * element_common.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +function initCommonAttributes(elm) { + var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom; + + // Setup form data for common element attributes + setFormValue('title', dom.getAttrib(elm, 'title')); + setFormValue('id', dom.getAttrib(elm, 'id')); + selectByValue(formObj, 'class', dom.getAttrib(elm, 'class'), true); + setFormValue('style', dom.getAttrib(elm, 'style')); + selectByValue(formObj, 'dir', dom.getAttrib(elm, 'dir')); + setFormValue('lang', dom.getAttrib(elm, 'lang')); + setFormValue('onfocus', dom.getAttrib(elm, 'onfocus')); + setFormValue('onblur', dom.getAttrib(elm, 'onblur')); + setFormValue('onclick', dom.getAttrib(elm, 'onclick')); + setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick')); + setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown')); + setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup')); + setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover')); + setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove')); + setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout')); + setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress')); + setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown')); + setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup')); +} + +function setFormValue(name, value) { + if(document.forms[0].elements[name]) document.forms[0].elements[name].value = value; +} + +function insertDateTime(id) { + document.getElementById(id).value = getDateTime(new Date(), "%Y-%m-%dT%H:%M:%S"); +} + +function getDateTime(d, fmt) { + fmt = fmt.replace("%D", "%m/%d/%y"); + fmt = fmt.replace("%r", "%I:%M:%S %p"); + fmt = fmt.replace("%Y", "" + d.getFullYear()); + fmt = fmt.replace("%y", "" + d.getYear()); + fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2)); + fmt = fmt.replace("%d", addZeros(d.getDate(), 2)); + fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2)); + fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2)); + fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2)); + fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1)); + fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM")); + fmt = fmt.replace("%%", "%"); + + return fmt; +} + +function addZeros(value, len) { + var i; + + value = "" + value; + + if (value.length < len) { + for (i=0; i<(len-value.length); i++) + value = "0" + value; + } + + return value; +} + +function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { + if (!form_obj || !form_obj.elements[field_name]) + return; + + var sel = form_obj.elements[field_name]; + + var found = false; + for (var i=0; i 0) { + tagName = element_name; + + insertInlineElement(element_name); + var elementArray = tinymce.grep(SXE.inst.dom.select(element_name)); + for (var i=0; i -1) ? true : false; +} + +SXE.removeClass = function(elm,cl) { + if(elm.className == null || elm.className == "" || !SXE.containsClass(elm,cl)) { + return true; + } + var classNames = elm.className.split(" "); + var newClassNames = ""; + for (var x = 0, cnl = classNames.length; x < cnl; x++) { + if (classNames[x] != cl) { + newClassNames += (classNames[x] + " "); + } + } + elm.className = newClassNames.substring(0,newClassNames.length-1); //removes extra space at the end +} + +SXE.addClass = function(elm,cl) { + if(!SXE.containsClass(elm,cl)) elm.className ? elm.className += " " + cl : elm.className = cl; + return true; +} + +function insertInlineElement(en) { + var ed = tinyMCEPopup.editor, dom = ed.dom; + + ed.getDoc().execCommand('FontName', false, 'mceinline'); + tinymce.each(dom.select('span,font'), function(n) { + if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') + dom.replace(dom.create(en, {'data-mce-new' : 1}), n, 1); + }); +} diff --git a/js/tiny_mce/plugins/xhtmlxtras/js/ins.js b/js/tiny_mce/plugins/xhtmlxtras/js/ins.js index 3774f0a1..71a8a261 100644 --- a/js/tiny_mce/plugins/xhtmlxtras/js/ins.js +++ b/js/tiny_mce/plugins/xhtmlxtras/js/ins.js @@ -1,62 +1,53 @@ -/** - * ins.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -function init() { - SXE.initElementDialog('ins'); - if (SXE.currentAction == "update") { - setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime')); - setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite')); - SXE.showRemoveButton(); - } -} - -function setElementAttribs(elm) { - setAllCommonAttribs(elm); - setAttrib(elm, 'datetime'); - setAttrib(elm, 'cite'); -} - -function insertIns() { - var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'INS'); - tinyMCEPopup.execCommand('mceBeginUndoLevel'); - if (elm == null) { - var s = SXE.inst.selection.getContent(); - if(s.length > 0) { - insertInlineElement('INS'); - var elementArray = tinymce.grep(SXE.inst.dom.select('ins'), function(n) {return n.id == '#sxe_temp_ins#';}); - for (var i=0; i 0) { + insertInlineElement('ins'); + var elementArray = SXE.inst.dom.select('ins[data-mce-new]'); + for (var i=0; i - - - {#advanced_dlg.about_title} - - - - - - - -
    -
    -

    {#advanced_dlg.about_title}

    -

    Version: ()

    -

    TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control released as Open Source under LGPL - by Moxiecode Systems AB. It has the ability to convert HTML TEXTAREA fields or other HTML elements to editor instances.

    -

    Copyright © 2003-2008, Moxiecode Systems AB, All rights reserved.

    -

    For more information about this software visit the TinyMCE website.

    - -
    - Got Moxie? - Hosted By Sourceforge - Also on freshmeat -
    -
    - -
    -
    -

    {#advanced_dlg.about_loaded}

    - -
    -
    - -

     

    -
    -
    - -
    -
    -
    -
    - -
    - -
    - - + + + + {#advanced_dlg.about_title} + + + + + + + +
    +
    +

    {#advanced_dlg.about_title}

    +

    Version: ()

    +

    TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control released as Open Source under LGPL + by Moxiecode Systems AB. It has the ability to convert HTML TEXTAREA fields or other HTML elements to editor instances.

    +

    Copyright © 2003-2008, Moxiecode Systems AB, All rights reserved.

    +

    For more information about this software visit the TinyMCE website.

    + +
    + Got Moxie? +
    +
    + +
    +
    +

    {#advanced_dlg.about_loaded}

    + +
    +
    + +

     

    +
    +
    + +
    +
    +
    +
    + +
    + +
    + + diff --git a/js/tiny_mce/themes/advanced/anchor.htm b/js/tiny_mce/themes/advanced/anchor.htm index 2bc63fcf..dc53312d 100644 --- a/js/tiny_mce/themes/advanced/anchor.htm +++ b/js/tiny_mce/themes/advanced/anchor.htm @@ -1,26 +1,26 @@ - - - - {#advanced_dlg.anchor_title} - - - - -
    - - - - - - - - -
    {#advanced_dlg.anchor_title}
    {#advanced_dlg.anchor_name}:
    - -
    - - -
    -
    - - + + + + {#advanced_dlg.anchor_title} + + + + +
    + + + + + + + + +
    {#advanced_dlg.anchor_title}
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/themes/advanced/charmap.htm b/js/tiny_mce/themes/advanced/charmap.htm index f11a38ad..12acfe18 100644 --- a/js/tiny_mce/themes/advanced/charmap.htm +++ b/js/tiny_mce/themes/advanced/charmap.htm @@ -1,53 +1,55 @@ - - - - {#advanced_dlg.charmap_title} - - - - - - - - - - - - - - - - -
    {#advanced_dlg.charmap_title}
    - - - - - - - - - -
     
     
    -
    - - - - - - - - - - - - - - - - -
    HTML-Code
     
     
    NUM-Code
     
    -
    - - - + + + + {#advanced_dlg.charmap_title} + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + +
     
     
    +
    + + + + + + + + + + + + + + + + +
     
     
     
    +
    {#advanced_dlg.charmap_usage}
    + + diff --git a/js/tiny_mce/themes/advanced/color_picker.htm b/js/tiny_mce/themes/advanced/color_picker.htm index 096e7550..e7f19aba 100644 --- a/js/tiny_mce/themes/advanced/color_picker.htm +++ b/js/tiny_mce/themes/advanced/color_picker.htm @@ -1,73 +1,74 @@ - - - - {#advanced_dlg.colorpicker_title} - - - - - -
    - - -
    -
    -
    - {#advanced_dlg.colorpicker_picker_title} -
    - - -
    - -
    - -
    -
    -
    -
    - -
    -
    - {#advanced_dlg.colorpicker_palette_title} -
    - -
    - -
    -
    -
    - -
    -
    - {#advanced_dlg.colorpicker_named_title} -
    - -
    - -
    - -
    - {#advanced_dlg.colorpicker_name} -
    -
    -
    -
    - -
    - - -
    - -
    - -
    -
    -
    - - + + + + {#advanced_dlg.colorpicker_title} + + + + + + +
    + + +
    +
    +
    + {#advanced_dlg.colorpicker_picker_title} +
    + + +
    + +
    + +
    +
    +
    +
    + +
    +
    + {#advanced_dlg.colorpicker_palette_title} +
    + +
    + +
    +
    +
    + +
    +
    + {#advanced_dlg.colorpicker_named_title} +
    + +
    + +
    + +
    + {#advanced_dlg.colorpicker_name} +
    +
    +
    +
    + +
    + + +
    + +
    + +
    +
    +
    + + diff --git a/js/tiny_mce/themes/advanced/editor_template.js b/js/tiny_mce/themes/advanced/editor_template.js index fc4abe18..812578d0 100644 --- a/js/tiny_mce/themes/advanced/editor_template.js +++ b/js/tiny_mce/themes/advanced/editor_template.js @@ -1 +1 @@ -(function(e){var d=e.DOM,b=e.dom.Event,h=e.extend,f=e.each,a=e.util.Cookie,g,c=e.explode;e.ThemeManager.requireLangPack("advanced");e.create("tinymce.themes.AdvancedTheme",{sizes:[8,10,12,14,18,24,36],controls:{bold:["bold_desc","Bold"],italic:["italic_desc","Italic"],underline:["underline_desc","Underline"],strikethrough:["striketrough_desc","Strikethrough"],justifyleft:["justifyleft_desc","JustifyLeft"],justifycenter:["justifycenter_desc","JustifyCenter"],justifyright:["justifyright_desc","JustifyRight"],justifyfull:["justifyfull_desc","JustifyFull"],bullist:["bullist_desc","InsertUnorderedList"],numlist:["numlist_desc","InsertOrderedList"],outdent:["outdent_desc","Outdent"],indent:["indent_desc","Indent"],cut:["cut_desc","Cut"],copy:["copy_desc","Copy"],paste:["paste_desc","Paste"],undo:["undo_desc","Undo"],redo:["redo_desc","Redo"],link:["link_desc","mceLink"],unlink:["unlink_desc","unlink"],image:["image_desc","mceImage"],cleanup:["cleanup_desc","mceCleanup"],help:["help_desc","mceHelp"],code:["code_desc","mceCodeEditor"],hr:["hr_desc","InsertHorizontalRule"],removeformat:["removeformat_desc","RemoveFormat"],sub:["sub_desc","subscript"],sup:["sup_desc","superscript"],forecolor:["forecolor_desc","ForeColor"],forecolorpicker:["forecolor_desc","mceForeColor"],backcolor:["backcolor_desc","HiliteColor"],backcolorpicker:["backcolor_desc","mceBackColor"],charmap:["charmap_desc","mceCharMap"],visualaid:["visualaid_desc","mceToggleVisualAid"],anchor:["anchor_desc","mceInsertAnchor"],newdocument:["newdocument_desc","mceNewDocument"],blockquote:["blockquote_desc","mceBlockQuote"]},stateControls:["bold","italic","underline","strikethrough","bullist","numlist","justifyleft","justifycenter","justifyright","justifyfull","sub","sup","blockquote"],init:function(j,k){var l=this,m,i,n;l.editor=j;l.url=k;l.onResolveName=new e.util.Dispatcher(this);l.settings=m=h({theme_advanced_path:true,theme_advanced_toolbar_location:"bottom",theme_advanced_buttons1:"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect",theme_advanced_buttons2:"bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code",theme_advanced_buttons3:"hr,removeformat,visualaid,|,sub,sup,|,charmap",theme_advanced_blockformats:"p,address,pre,h1,h2,h3,h4,h5,h6",theme_advanced_toolbar_align:"center",theme_advanced_fonts:"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",theme_advanced_more_colors:1,theme_advanced_row_height:23,theme_advanced_resize_horizontal:1,theme_advanced_resizing_use_cookie:1,theme_advanced_font_sizes:"1,2,3,4,5,6,7",readonly:j.settings.readonly},j.settings);if(!m.font_size_style_values){m.font_size_style_values="8pt,10pt,12pt,14pt,18pt,24pt,36pt"}if(e.is(m.theme_advanced_font_sizes,"string")){m.font_size_style_values=e.explode(m.font_size_style_values);m.font_size_classes=e.explode(m.font_size_classes||"");n={};j.settings.theme_advanced_font_sizes=m.theme_advanced_font_sizes;f(j.getParam("theme_advanced_font_sizes","","hash"),function(q,p){var o;if(p==q&&q>=1&&q<=7){p=q+" ("+l.sizes[q-1]+"pt)";o=m.font_size_classes[q-1];q=m.font_size_style_values[q-1]||(l.sizes[q-1]+"pt")}if(/^\s*\./.test(q)){o=q.replace(/\./g,"")}n[p]=o?{"class":o}:{fontSize:q}});m.theme_advanced_font_sizes=n}if((i=m.theme_advanced_path_location)&&i!="none"){m.theme_advanced_statusbar_location=m.theme_advanced_path_location}if(m.theme_advanced_statusbar_location=="none"){m.theme_advanced_statusbar_location=0}j.onInit.add(function(){if(!j.settings.readonly){j.onNodeChange.add(l._nodeChanged,l)}if(j.settings.content_css!==false){j.dom.loadCSS(j.baseURI.toAbsolute("themes/advanced/skins/"+j.settings.skin+"/content.css"))}});j.onSetProgressState.add(function(q,o,r){var s,t=q.id,p;if(o){l.progressTimer=setTimeout(function(){s=q.getContainer();s=s.insertBefore(d.create("DIV",{style:"position:relative"}),s.firstChild);p=d.get(q.id+"_tbl");d.add(s,"div",{id:t+"_blocker","class":"mceBlocker",style:{width:p.clientWidth+2,height:p.clientHeight+2}});d.add(s,"div",{id:t+"_progress","class":"mceProgress",style:{left:p.clientWidth/2,top:p.clientHeight/2}})},r||0)}else{d.remove(t+"_blocker");d.remove(t+"_progress");clearTimeout(l.progressTimer)}});d.loadCSS(m.editor_css?j.documentBaseURI.toAbsolute(m.editor_css):k+"/skins/"+j.settings.skin+"/ui.css");if(m.skin_variant){d.loadCSS(k+"/skins/"+j.settings.skin+"/ui_"+m.skin_variant+".css")}},createControl:function(l,i){var j,k;if(k=i.createControl(l)){return k}switch(l){case"styleselect":return this._createStyleSelect();case"formatselect":return this._createBlockFormats();case"fontselect":return this._createFontSelect();case"fontsizeselect":return this._createFontSizeSelect();case"forecolor":return this._createForeColorMenu();case"backcolor":return this._createBackColorMenu()}if((j=this.controls[l])){return i.createButton(l,{title:"advanced."+j[0],cmd:j[1],ui:j[2],value:j[3]})}},execCommand:function(k,j,l){var i=this["_"+k];if(i){i.call(this,j,l);return true}return false},_importClasses:function(k){var i=this.editor,j=i.controlManager.get("styleselect");if(j.getLength()==0){f(i.dom.getClasses(),function(n,l){var m="style_"+l;i.formatter.register(m,{inline:"span",classes:n["class"],selector:"*"});j.add(n["class"],m)})}},_createStyleSelect:function(m){var k=this,i=k.editor,j=i.controlManager,l;l=j.createListBox("styleselect",{title:"advanced.style_select",onselect:function(n){i.execCommand("mceToggleFormat",false,n);return false}});i.onInit.add(function(){var o=0,n=i.getParam("style_formats");if(n){f(n,function(p){var q,r=0;f(p,function(){r++});if(r>1){q=p.name=p.name||"style_"+(o++);i.formatter.register(q,p);l.add(p.title,q)}else{l.add(p.title)}})}else{f(i.getParam("theme_advanced_styles","","hash"),function(r,q){var p;if(r){p="style_"+(o++);i.formatter.register(p,{inline:"span",classes:r});l.add(k.editor.translate(q),p)}})}});if(l.getLength()==0){l.onPostRender.add(function(o,p){if(!l.NativeListBox){b.add(p.id+"_text","focus",k._importClasses,k);b.add(p.id+"_text","mousedown",k._importClasses,k);b.add(p.id+"_open","focus",k._importClasses,k);b.add(p.id+"_open","mousedown",k._importClasses,k)}else{b.add(p.id,"focus",k._importClasses,k)}})}return l},_createFontSelect:function(){var k,j=this,i=j.editor;k=i.controlManager.createListBox("fontselect",{title:"advanced.fontdefault",onselect:function(l){i.execCommand("FontName",false,l);return false}});if(k){f(i.getParam("theme_advanced_fonts",j.settings.theme_advanced_fonts,"hash"),function(m,l){k.add(i.translate(l),m,{style:m.indexOf("dings")==-1?"font-family:"+m:""})})}return k},_createFontSizeSelect:function(){var m=this,k=m.editor,n,l=0,j=[];n=k.controlManager.createListBox("fontsizeselect",{title:"advanced.font_size",onselect:function(i){if(i.fontSize){k.execCommand("FontSize",false,i.fontSize)}else{f(m.settings.theme_advanced_font_sizes,function(p,o){if(p["class"]){j.push(p["class"])}});k.editorCommands._applyInlineStyle("span",{"class":i["class"]},{check_classes:j})}return false}});if(n){f(m.settings.theme_advanced_font_sizes,function(o,i){var p=o.fontSize;if(p>=1&&p<=7){p=m.sizes[parseInt(p)-1]+"pt"}n.add(i,o,{style:"font-size:"+p,"class":"mceFontSize"+(l++)+(" "+(o["class"]||""))})})}return n},_createBlockFormats:function(){var k,i={p:"advanced.paragraph",address:"advanced.address",pre:"advanced.pre",h1:"advanced.h1",h2:"advanced.h2",h3:"advanced.h3",h4:"advanced.h4",h5:"advanced.h5",h6:"advanced.h6",div:"advanced.div",blockquote:"advanced.blockquote",code:"advanced.code",dt:"advanced.dt",dd:"advanced.dd",samp:"advanced.samp"},j=this;k=j.editor.controlManager.createListBox("formatselect",{title:"advanced.block",cmd:"FormatBlock"});if(k){f(j.editor.getParam("theme_advanced_blockformats",j.settings.theme_advanced_blockformats,"hash"),function(m,l){k.add(j.editor.translate(l!=m?l:i[m]),m,{"class":"mce_formatPreview mce_"+m})})}return k},_createForeColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_text_colors){l.colors=i}if(k.theme_advanced_default_foreground_color){l.default_color=k.theme_advanced_default_foreground_color}l.title="advanced.forecolor_desc";l.cmd="ForeColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("forecolor",l);return m},_createBackColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_background_colors){l.colors=i}if(k.theme_advanced_default_background_color){l.default_color=k.theme_advanced_default_background_color}l.title="advanced.backcolor_desc";l.cmd="HiliteColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("backcolor",l);return m},renderUI:function(k){var m,l,q,v=this,r=v.editor,w=v.settings,u,j,i;m=j=d.create("span",{id:r.id+"_parent","class":"mceEditor "+r.settings.skin+"Skin"+(w.skin_variant?" "+r.settings.skin+"Skin"+v._ufirst(w.skin_variant):"")});if(!d.boxModel){m=d.add(m,"div",{"class":"mceOldBoxModel"})}m=u=d.add(m,"table",{id:r.id+"_tbl","class":"mceLayout",cellSpacing:0,cellPadding:0});m=q=d.add(m,"tbody");switch((w.theme_advanced_layout_manager||"").toLowerCase()){case"rowlayout":l=v._rowLayout(w,q,k);break;case"customlayout":l=r.execCallback("theme_advanced_custom_layout",w,q,k,j);break;default:l=v._simpleLayout(w,q,k,j)}m=k.targetNode;i=d.stdMode?u.getElementsByTagName("tr"):u.rows;d.addClass(i[0],"mceFirst");d.addClass(i[i.length-1],"mceLast");f(d.select("tr",q),function(o){d.addClass(o.firstChild,"mceFirst");d.addClass(o.childNodes[o.childNodes.length-1],"mceLast")});if(d.get(w.theme_advanced_toolbar_container)){d.get(w.theme_advanced_toolbar_container).appendChild(j)}else{d.insertAfter(j,m)}b.add(r.id+"_path_row","click",function(n){n=n.target;if(n.nodeName=="A"){v._sel(n.className.replace(/^.*mcePath_([0-9]+).*$/,"$1"));return b.cancel(n)}});if(!r.getParam("accessibility_focus")){b.add(d.add(j,"a",{href:"#"},""),"focus",function(){tinyMCE.get(r.id).focus()})}if(w.theme_advanced_toolbar_location=="external"){k.deltaHeight=0}v.deltaHeight=k.deltaHeight;k.targetNode=null;return{iframeContainer:l,editorContainer:r.id+"_parent",sizeContainer:u,deltaHeight:k.deltaHeight}},getInfo:function(){return{longname:"Advanced theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:e.majorVersion+"."+e.minorVersion}},resizeBy:function(i,j){var k=d.get(this.editor.id+"_tbl");this.resizeTo(k.clientWidth+i,k.clientHeight+j)},resizeTo:function(i,l){var j=this.editor,k=this.settings,m=d.get(j.id+"_tbl"),n=d.get(j.id+"_ifr");i=Math.max(k.theme_advanced_resizing_min_width||100,i);l=Math.max(k.theme_advanced_resizing_min_height||100,l);i=Math.min(k.theme_advanced_resizing_max_width||65535,i);l=Math.min(k.theme_advanced_resizing_max_height||65535,l);d.setStyle(m,"height","");d.setStyle(n,"height",l);if(k.theme_advanced_resize_horizontal){d.setStyle(m,"width","");d.setStyle(n,"width",i);if(i"))}q.push(d.createHTML("a",{href:"#",accesskey:"q",title:r.getLang("advanced.toolbar_focus")},""));for(p=1;(y=A["theme_advanced_buttons"+p]);p++){m=j.createToolbar("toolbar"+p,{"class":"mceToolbarRow"+p});if(A["theme_advanced_buttons"+p+"_add"]){y+=","+A["theme_advanced_buttons"+p+"_add"]}if(A["theme_advanced_buttons"+p+"_add_before"]){y=A["theme_advanced_buttons"+p+"_add_before"]+","+y}z._addControls(y,m);q.push(m.renderHTML());k.deltaHeight-=A.theme_advanced_row_height}q.push(d.createHTML("a",{href:"#",accesskey:"z",title:r.getLang("advanced.toolbar_focus"),onfocus:"tinyMCE.getInstanceById('"+r.id+"').focus();"},""));d.setHTML(l,q.join(""))},_addStatusBar:function(m,j){var k,v=this,p=v.editor,w=v.settings,i,q,u,l;k=d.add(m,"tr");k=l=d.add(k,"td",{"class":"mceStatusbar"});k=d.add(k,"div",{id:p.id+"_path_row"},w.theme_advanced_path?p.translate("advanced.path")+": ":" ");d.add(k,"a",{href:"#",accesskey:"x"});if(w.theme_advanced_resizing){d.add(l,"a",{id:p.id+"_resize",href:"javascript:;",onclick:"return false;","class":"mceResize"});if(w.theme_advanced_resizing_use_cookie){p.onPostRender.add(function(){var n=a.getHash("TinyMCE_"+p.id+"_size"),r=d.get(p.id+"_tbl");if(!n){return}v.resizeTo(n.cw,n.ch)})}p.onPostRender.add(function(){b.add(p.id+"_resize","mousedown",function(D){var t,r,s,o,C,z,A,F,n,E,x;function y(G){n=A+(G.screenX-C);E=F+(G.screenY-z);v.resizeTo(n,E)}function B(G){b.remove(d.doc,"mousemove",t);b.remove(p.getDoc(),"mousemove",r);b.remove(d.doc,"mouseup",s);b.remove(p.getDoc(),"mouseup",o);if(w.theme_advanced_resizing_use_cookie){a.setHash("TinyMCE_"+p.id+"_size",{cw:n,ch:E})}}D.preventDefault();C=D.screenX;z=D.screenY;x=d.get(v.editor.id+"_ifr");A=n=x.clientWidth;F=E=x.clientHeight;t=b.add(d.doc,"mousemove",y);r=b.add(p.getDoc(),"mousemove",y);s=b.add(d.doc,"mouseup",B);o=b.add(p.getDoc(),"mouseup",B)})})}j.deltaHeight-=21;k=m=null},_nodeChanged:function(r,z,l,x,j){var C=this,i,y=0,B,u,D=C.settings,A,k,w,m,q;e.each(C.stateControls,function(n){z.setActive(n,r.queryCommandState(C.controls[n][1]))});function o(p){var s,n=j.parents,t=p;if(typeof(p)=="string"){t=function(v){return v.nodeName==p}}for(s=0;s=1&&q<=7){p=q+" ("+l.sizes[q-1]+"pt)";o=m.font_size_classes[q-1];q=m.font_size_style_values[q-1]||(l.sizes[q-1]+"pt")}if(/^\s*\./.test(q)){o=q.replace(/\./g,"")}n[p]=o?{"class":o}:{fontSize:q}});m.theme_advanced_font_sizes=n}if((i=m.theme_advanced_path_location)&&i!="none"){m.theme_advanced_statusbar_location=m.theme_advanced_path_location}if(m.theme_advanced_statusbar_location=="none"){m.theme_advanced_statusbar_location=0}if(j.settings.content_css!==false){j.contentCSS.push(j.baseURI.toAbsolute(k+"/skins/"+j.settings.skin+"/content.css"))}j.onInit.add(function(){if(!j.settings.readonly){j.onNodeChange.add(l._nodeChanged,l);j.onKeyUp.add(l._updateUndoStatus,l);j.onMouseUp.add(l._updateUndoStatus,l);j.dom.bind(j.dom.getRoot(),"dragend",function(){l._updateUndoStatus(j)})}});j.onSetProgressState.add(function(q,o,r){var s,t=q.id,p;if(o){l.progressTimer=setTimeout(function(){s=q.getContainer();s=s.insertBefore(d.create("DIV",{style:"position:relative"}),s.firstChild);p=d.get(q.id+"_tbl");d.add(s,"div",{id:t+"_blocker","class":"mceBlocker",style:{width:p.clientWidth+2,height:p.clientHeight+2}});d.add(s,"div",{id:t+"_progress","class":"mceProgress",style:{left:p.clientWidth/2,top:p.clientHeight/2}})},r||0)}else{d.remove(t+"_blocker");d.remove(t+"_progress");clearTimeout(l.progressTimer)}});d.loadCSS(m.editor_css?j.documentBaseURI.toAbsolute(m.editor_css):k+"/skins/"+j.settings.skin+"/ui.css");if(m.skin_variant){d.loadCSS(k+"/skins/"+j.settings.skin+"/ui_"+m.skin_variant+".css")}},_isHighContrast:function(){var i,j=d.add(d.getRoot(),"div",{style:"background-color: rgb(171,239,86);"});i=(d.getStyle(j,"background-color",true)+"").toLowerCase().replace(/ /g,"");d.remove(j);return i!="rgb(171,239,86)"&&i!="#abef56"},createControl:function(l,i){var j,k;if(k=i.createControl(l)){return k}switch(l){case"styleselect":return this._createStyleSelect();case"formatselect":return this._createBlockFormats();case"fontselect":return this._createFontSelect();case"fontsizeselect":return this._createFontSizeSelect();case"forecolor":return this._createForeColorMenu();case"backcolor":return this._createBackColorMenu()}if((j=this.controls[l])){return i.createButton(l,{title:"advanced."+j[0],cmd:j[1],ui:j[2],value:j[3]})}},execCommand:function(k,j,l){var i=this["_"+k];if(i){i.call(this,j,l);return true}return false},_importClasses:function(k){var i=this.editor,j=i.controlManager.get("styleselect");if(j.getLength()==0){f(i.dom.getClasses(),function(n,l){var m="style_"+l;i.formatter.register(m,{inline:"span",attributes:{"class":n["class"]},selector:"*"});j.add(n["class"],m)})}},_createStyleSelect:function(m){var k=this,i=k.editor,j=i.controlManager,l;l=j.createListBox("styleselect",{title:"advanced.style_select",onselect:function(o){var p,n=[];f(l.items,function(q){n.push(q.value)});i.focus();i.undoManager.add();p=i.formatter.matchAll(n);if(!o||p[0]==o){if(p[0]){i.formatter.remove(p[0])}}else{i.formatter.apply(o)}i.undoManager.add();i.nodeChanged();return false}});i.onInit.add(function(){var o=0,n=i.getParam("style_formats");if(n){f(n,function(p){var q,r=0;f(p,function(){r++});if(r>1){q=p.name=p.name||"style_"+(o++);i.formatter.register(q,p);l.add(p.title,q)}else{l.add(p.title)}})}else{f(i.getParam("theme_advanced_styles","","hash"),function(r,q){var p;if(r){p="style_"+(o++);i.formatter.register(p,{inline:"span",classes:r,selector:"*"});l.add(k.editor.translate(q),p)}})}});if(l.getLength()==0){l.onPostRender.add(function(o,p){if(!l.NativeListBox){b.add(p.id+"_text","focus",k._importClasses,k);b.add(p.id+"_text","mousedown",k._importClasses,k);b.add(p.id+"_open","focus",k._importClasses,k);b.add(p.id+"_open","mousedown",k._importClasses,k)}else{b.add(p.id,"focus",k._importClasses,k)}})}return l},_createFontSelect:function(){var k,j=this,i=j.editor;k=i.controlManager.createListBox("fontselect",{title:"advanced.fontdefault",onselect:function(l){var m=k.items[k.selectedIndex];if(!l&&m){i.execCommand("FontName",false,m.value);return}i.execCommand("FontName",false,l);k.select(function(n){return l==n});if(m&&m.value==l){k.select(null)}return false}});if(k){f(i.getParam("theme_advanced_fonts",j.settings.theme_advanced_fonts,"hash"),function(m,l){k.add(i.translate(l),m,{style:m.indexOf("dings")==-1?"font-family:"+m:""})})}return k},_createFontSizeSelect:function(){var m=this,k=m.editor,n,l=0,j=[];n=k.controlManager.createListBox("fontsizeselect",{title:"advanced.font_size",onselect:function(i){var o=n.items[n.selectedIndex];if(!i&&o){o=o.value;if(o["class"]){k.formatter.toggle("fontsize_class",{value:o["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,o.fontSize)}return}if(i["class"]){k.focus();k.undoManager.add();k.formatter.toggle("fontsize_class",{value:i["class"]});k.undoManager.add();k.nodeChanged()}else{k.execCommand("FontSize",false,i.fontSize)}n.select(function(p){return i==p});if(o&&(o.value.fontSize==i.fontSize||o.value["class"]==i["class"])){n.select(null)}return false}});if(n){f(m.settings.theme_advanced_font_sizes,function(o,i){var p=o.fontSize;if(p>=1&&p<=7){p=m.sizes[parseInt(p)-1]+"pt"}n.add(i,o,{style:"font-size:"+p,"class":"mceFontSize"+(l++)+(" "+(o["class"]||""))})})}return n},_createBlockFormats:function(){var k,i={p:"advanced.paragraph",address:"advanced.address",pre:"advanced.pre",h1:"advanced.h1",h2:"advanced.h2",h3:"advanced.h3",h4:"advanced.h4",h5:"advanced.h5",h6:"advanced.h6",div:"advanced.div",blockquote:"advanced.blockquote",code:"advanced.code",dt:"advanced.dt",dd:"advanced.dd",samp:"advanced.samp"},j=this;k=j.editor.controlManager.createListBox("formatselect",{title:"advanced.block",onselect:function(l){j.editor.execCommand("FormatBlock",false,l);return false}});if(k){f(j.editor.getParam("theme_advanced_blockformats",j.settings.theme_advanced_blockformats,"hash"),function(m,l){k.add(j.editor.translate(l!=m?l:i[m]),m,{"class":"mce_formatPreview mce_"+m})})}return k},_createForeColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_text_colors){l.colors=i}if(k.theme_advanced_default_foreground_color){l.default_color=k.theme_advanced_default_foreground_color}l.title="advanced.forecolor_desc";l.cmd="ForeColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("forecolor",l);return m},_createBackColorMenu:function(){var m,j=this,k=j.settings,l={},i;if(k.theme_advanced_more_colors){l.more_colors_func=function(){j._mceColorPicker(0,{color:m.value,func:function(n){m.setColor(n)}})}}if(i=k.theme_advanced_background_colors){l.colors=i}if(k.theme_advanced_default_background_color){l.default_color=k.theme_advanced_default_background_color}l.title="advanced.backcolor_desc";l.cmd="HiliteColor";l.scope=this;m=j.editor.controlManager.createColorSplitButton("backcolor",l);return m},renderUI:function(k){var m,l,q,v=this,r=v.editor,w=v.settings,u,j,i;if(r.settings){r.settings.aria_label=w.aria_label+r.getLang("advanced.help_shortcut")}m=j=d.create("span",{role:"application","aria-labelledby":r.id+"_voice",id:r.id+"_parent","class":"mceEditor "+r.settings.skin+"Skin"+(w.skin_variant?" "+r.settings.skin+"Skin"+v._ufirst(w.skin_variant):"")});d.add(m,"span",{"class":"mceVoiceLabel",style:"display:none;",id:r.id+"_voice"},w.aria_label);if(!d.boxModel){m=d.add(m,"div",{"class":"mceOldBoxModel"})}m=u=d.add(m,"table",{role:"presentation",id:r.id+"_tbl","class":"mceLayout",cellSpacing:0,cellPadding:0});m=q=d.add(m,"tbody");switch((w.theme_advanced_layout_manager||"").toLowerCase()){case"rowlayout":l=v._rowLayout(w,q,k);break;case"customlayout":l=r.execCallback("theme_advanced_custom_layout",w,q,k,j);break;default:l=v._simpleLayout(w,q,k,j)}m=k.targetNode;i=u.rows;d.addClass(i[0],"mceFirst");d.addClass(i[i.length-1],"mceLast");f(d.select("tr",q),function(o){d.addClass(o.firstChild,"mceFirst");d.addClass(o.childNodes[o.childNodes.length-1],"mceLast")});if(d.get(w.theme_advanced_toolbar_container)){d.get(w.theme_advanced_toolbar_container).appendChild(j)}else{d.insertAfter(j,m)}b.add(r.id+"_path_row","click",function(n){n=n.target;if(n.nodeName=="A"){v._sel(n.className.replace(/^.*mcePath_([0-9]+).*$/,"$1"));return b.cancel(n)}});if(!r.getParam("accessibility_focus")){b.add(d.add(j,"a",{href:"#"},""),"focus",function(){tinyMCE.get(r.id).focus()})}if(w.theme_advanced_toolbar_location=="external"){k.deltaHeight=0}v.deltaHeight=k.deltaHeight;k.targetNode=null;r.onKeyDown.add(function(p,n){var s=121,o=122;if(n.altKey){if(n.keyCode===s){if(e.isWebKit){window.focus()}v.toolbarGroup.focus();return b.cancel(n)}else{if(n.keyCode===o){d.get(p.id+"_path_row").focus();return b.cancel(n)}}}});r.addShortcut("alt+0","","mceShortcuts",v);return{iframeContainer:l,editorContainer:r.id+"_parent",sizeContainer:u,deltaHeight:k.deltaHeight}},getInfo:function(){return{longname:"Advanced theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:e.majorVersion+"."+e.minorVersion}},resizeBy:function(i,j){var k=d.get(this.editor.id+"_ifr");this.resizeTo(k.clientWidth+i,k.clientHeight+j)},resizeTo:function(i,m,k){var j=this.editor,l=this.settings,n=d.get(j.id+"_tbl"),o=d.get(j.id+"_ifr");i=Math.max(l.theme_advanced_resizing_min_width||100,i);m=Math.max(l.theme_advanced_resizing_min_height||100,m);i=Math.min(l.theme_advanced_resizing_max_width||65535,i);m=Math.min(l.theme_advanced_resizing_max_height||65535,m);d.setStyle(n,"height","");d.setStyle(o,"height",m);if(l.theme_advanced_resize_horizontal){d.setStyle(n,"width","");d.setStyle(o,"width",i);if(i"));d.setHTML(l,q.join(""))},_addStatusBar:function(m,j){var k,v=this,p=v.editor,w=v.settings,i,q,u,l;k=d.add(m,"tr");k=l=d.add(k,"td",{"class":"mceStatusbar"});k=d.add(k,"div",{id:p.id+"_path_row",role:"group","aria-labelledby":p.id+"_path_voice"});if(w.theme_advanced_path){d.add(k,"span",{id:p.id+"_path_voice"},p.translate("advanced.path"));d.add(k,"span",{},": ")}else{d.add(k,"span",{}," ")}if(w.theme_advanced_resizing){d.add(l,"a",{id:p.id+"_resize",href:"javascript:;",onclick:"return false;","class":"mceResize",tabIndex:"-1"});if(w.theme_advanced_resizing_use_cookie){p.onPostRender.add(function(){var n=a.getHash("TinyMCE_"+p.id+"_size"),r=d.get(p.id+"_tbl");if(!n){return}v.resizeTo(n.cw,n.ch)})}p.onPostRender.add(function(){b.add(p.id+"_resize","click",function(n){n.preventDefault()});b.add(p.id+"_resize","mousedown",function(D){var t,r,s,o,C,z,A,F,n,E,x;function y(G){G.preventDefault();n=A+(G.screenX-C);E=F+(G.screenY-z);v.resizeTo(n,E)}function B(G){b.remove(d.doc,"mousemove",t);b.remove(p.getDoc(),"mousemove",r);b.remove(d.doc,"mouseup",s);b.remove(p.getDoc(),"mouseup",o);n=A+(G.screenX-C);E=F+(G.screenY-z);v.resizeTo(n,E,true)}D.preventDefault();C=D.screenX;z=D.screenY;x=d.get(v.editor.id+"_ifr");A=n=x.clientWidth;F=E=x.clientHeight;t=b.add(d.doc,"mousemove",y);r=b.add(p.getDoc(),"mousemove",y);s=b.add(d.doc,"mouseup",B);o=b.add(p.getDoc(),"mouseup",B)})})}j.deltaHeight-=21;k=m=null},_updateUndoStatus:function(j){var i=j.controlManager,k=j.undoManager;i.setDisabled("undo",!k.hasUndo()&&!k.typing);i.setDisabled("redo",!k.hasRedo())},_nodeChanged:function(m,r,D,q,E){var y=this,C,F=0,x,G,z=y.settings,w,k,u,B,l,j,i;e.each(y.stateControls,function(n){r.setActive(n,m.queryCommandState(y.controls[n][1]))});function o(p){var s,n=E.parents,t=p;if(typeof(p)=="string"){t=function(v){return v.nodeName==p}}for(s=0;s0){y.statusKeyboardNavigation=new e.ui.KeyboardNavigation({root:m.id+"_path_row",items:d.select("a",C),excludeFromTabOrder:true,onCancel:function(){m.focus()}},d)}}},_sel:function(i){this.editor.execCommand("mceSelectNodeDepth",false,i)},_mceInsertAnchor:function(k,j){var i=this.editor;i.windowManager.open({url:this.url+"/anchor.htm",width:320+parseInt(i.getLang("advanced.anchor_delta_width",0)),height:90+parseInt(i.getLang("advanced.anchor_delta_height",0)),inline:true},{theme_url:this.url})},_mceCharMap:function(){var i=this.editor;i.windowManager.open({url:this.url+"/charmap.htm",width:550+parseInt(i.getLang("advanced.charmap_delta_width",0)),height:260+parseInt(i.getLang("advanced.charmap_delta_height",0)),inline:true},{theme_url:this.url})},_mceHelp:function(){var i=this.editor;i.windowManager.open({url:this.url+"/about.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceShortcuts:function(){var i=this.editor;i.windowManager.open({url:this.url+"/shortcuts.htm",width:480,height:380,inline:true},{theme_url:this.url})},_mceColorPicker:function(k,j){var i=this.editor;j=j||{};i.windowManager.open({url:this.url+"/color_picker.htm",width:375+parseInt(i.getLang("advanced.colorpicker_delta_width",0)),height:250+parseInt(i.getLang("advanced.colorpicker_delta_height",0)),close_previous:false,inline:true},{input_color:j.color,func:j.func,theme_url:this.url})},_mceCodeEditor:function(j,k){var i=this.editor;i.windowManager.open({url:this.url+"/source_editor.htm",width:parseInt(i.getParam("theme_advanced_source_editor_width",720)),height:parseInt(i.getParam("theme_advanced_source_editor_height",580)),inline:true,resizable:true,maximizable:true},{theme_url:this.url})},_mceImage:function(j,k){var i=this.editor;if(i.dom.getAttrib(i.selection.getNode(),"class").indexOf("mceItem")!=-1){return}i.windowManager.open({url:this.url+"/image.htm",width:355+parseInt(i.getLang("advanced.image_delta_width",0)),height:275+parseInt(i.getLang("advanced.image_delta_height",0)),inline:true},{theme_url:this.url})},_mceLink:function(j,k){var i=this.editor;i.windowManager.open({url:this.url+"/link.htm",width:310+parseInt(i.getLang("advanced.link_delta_width",0)),height:200+parseInt(i.getLang("advanced.link_delta_height",0)),inline:true},{theme_url:this.url})},_mceNewDocument:function(){var i=this.editor;i.windowManager.confirm("advanced.newdocument",function(j){if(j){i.execCommand("mceSetContent",false,"")}})},_mceForeColor:function(){var i=this;this._mceColorPicker(0,{color:i.fgColor,func:function(j){i.fgColor=j;i.editor.execCommand("ForeColor",false,j)}})},_mceBackColor:function(){var i=this;this._mceColorPicker(0,{color:i.bgColor,func:function(j){i.bgColor=j;i.editor.execCommand("HiliteColor",false,j)}})},_ufirst:function(i){return i.substring(0,1).toUpperCase()+i.substring(1)}});e.ThemeManager.add("advanced",e.themes.AdvancedTheme)}(tinymce)); \ No newline at end of file diff --git a/js/tiny_mce/themes/advanced/editor_template_src.js b/js/tiny_mce/themes/advanced/editor_template_src.js index b7dc85b8..5f50bd50 100644 --- a/js/tiny_mce/themes/advanced/editor_template_src.js +++ b/js/tiny_mce/themes/advanced/editor_template_src.js @@ -1,1167 +1,1362 @@ -/** - * editor_template_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function(tinymce) { - var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, each = tinymce.each, Cookie = tinymce.util.Cookie, lastExtID, explode = tinymce.explode; - - // Tell it to load theme specific language pack(s) - tinymce.ThemeManager.requireLangPack('advanced'); - - tinymce.create('tinymce.themes.AdvancedTheme', { - sizes : [8, 10, 12, 14, 18, 24, 36], - - // Control name lookup, format: title, command - controls : { - bold : ['bold_desc', 'Bold'], - italic : ['italic_desc', 'Italic'], - underline : ['underline_desc', 'Underline'], - strikethrough : ['striketrough_desc', 'Strikethrough'], - justifyleft : ['justifyleft_desc', 'JustifyLeft'], - justifycenter : ['justifycenter_desc', 'JustifyCenter'], - justifyright : ['justifyright_desc', 'JustifyRight'], - justifyfull : ['justifyfull_desc', 'JustifyFull'], - bullist : ['bullist_desc', 'InsertUnorderedList'], - numlist : ['numlist_desc', 'InsertOrderedList'], - outdent : ['outdent_desc', 'Outdent'], - indent : ['indent_desc', 'Indent'], - cut : ['cut_desc', 'Cut'], - copy : ['copy_desc', 'Copy'], - paste : ['paste_desc', 'Paste'], - undo : ['undo_desc', 'Undo'], - redo : ['redo_desc', 'Redo'], - link : ['link_desc', 'mceLink'], - unlink : ['unlink_desc', 'unlink'], - image : ['image_desc', 'mceImage'], - cleanup : ['cleanup_desc', 'mceCleanup'], - help : ['help_desc', 'mceHelp'], - code : ['code_desc', 'mceCodeEditor'], - hr : ['hr_desc', 'InsertHorizontalRule'], - removeformat : ['removeformat_desc', 'RemoveFormat'], - sub : ['sub_desc', 'subscript'], - sup : ['sup_desc', 'superscript'], - forecolor : ['forecolor_desc', 'ForeColor'], - forecolorpicker : ['forecolor_desc', 'mceForeColor'], - backcolor : ['backcolor_desc', 'HiliteColor'], - backcolorpicker : ['backcolor_desc', 'mceBackColor'], - charmap : ['charmap_desc', 'mceCharMap'], - visualaid : ['visualaid_desc', 'mceToggleVisualAid'], - anchor : ['anchor_desc', 'mceInsertAnchor'], - newdocument : ['newdocument_desc', 'mceNewDocument'], - blockquote : ['blockquote_desc', 'mceBlockQuote'] - }, - - stateControls : ['bold', 'italic', 'underline', 'strikethrough', 'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'sub', 'sup', 'blockquote'], - - init : function(ed, url) { - var t = this, s, v, o; - - t.editor = ed; - t.url = url; - t.onResolveName = new tinymce.util.Dispatcher(this); - - // Default settings - t.settings = s = extend({ - theme_advanced_path : true, - theme_advanced_toolbar_location : 'bottom', - theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect", - theme_advanced_buttons2 : "bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code", - theme_advanced_buttons3 : "hr,removeformat,visualaid,|,sub,sup,|,charmap", - theme_advanced_blockformats : "p,address,pre,h1,h2,h3,h4,h5,h6", - theme_advanced_toolbar_align : "center", - theme_advanced_fonts : "Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats", - theme_advanced_more_colors : 1, - theme_advanced_row_height : 23, - theme_advanced_resize_horizontal : 1, - theme_advanced_resizing_use_cookie : 1, - theme_advanced_font_sizes : "1,2,3,4,5,6,7", - readonly : ed.settings.readonly - }, ed.settings); - - // Setup default font_size_style_values - if (!s.font_size_style_values) - s.font_size_style_values = "8pt,10pt,12pt,14pt,18pt,24pt,36pt"; - - if (tinymce.is(s.theme_advanced_font_sizes, 'string')) { - s.font_size_style_values = tinymce.explode(s.font_size_style_values); - s.font_size_classes = tinymce.explode(s.font_size_classes || ''); - - // Parse string value - o = {}; - ed.settings.theme_advanced_font_sizes = s.theme_advanced_font_sizes; - each(ed.getParam('theme_advanced_font_sizes', '', 'hash'), function(v, k) { - var cl; - - if (k == v && v >= 1 && v <= 7) { - k = v + ' (' + t.sizes[v - 1] + 'pt)'; - cl = s.font_size_classes[v - 1]; - v = s.font_size_style_values[v - 1] || (t.sizes[v - 1] + 'pt'); - } - - if (/^\s*\./.test(v)) - cl = v.replace(/\./g, ''); - - o[k] = cl ? {'class' : cl} : {fontSize : v}; - }); - - s.theme_advanced_font_sizes = o; - } - - if ((v = s.theme_advanced_path_location) && v != 'none') - s.theme_advanced_statusbar_location = s.theme_advanced_path_location; - - if (s.theme_advanced_statusbar_location == 'none') - s.theme_advanced_statusbar_location = 0; - - // Init editor - ed.onInit.add(function() { - if (!ed.settings.readonly) - ed.onNodeChange.add(t._nodeChanged, t); - - if (ed.settings.content_css !== false) - ed.dom.loadCSS(ed.baseURI.toAbsolute("themes/advanced/skins/" + ed.settings.skin + "/content.css")); - }); - - ed.onSetProgressState.add(function(ed, b, ti) { - var co, id = ed.id, tb; - - if (b) { - t.progressTimer = setTimeout(function() { - co = ed.getContainer(); - co = co.insertBefore(DOM.create('DIV', {style : 'position:relative'}), co.firstChild); - tb = DOM.get(ed.id + '_tbl'); - - DOM.add(co, 'div', {id : id + '_blocker', 'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height : tb.clientHeight + 2}}); - DOM.add(co, 'div', {id : id + '_progress', 'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top : tb.clientHeight / 2}}); - }, ti || 0); - } else { - DOM.remove(id + '_blocker'); - DOM.remove(id + '_progress'); - clearTimeout(t.progressTimer); - } - }); - - DOM.loadCSS(s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin + "/ui.css"); - - if (s.skin_variant) - DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" + s.skin_variant + ".css"); - }, - - createControl : function(n, cf) { - var cd, c; - - if (c = cf.createControl(n)) - return c; - - switch (n) { - case "styleselect": - return this._createStyleSelect(); - - case "formatselect": - return this._createBlockFormats(); - - case "fontselect": - return this._createFontSelect(); - - case "fontsizeselect": - return this._createFontSizeSelect(); - - case "forecolor": - return this._createForeColorMenu(); - - case "backcolor": - return this._createBackColorMenu(); - } - - if ((cd = this.controls[n])) - return cf.createButton(n, {title : "advanced." + cd[0], cmd : cd[1], ui : cd[2], value : cd[3]}); - }, - - execCommand : function(cmd, ui, val) { - var f = this['_' + cmd]; - - if (f) { - f.call(this, ui, val); - return true; - } - - return false; - }, - - _importClasses : function(e) { - var ed = this.editor, ctrl = ed.controlManager.get('styleselect'); - - if (ctrl.getLength() == 0) { - each(ed.dom.getClasses(), function(o, idx) { - var name = 'style_' + idx; - - ed.formatter.register(name, { - inline : 'span', - classes : o['class'], - selector : '*' - }); - - ctrl.add(o['class'], name); - }); - } - }, - - _createStyleSelect : function(n) { - var t = this, ed = t.editor, ctrlMan = ed.controlManager, ctrl; - - // Setup style select box - ctrl = ctrlMan.createListBox('styleselect', { - title : 'advanced.style_select', - onselect : function(name) { - ed.execCommand('mceToggleFormat', false, name); - - return false; // No auto select - } - }); - - // Handle specified format - ed.onInit.add(function() { - var counter = 0, formats = ed.getParam('style_formats'); - - if (formats) { - each(formats, function(fmt) { - var name, keys = 0; - - each(fmt, function() {keys++;}); - - if (keys > 1) { - name = fmt.name = fmt.name || 'style_' + (counter++); - ed.formatter.register(name, fmt); - ctrl.add(fmt.title, name); - } else - ctrl.add(fmt.title); - }); - } else { - each(ed.getParam('theme_advanced_styles', '', 'hash'), function(val, key) { - var name; - - if (val) { - name = 'style_' + (counter++); - - ed.formatter.register(name, { - inline : 'span', - classes : val - }); - - ctrl.add(t.editor.translate(key), name); - } - }); - } - }); - - // Auto import classes if the ctrl box is empty - if (ctrl.getLength() == 0) { - ctrl.onPostRender.add(function(ed, n) { - if (!ctrl.NativeListBox) { - Event.add(n.id + '_text', 'focus', t._importClasses, t); - Event.add(n.id + '_text', 'mousedown', t._importClasses, t); - Event.add(n.id + '_open', 'focus', t._importClasses, t); - Event.add(n.id + '_open', 'mousedown', t._importClasses, t); - } else - Event.add(n.id, 'focus', t._importClasses, t); - }); - } - - return ctrl; - }, - - _createFontSelect : function() { - var c, t = this, ed = t.editor; - - c = ed.controlManager.createListBox('fontselect', { - title : 'advanced.fontdefault', - onselect : function(v) { - ed.execCommand('FontName', false, v); - return false; // No auto select - } - }); - - if (c) { - each(ed.getParam('theme_advanced_fonts', t.settings.theme_advanced_fonts, 'hash'), function(v, k) { - c.add(ed.translate(k), v, {style : v.indexOf('dings') == -1 ? 'font-family:' + v : ''}); - }); - } - - return c; - }, - - _createFontSizeSelect : function() { - var t = this, ed = t.editor, c, i = 0, cl = []; - - c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) { - if (v.fontSize) - ed.execCommand('FontSize', false, v.fontSize); - else { - each(t.settings.theme_advanced_font_sizes, function(v, k) { - if (v['class']) - cl.push(v['class']); - }); - - ed.editorCommands._applyInlineStyle('span', {'class' : v['class']}, {check_classes : cl}); - } - - return false; // No auto select - }}); - - if (c) { - each(t.settings.theme_advanced_font_sizes, function(v, k) { - var fz = v.fontSize; - - if (fz >= 1 && fz <= 7) - fz = t.sizes[parseInt(fz) - 1] + 'pt'; - - c.add(k, v, {'style' : 'font-size:' + fz, 'class' : 'mceFontSize' + (i++) + (' ' + (v['class'] || ''))}); - }); - } - - return c; - }, - - _createBlockFormats : function() { - var c, fmts = { - p : 'advanced.paragraph', - address : 'advanced.address', - pre : 'advanced.pre', - h1 : 'advanced.h1', - h2 : 'advanced.h2', - h3 : 'advanced.h3', - h4 : 'advanced.h4', - h5 : 'advanced.h5', - h6 : 'advanced.h6', - div : 'advanced.div', - blockquote : 'advanced.blockquote', - code : 'advanced.code', - dt : 'advanced.dt', - dd : 'advanced.dd', - samp : 'advanced.samp' - }, t = this; - - c = t.editor.controlManager.createListBox('formatselect', {title : 'advanced.block', cmd : 'FormatBlock'}); - if (c) { - each(t.editor.getParam('theme_advanced_blockformats', t.settings.theme_advanced_blockformats, 'hash'), function(v, k) { - c.add(t.editor.translate(k != v ? k : fmts[v]), v, {'class' : 'mce_formatPreview mce_' + v}); - }); - } - - return c; - }, - - _createForeColorMenu : function() { - var c, t = this, s = t.settings, o = {}, v; - - if (s.theme_advanced_more_colors) { - o.more_colors_func = function() { - t._mceColorPicker(0, { - color : c.value, - func : function(co) { - c.setColor(co); - } - }); - }; - } - - if (v = s.theme_advanced_text_colors) - o.colors = v; - - if (s.theme_advanced_default_foreground_color) - o.default_color = s.theme_advanced_default_foreground_color; - - o.title = 'advanced.forecolor_desc'; - o.cmd = 'ForeColor'; - o.scope = this; - - c = t.editor.controlManager.createColorSplitButton('forecolor', o); - - return c; - }, - - _createBackColorMenu : function() { - var c, t = this, s = t.settings, o = {}, v; - - if (s.theme_advanced_more_colors) { - o.more_colors_func = function() { - t._mceColorPicker(0, { - color : c.value, - func : function(co) { - c.setColor(co); - } - }); - }; - } - - if (v = s.theme_advanced_background_colors) - o.colors = v; - - if (s.theme_advanced_default_background_color) - o.default_color = s.theme_advanced_default_background_color; - - o.title = 'advanced.backcolor_desc'; - o.cmd = 'HiliteColor'; - o.scope = this; - - c = t.editor.controlManager.createColorSplitButton('backcolor', o); - - return c; - }, - - renderUI : function(o) { - var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p, nl; - - n = p = DOM.create('span', {id : ed.id + '_parent', 'class' : 'mceEditor ' + ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' + t._ufirst(s.skin_variant) : '')}); - - if (!DOM.boxModel) - n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'}); - - n = sc = DOM.add(n, 'table', {id : ed.id + '_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0}); - n = tb = DOM.add(n, 'tbody'); - - switch ((s.theme_advanced_layout_manager || '').toLowerCase()) { - case "rowlayout": - ic = t._rowLayout(s, tb, o); - break; - - case "customlayout": - ic = ed.execCallback("theme_advanced_custom_layout", s, tb, o, p); - break; - - default: - ic = t._simpleLayout(s, tb, o, p); - } - - n = o.targetNode; - - // Add classes to first and last TRs - nl = DOM.stdMode ? sc.getElementsByTagName('tr') : sc.rows; // Quick fix for IE 8 - DOM.addClass(nl[0], 'mceFirst'); - DOM.addClass(nl[nl.length - 1], 'mceLast'); - - // Add classes to first and last TDs - each(DOM.select('tr', tb), function(n) { - DOM.addClass(n.firstChild, 'mceFirst'); - DOM.addClass(n.childNodes[n.childNodes.length - 1], 'mceLast'); - }); - - if (DOM.get(s.theme_advanced_toolbar_container)) - DOM.get(s.theme_advanced_toolbar_container).appendChild(p); - else - DOM.insertAfter(p, n); - - Event.add(ed.id + '_path_row', 'click', function(e) { - e = e.target; - - if (e.nodeName == 'A') { - t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/, '$1')); - - return Event.cancel(e); - } - }); -/* - if (DOM.get(ed.id + '_path_row')) { - Event.add(ed.id + '_tbl', 'mouseover', function(e) { - var re; - - e = e.target; - - if (e.nodeName == 'SPAN' && DOM.hasClass(e.parentNode, 'mceButton')) { - re = DOM.get(ed.id + '_path_row'); - t.lastPath = re.innerHTML; - DOM.setHTML(re, e.parentNode.title); - } - }); - - Event.add(ed.id + '_tbl', 'mouseout', function(e) { - if (t.lastPath) { - DOM.setHTML(ed.id + '_path_row', t.lastPath); - t.lastPath = 0; - } - }); - } -*/ - - if (!ed.getParam('accessibility_focus')) - Event.add(DOM.add(p, 'a', {href : '#'}, ''), 'focus', function() {tinyMCE.get(ed.id).focus();}); - - if (s.theme_advanced_toolbar_location == 'external') - o.deltaHeight = 0; - - t.deltaHeight = o.deltaHeight; - o.targetNode = null; - - return { - iframeContainer : ic, - editorContainer : ed.id + '_parent', - sizeContainer : sc, - deltaHeight : o.deltaHeight - }; - }, - - getInfo : function() { - return { - longname : 'Advanced theme', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - version : tinymce.majorVersion + "." + tinymce.minorVersion - } - }, - - resizeBy : function(dw, dh) { - var e = DOM.get(this.editor.id + '_tbl'); - - this.resizeTo(e.clientWidth + dw, e.clientHeight + dh); - }, - - resizeTo : function(w, h) { - var ed = this.editor, s = this.settings, e = DOM.get(ed.id + '_tbl'), ifr = DOM.get(ed.id + '_ifr'); - - // Boundery fix box - w = Math.max(s.theme_advanced_resizing_min_width || 100, w); - h = Math.max(s.theme_advanced_resizing_min_height || 100, h); - w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w); - h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h); - - // Resize iframe and container - DOM.setStyle(e, 'height', ''); - DOM.setStyle(ifr, 'height', h); - - if (s.theme_advanced_resize_horizontal) { - DOM.setStyle(e, 'width', ''); - DOM.setStyle(ifr, 'width', w); - - // Make sure that the size is never smaller than the over all ui - if (w < e.clientWidth) - DOM.setStyle(ifr, 'width', e.clientWidth); - } - }, - - destroy : function() { - var id = this.editor.id; - - Event.clear(id + '_resize'); - Event.clear(id + '_path_row'); - Event.clear(id + '_external_close'); - }, - - // Internal functions - - _simpleLayout : function(s, tb, o, p) { - var t = this, ed = t.editor, lo = s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic, etb, c; - - if (s.readonly) { - n = DOM.add(tb, 'tr'); - n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); - return ic; - } - - // Create toolbar container at top - if (lo == 'top') - t._addToolbars(tb, o); - - // Create external toolbar - if (lo == 'external') { - n = c = DOM.create('div', {style : 'position:relative'}); - n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' : 'mceExternalToolbar'}); - DOM.add(n, 'a', {id : ed.id + '_external_close', href : 'javascript:;', 'class' : 'mceExternalClose'}); - n = DOM.add(n, 'table', {id : ed.id + '_tblext', cellSpacing : 0, cellPadding : 0}); - etb = DOM.add(n, 'tbody'); - - if (p.firstChild.className == 'mceOldBoxModel') - p.firstChild.appendChild(c); - else - p.insertBefore(c, p.firstChild); - - t._addToolbars(etb, o); - - ed.onMouseUp.add(function() { - var e = DOM.get(ed.id + '_external'); - DOM.show(e); - - DOM.hide(lastExtID); - - var f = Event.add(ed.id + '_external_close', 'click', function() { - DOM.hide(ed.id + '_external'); - Event.remove(ed.id + '_external_close', 'click', f); - }); - - DOM.show(e); - DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id + '_tblext').h - 1); - - // Fixes IE rendering bug - DOM.hide(e); - DOM.show(e); - e.style.filter = ''; - - lastExtID = ed.id + '_external'; - - e = null; - }); - } - - if (sl == 'top') - t._addStatusBar(tb, o); - - // Create iframe container - if (!s.theme_advanced_toolbar_container) { - n = DOM.add(tb, 'tr'); - n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); - } - - // Create toolbar container at bottom - if (lo == 'bottom') - t._addToolbars(tb, o); - - if (sl == 'bottom') - t._addStatusBar(tb, o); - - return ic; - }, - - _rowLayout : function(s, tb, o) { - var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n, ic, to, a; - - dc = s.theme_advanced_containers_default_class || ''; - da = s.theme_advanced_containers_default_align || 'center'; - - each(explode(s.theme_advanced_containers || ''), function(c, i) { - var v = s['theme_advanced_container_' + c] || ''; - - switch (v.toLowerCase()) { - case 'mceeditor': - n = DOM.add(tb, 'tr'); - n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); - break; - - case 'mceelementpath': - t._addStatusBar(tb, o); - break; - - default: - a = (s['theme_advanced_container_' + c + '_align'] || da).toLowerCase(); - a = 'mce' + t._ufirst(a); - - n = DOM.add(DOM.add(tb, 'tr'), 'td', { - 'class' : 'mceToolbar ' + (s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da - }); - - to = cf.createToolbar("toolbar" + i); - t._addControls(v, to); - DOM.setHTML(n, to.renderHTML()); - o.deltaHeight -= s.theme_advanced_row_height; - } - }); - - return ic; - }, - - _addControls : function(v, tb) { - var t = this, s = t.settings, di, cf = t.editor.controlManager; - - if (s.theme_advanced_disable && !t._disabled) { - di = {}; - - each(explode(s.theme_advanced_disable), function(v) { - di[v] = 1; - }); - - t._disabled = di; - } else - di = t._disabled; - - each(explode(v), function(n) { - var c; - - if (di && di[n]) - return; - - // Compatiblity with 2.x - if (n == 'tablecontrols') { - each(["table","|","row_props","cell_props","|","row_before","row_after","delete_row","|","col_before","col_after","delete_col","|","split_cells","merge_cells"], function(n) { - n = t.createControl(n, cf); - - if (n) - tb.add(n); - }); - - return; - } - - c = t.createControl(n, cf); - - if (c) - tb.add(c); - }); - }, - - _addToolbars : function(c, o) { - var t = this, i, tb, ed = t.editor, s = t.settings, v, cf = ed.controlManager, di, n, h = [], a; - - a = s.theme_advanced_toolbar_align.toLowerCase(); - a = 'mce' + t._ufirst(a); - - n = DOM.add(DOM.add(c, 'tr'), 'td', {'class' : 'mceToolbar ' + a}); - - if (!ed.getParam('accessibility_focus')) - h.push(DOM.createHTML('a', {href : '#', onfocus : 'tinyMCE.get(\'' + ed.id + '\').focus();'}, '')); - - h.push(DOM.createHTML('a', {href : '#', accesskey : 'q', title : ed.getLang("advanced.toolbar_focus")}, '')); - - // Create toolbar and add the controls - for (i=1; (v = s['theme_advanced_buttons' + i]); i++) { - tb = cf.createToolbar("toolbar" + i, {'class' : 'mceToolbarRow' + i}); - - if (s['theme_advanced_buttons' + i + '_add']) - v += ',' + s['theme_advanced_buttons' + i + '_add']; - - if (s['theme_advanced_buttons' + i + '_add_before']) - v = s['theme_advanced_buttons' + i + '_add_before'] + ',' + v; - - t._addControls(v, tb); - - //n.appendChild(n = tb.render()); - h.push(tb.renderHTML()); - - o.deltaHeight -= s.theme_advanced_row_height; - } - - h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title : ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' + ed.id + '\').focus();'}, '')); - DOM.setHTML(n, h.join('')); - }, - - _addStatusBar : function(tb, o) { - var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td; - - n = DOM.add(tb, 'tr'); - n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'}); - n = DOM.add(n, 'div', {id : ed.id + '_path_row'}, s.theme_advanced_path ? ed.translate('advanced.path') + ': ' : ' '); - DOM.add(n, 'a', {href : '#', accesskey : 'x'}); - - if (s.theme_advanced_resizing) { - DOM.add(td, 'a', {id : ed.id + '_resize', href : 'javascript:;', onclick : "return false;", 'class' : 'mceResize'}); - - if (s.theme_advanced_resizing_use_cookie) { - ed.onPostRender.add(function() { - var o = Cookie.getHash("TinyMCE_" + ed.id + "_size"), c = DOM.get(ed.id + '_tbl'); - - if (!o) - return; - - t.resizeTo(o.cw, o.ch); - }); - } - - ed.onPostRender.add(function() { - Event.add(ed.id + '_resize', 'mousedown', function(e) { - var mouseMoveHandler1, mouseMoveHandler2, - mouseUpHandler1, mouseUpHandler2, - startX, startY, startWidth, startHeight, width, height, ifrElm; - - function resizeOnMove(e) { - width = startWidth + (e.screenX - startX); - height = startHeight + (e.screenY - startY); - - t.resizeTo(width, height); - }; - - function endResize(e) { - // Stop listening - Event.remove(DOM.doc, 'mousemove', mouseMoveHandler1); - Event.remove(ed.getDoc(), 'mousemove', mouseMoveHandler2); - Event.remove(DOM.doc, 'mouseup', mouseUpHandler1); - Event.remove(ed.getDoc(), 'mouseup', mouseUpHandler2); - - // Store away the size - if (s.theme_advanced_resizing_use_cookie) { - Cookie.setHash("TinyMCE_" + ed.id + "_size", { - cw : width, - ch : height - }); - } - }; - - e.preventDefault(); - - // Get the current rect size - startX = e.screenX; - startY = e.screenY; - ifrElm = DOM.get(t.editor.id + '_ifr'); - startWidth = width = ifrElm.clientWidth; - startHeight = height = ifrElm.clientHeight; - - // Register envent handlers - mouseMoveHandler1 = Event.add(DOM.doc, 'mousemove', resizeOnMove); - mouseMoveHandler2 = Event.add(ed.getDoc(), 'mousemove', resizeOnMove); - mouseUpHandler1 = Event.add(DOM.doc, 'mouseup', endResize); - mouseUpHandler2 = Event.add(ed.getDoc(), 'mouseup', endResize); - }); - }); - } - - o.deltaHeight -= 21; - n = tb = null; - }, - - _nodeChanged : function(ed, cm, n, co, ob) { - var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn, formatNames, matches; - - tinymce.each(t.stateControls, function(c) { - cm.setActive(c, ed.queryCommandState(t.controls[c][1])); - }); - - function getParent(name) { - var i, parents = ob.parents, func = name; - - if (typeof(name) == 'string') { - func = function(node) { - return node.nodeName == name; - }; - } - - for (i = 0; i < parents.length; i++) { - if (func(parents[i])) - return parents[i]; - } - }; - - cm.setActive('visualaid', ed.hasVisual); - cm.setDisabled('undo', !ed.undoManager.hasUndo() && !ed.typing); - cm.setDisabled('redo', !ed.undoManager.hasRedo()); - cm.setDisabled('outdent', !ed.queryCommandState('Outdent')); - - p = getParent('A'); - if (c = cm.get('link')) { - if (!p || !p.name) { - c.setDisabled(!p && co); - c.setActive(!!p); - } - } - - if (c = cm.get('unlink')) { - c.setDisabled(!p && co); - c.setActive(!!p && !p.name); - } - - if (c = cm.get('anchor')) { - c.setActive(!!p && p.name); - } - - p = getParent('IMG'); - if (c = cm.get('image')) - c.setActive(!!p && n.className.indexOf('mceItem') == -1); - - if (c = cm.get('styleselect')) { - t._importClasses(); - - formatNames = []; - each(c.items, function(item) { - formatNames.push(item.value); - }); - - matches = ed.formatter.matchAll(formatNames); - c.select(matches[0]); - } - - if (c = cm.get('formatselect')) { - p = getParent(DOM.isBlock); - - if (p) - c.select(p.nodeName.toLowerCase()); - } - - // Find out current fontSize, fontFamily and fontClass - getParent(function(n) { - if (n.nodeName === 'SPAN') { - if (!cl && n.className) - cl = n.className; - - if (!fz && n.style.fontSize) - fz = n.style.fontSize; - - if (!fn && n.style.fontFamily) - fn = n.style.fontFamily.replace(/[\"\']+/g, '').replace(/^([^,]+).*/, '$1').toLowerCase(); - } - - return false; - }); - - if (c = cm.get('fontselect')) { - c.select(function(v) { - return v.replace(/^([^,]+).*/, '$1').toLowerCase() == fn; - }); - } - - // Select font size - if (c = cm.get('fontsizeselect')) { - // Use computed style - if (s.theme_advanced_runtime_fontsize && !fz && !cl) - fz = ed.dom.getStyle(n, 'fontSize', true); - - c.select(function(v) { - if (v.fontSize && v.fontSize === fz) - return true; - - if (v['class'] && v['class'] === cl) - return true; - }); - } - - if (s.theme_advanced_path && s.theme_advanced_statusbar_location) { - p = DOM.get(ed.id + '_path') || DOM.add(ed.id + '_path_row', 'span', {id : ed.id + '_path'}); - DOM.setHTML(p, ''); - - getParent(function(n) { - var na = n.nodeName.toLowerCase(), u, pi, ti = ''; - - /*if (n.getAttribute('_mce_bogus')) - return; -*/ - // Ignore non element and hidden elements - if (n.nodeType != 1 || n.nodeName === 'BR' || (DOM.hasClass(n, 'mceItemHidden') || DOM.hasClass(n, 'mceItemRemoved'))) - return; - - // Fake name - if (v = DOM.getAttrib(n, 'mce_name')) - na = v; - - // Handle prefix - if (tinymce.isIE && n.scopeName !== 'HTML') - na = n.scopeName + ':' + na; - - // Remove internal prefix - na = na.replace(/mce\:/g, ''); - - // Handle node name - switch (na) { - case 'b': - na = 'strong'; - break; - - case 'i': - na = 'em'; - break; - - case 'img': - if (v = DOM.getAttrib(n, 'src')) - ti += 'src: ' + v + ' '; - - break; - - case 'a': - if (v = DOM.getAttrib(n, 'name')) { - ti += 'name: ' + v + ' '; - na += '#' + v; - } - - if (v = DOM.getAttrib(n, 'href')) - ti += 'href: ' + v + ' '; - - break; - - case 'font': - if (v = DOM.getAttrib(n, 'face')) - ti += 'font: ' + v + ' '; - - if (v = DOM.getAttrib(n, 'size')) - ti += 'size: ' + v + ' '; - - if (v = DOM.getAttrib(n, 'color')) - ti += 'color: ' + v + ' '; - - break; - - case 'span': - if (v = DOM.getAttrib(n, 'style')) - ti += 'style: ' + v + ' '; - - break; - } - - if (v = DOM.getAttrib(n, 'id')) - ti += 'id: ' + v + ' '; - - if (v = n.className) { - v = v.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g, '') - - if (v) { - ti += 'class: ' + v + ' '; - - if (DOM.isBlock(n) || na == 'img' || na == 'span') - na += '.' + v; - } - } - - na = na.replace(/(html:)/g, ''); - na = {name : na, node : n, title : ti}; - t.onResolveName.dispatch(t, na); - ti = na.title; - na = na.name; - - //u = "javascript:tinymce.EditorManager.get('" + ed.id + "').theme._sel('" + (de++) + "');"; - pi = DOM.create('a', {'href' : "javascript:;", onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de++)}, na); - - if (p.hasChildNodes()) { - p.insertBefore(DOM.doc.createTextNode(' \u00bb '), p.firstChild); - p.insertBefore(pi, p.firstChild); - } else - p.appendChild(pi); - }, ed.getBody()); - } - }, - - // Commands gets called by execCommand - - _sel : function(v) { - this.editor.execCommand('mceSelectNodeDepth', false, v); - }, - - _mceInsertAnchor : function(ui, v) { - var ed = this.editor; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/anchor.htm', - width : 320 + parseInt(ed.getLang('advanced.anchor_delta_width', 0)), - height : 90 + parseInt(ed.getLang('advanced.anchor_delta_height', 0)), - inline : true - }, { - theme_url : this.url - }); - }, - - _mceCharMap : function() { - var ed = this.editor; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/charmap.htm', - width : 550 + parseInt(ed.getLang('advanced.charmap_delta_width', 0)), - height : 250 + parseInt(ed.getLang('advanced.charmap_delta_height', 0)), - inline : true - }, { - theme_url : this.url - }); - }, - - _mceHelp : function() { - var ed = this.editor; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/about.htm', - width : 480, - height : 380, - inline : true - }, { - theme_url : this.url - }); - }, - - _mceColorPicker : function(u, v) { - var ed = this.editor; - - v = v || {}; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/color_picker.htm', - width : 375 + parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)), - height : 250 + parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)), - close_previous : false, - inline : true - }, { - input_color : v.color, - func : v.func, - theme_url : this.url - }); - }, - - _mceCodeEditor : function(ui, val) { - var ed = this.editor; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/source_editor.htm', - width : parseInt(ed.getParam("theme_advanced_source_editor_width", 720)), - height : parseInt(ed.getParam("theme_advanced_source_editor_height", 580)), - inline : true, - resizable : true, - maximizable : true - }, { - theme_url : this.url - }); - }, - - _mceImage : function(ui, val) { - var ed = this.editor; - - // Internal image object like a flash placeholder - if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1) - return; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/image.htm', - width : 355 + parseInt(ed.getLang('advanced.image_delta_width', 0)), - height : 275 + parseInt(ed.getLang('advanced.image_delta_height', 0)), - inline : true - }, { - theme_url : this.url - }); - }, - - _mceLink : function(ui, val) { - var ed = this.editor; - - ed.windowManager.open({ - url : tinymce.baseURL + '/themes/advanced/link.htm', - width : 310 + parseInt(ed.getLang('advanced.link_delta_width', 0)), - height : 200 + parseInt(ed.getLang('advanced.link_delta_height', 0)), - inline : true - }, { - theme_url : this.url - }); - }, - - _mceNewDocument : function() { - var ed = this.editor; - - ed.windowManager.confirm('advanced.newdocument', function(s) { - if (s) - ed.execCommand('mceSetContent', false, ''); - }); - }, - - _mceForeColor : function() { - var t = this; - - this._mceColorPicker(0, { - color: t.fgColor, - func : function(co) { - t.fgColor = co; - t.editor.execCommand('ForeColor', false, co); - } - }); - }, - - _mceBackColor : function() { - var t = this; - - this._mceColorPicker(0, { - color: t.bgColor, - func : function(co) { - t.bgColor = co; - t.editor.execCommand('HiliteColor', false, co); - } - }); - }, - - _ufirst : function(s) { - return s.substring(0, 1).toUpperCase() + s.substring(1); - } - }); - - tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme); -}(tinymce)); \ No newline at end of file +/** + * editor_template_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, each = tinymce.each, Cookie = tinymce.util.Cookie, lastExtID, explode = tinymce.explode; + + // Tell it to load theme specific language pack(s) + tinymce.ThemeManager.requireLangPack('advanced'); + + tinymce.create('tinymce.themes.AdvancedTheme', { + sizes : [8, 10, 12, 14, 18, 24, 36], + + // Control name lookup, format: title, command + controls : { + bold : ['bold_desc', 'Bold'], + italic : ['italic_desc', 'Italic'], + underline : ['underline_desc', 'Underline'], + strikethrough : ['striketrough_desc', 'Strikethrough'], + justifyleft : ['justifyleft_desc', 'JustifyLeft'], + justifycenter : ['justifycenter_desc', 'JustifyCenter'], + justifyright : ['justifyright_desc', 'JustifyRight'], + justifyfull : ['justifyfull_desc', 'JustifyFull'], + bullist : ['bullist_desc', 'InsertUnorderedList'], + numlist : ['numlist_desc', 'InsertOrderedList'], + outdent : ['outdent_desc', 'Outdent'], + indent : ['indent_desc', 'Indent'], + cut : ['cut_desc', 'Cut'], + copy : ['copy_desc', 'Copy'], + paste : ['paste_desc', 'Paste'], + undo : ['undo_desc', 'Undo'], + redo : ['redo_desc', 'Redo'], + link : ['link_desc', 'mceLink'], + unlink : ['unlink_desc', 'unlink'], + image : ['image_desc', 'mceImage'], + cleanup : ['cleanup_desc', 'mceCleanup'], + help : ['help_desc', 'mceHelp'], + code : ['code_desc', 'mceCodeEditor'], + hr : ['hr_desc', 'InsertHorizontalRule'], + removeformat : ['removeformat_desc', 'RemoveFormat'], + sub : ['sub_desc', 'subscript'], + sup : ['sup_desc', 'superscript'], + forecolor : ['forecolor_desc', 'ForeColor'], + forecolorpicker : ['forecolor_desc', 'mceForeColor'], + backcolor : ['backcolor_desc', 'HiliteColor'], + backcolorpicker : ['backcolor_desc', 'mceBackColor'], + charmap : ['charmap_desc', 'mceCharMap'], + visualaid : ['visualaid_desc', 'mceToggleVisualAid'], + anchor : ['anchor_desc', 'mceInsertAnchor'], + newdocument : ['newdocument_desc', 'mceNewDocument'], + blockquote : ['blockquote_desc', 'mceBlockQuote'] + }, + + stateControls : ['bold', 'italic', 'underline', 'strikethrough', 'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'sub', 'sup', 'blockquote'], + + init : function(ed, url) { + var t = this, s, v, o; + + t.editor = ed; + t.url = url; + t.onResolveName = new tinymce.util.Dispatcher(this); + + ed.forcedHighContrastMode = ed.settings.detect_highcontrast && t._isHighContrast(); + ed.settings.skin = ed.forcedHighContrastMode ? 'highcontrast' : ed.settings.skin; + + // Default settings + t.settings = s = extend({ + theme_advanced_path : true, + theme_advanced_toolbar_location : 'bottom', + theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect", + theme_advanced_buttons2 : "bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code", + theme_advanced_buttons3 : "hr,removeformat,visualaid,|,sub,sup,|,charmap", + theme_advanced_blockformats : "p,address,pre,h1,h2,h3,h4,h5,h6", + theme_advanced_toolbar_align : "center", + theme_advanced_fonts : "Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats", + theme_advanced_more_colors : 1, + theme_advanced_row_height : 23, + theme_advanced_resize_horizontal : 1, + theme_advanced_resizing_use_cookie : 1, + theme_advanced_font_sizes : "1,2,3,4,5,6,7", + theme_advanced_font_selector : "span", + theme_advanced_show_current_color: 0, + readonly : ed.settings.readonly + }, ed.settings); + + // Setup default font_size_style_values + if (!s.font_size_style_values) + s.font_size_style_values = "8pt,10pt,12pt,14pt,18pt,24pt,36pt"; + + if (tinymce.is(s.theme_advanced_font_sizes, 'string')) { + s.font_size_style_values = tinymce.explode(s.font_size_style_values); + s.font_size_classes = tinymce.explode(s.font_size_classes || ''); + + // Parse string value + o = {}; + ed.settings.theme_advanced_font_sizes = s.theme_advanced_font_sizes; + each(ed.getParam('theme_advanced_font_sizes', '', 'hash'), function(v, k) { + var cl; + + if (k == v && v >= 1 && v <= 7) { + k = v + ' (' + t.sizes[v - 1] + 'pt)'; + cl = s.font_size_classes[v - 1]; + v = s.font_size_style_values[v - 1] || (t.sizes[v - 1] + 'pt'); + } + + if (/^\s*\./.test(v)) + cl = v.replace(/\./g, ''); + + o[k] = cl ? {'class' : cl} : {fontSize : v}; + }); + + s.theme_advanced_font_sizes = o; + } + + if ((v = s.theme_advanced_path_location) && v != 'none') + s.theme_advanced_statusbar_location = s.theme_advanced_path_location; + + if (s.theme_advanced_statusbar_location == 'none') + s.theme_advanced_statusbar_location = 0; + + if (ed.settings.content_css !== false) + ed.contentCSS.push(ed.baseURI.toAbsolute(url + "/skins/" + ed.settings.skin + "/content.css")); + + // Init editor + ed.onInit.add(function() { + if (!ed.settings.readonly) { + ed.onNodeChange.add(t._nodeChanged, t); + ed.onKeyUp.add(t._updateUndoStatus, t); + ed.onMouseUp.add(t._updateUndoStatus, t); + ed.dom.bind(ed.dom.getRoot(), 'dragend', function() { + t._updateUndoStatus(ed); + }); + } + }); + + ed.onSetProgressState.add(function(ed, b, ti) { + var co, id = ed.id, tb; + + if (b) { + t.progressTimer = setTimeout(function() { + co = ed.getContainer(); + co = co.insertBefore(DOM.create('DIV', {style : 'position:relative'}), co.firstChild); + tb = DOM.get(ed.id + '_tbl'); + + DOM.add(co, 'div', {id : id + '_blocker', 'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height : tb.clientHeight + 2}}); + DOM.add(co, 'div', {id : id + '_progress', 'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top : tb.clientHeight / 2}}); + }, ti || 0); + } else { + DOM.remove(id + '_blocker'); + DOM.remove(id + '_progress'); + clearTimeout(t.progressTimer); + } + }); + + DOM.loadCSS(s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin + "/ui.css"); + + if (s.skin_variant) + DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" + s.skin_variant + ".css"); + }, + + _isHighContrast : function() { + var actualColor, div = DOM.add(DOM.getRoot(), 'div', {'style': 'background-color: rgb(171,239,86);'}); + + actualColor = (DOM.getStyle(div, 'background-color', true) + '').toLowerCase().replace(/ /g, ''); + DOM.remove(div); + + return actualColor != 'rgb(171,239,86)' && actualColor != '#abef56'; + }, + + createControl : function(n, cf) { + var cd, c; + + if (c = cf.createControl(n)) + return c; + + switch (n) { + case "styleselect": + return this._createStyleSelect(); + + case "formatselect": + return this._createBlockFormats(); + + case "fontselect": + return this._createFontSelect(); + + case "fontsizeselect": + return this._createFontSizeSelect(); + + case "forecolor": + return this._createForeColorMenu(); + + case "backcolor": + return this._createBackColorMenu(); + } + + if ((cd = this.controls[n])) + return cf.createButton(n, {title : "advanced." + cd[0], cmd : cd[1], ui : cd[2], value : cd[3]}); + }, + + execCommand : function(cmd, ui, val) { + var f = this['_' + cmd]; + + if (f) { + f.call(this, ui, val); + return true; + } + + return false; + }, + + _importClasses : function(e) { + var ed = this.editor, ctrl = ed.controlManager.get('styleselect'); + + if (ctrl.getLength() == 0) { + each(ed.dom.getClasses(), function(o, idx) { + var name = 'style_' + idx; + + ed.formatter.register(name, { + inline : 'span', + attributes : {'class' : o['class']}, + selector : '*' + }); + + ctrl.add(o['class'], name); + }); + } + }, + + _createStyleSelect : function(n) { + var t = this, ed = t.editor, ctrlMan = ed.controlManager, ctrl; + + // Setup style select box + ctrl = ctrlMan.createListBox('styleselect', { + title : 'advanced.style_select', + onselect : function(name) { + var matches, formatNames = []; + + each(ctrl.items, function(item) { + formatNames.push(item.value); + }); + + ed.focus(); + ed.undoManager.add(); + + // Toggle off the current format + matches = ed.formatter.matchAll(formatNames); + if (!name || matches[0] == name) { + if (matches[0]) + ed.formatter.remove(matches[0]); + } else + ed.formatter.apply(name); + + ed.undoManager.add(); + ed.nodeChanged(); + + return false; // No auto select + } + }); + + // Handle specified format + ed.onInit.add(function() { + var counter = 0, formats = ed.getParam('style_formats'); + + if (formats) { + each(formats, function(fmt) { + var name, keys = 0; + + each(fmt, function() {keys++;}); + + if (keys > 1) { + name = fmt.name = fmt.name || 'style_' + (counter++); + ed.formatter.register(name, fmt); + ctrl.add(fmt.title, name); + } else + ctrl.add(fmt.title); + }); + } else { + each(ed.getParam('theme_advanced_styles', '', 'hash'), function(val, key) { + var name; + + if (val) { + name = 'style_' + (counter++); + + ed.formatter.register(name, { + inline : 'span', + classes : val, + selector : '*' + }); + + ctrl.add(t.editor.translate(key), name); + } + }); + } + }); + + // Auto import classes if the ctrl box is empty + if (ctrl.getLength() == 0) { + ctrl.onPostRender.add(function(ed, n) { + if (!ctrl.NativeListBox) { + Event.add(n.id + '_text', 'focus', t._importClasses, t); + Event.add(n.id + '_text', 'mousedown', t._importClasses, t); + Event.add(n.id + '_open', 'focus', t._importClasses, t); + Event.add(n.id + '_open', 'mousedown', t._importClasses, t); + } else + Event.add(n.id, 'focus', t._importClasses, t); + }); + } + + return ctrl; + }, + + _createFontSelect : function() { + var c, t = this, ed = t.editor; + + c = ed.controlManager.createListBox('fontselect', { + title : 'advanced.fontdefault', + onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + ed.execCommand('FontName', false, cur.value); + return; + } + + ed.execCommand('FontName', false, v); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + if (cur && cur.value == v) { + c.select(null); + } + + return false; // No auto select + } + }); + + if (c) { + each(ed.getParam('theme_advanced_fonts', t.settings.theme_advanced_fonts, 'hash'), function(v, k) { + c.add(ed.translate(k), v, {style : v.indexOf('dings') == -1 ? 'font-family:' + v : ''}); + }); + } + + return c; + }, + + _createFontSizeSelect : function() { + var t = this, ed = t.editor, c, i = 0, cl = []; + + c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) { + var cur = c.items[c.selectedIndex]; + + if (!v && cur) { + cur = cur.value; + + if (cur['class']) { + ed.formatter.toggle('fontsize_class', {value : cur['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else { + ed.execCommand('FontSize', false, cur.fontSize); + } + + return; + } + + if (v['class']) { + ed.focus(); + ed.undoManager.add(); + ed.formatter.toggle('fontsize_class', {value : v['class']}); + ed.undoManager.add(); + ed.nodeChanged(); + } else + ed.execCommand('FontSize', false, v.fontSize); + + // Fake selection, execCommand will fire a nodeChange and update the selection + c.select(function(sv) { + return v == sv; + }); + + if (cur && (cur.value.fontSize == v.fontSize || cur.value['class'] == v['class'])) { + c.select(null); + } + + return false; // No auto select + }}); + + if (c) { + each(t.settings.theme_advanced_font_sizes, function(v, k) { + var fz = v.fontSize; + + if (fz >= 1 && fz <= 7) + fz = t.sizes[parseInt(fz) - 1] + 'pt'; + + c.add(k, v, {'style' : 'font-size:' + fz, 'class' : 'mceFontSize' + (i++) + (' ' + (v['class'] || ''))}); + }); + } + + return c; + }, + + _createBlockFormats : function() { + var c, fmts = { + p : 'advanced.paragraph', + address : 'advanced.address', + pre : 'advanced.pre', + h1 : 'advanced.h1', + h2 : 'advanced.h2', + h3 : 'advanced.h3', + h4 : 'advanced.h4', + h5 : 'advanced.h5', + h6 : 'advanced.h6', + div : 'advanced.div', + blockquote : 'advanced.blockquote', + code : 'advanced.code', + dt : 'advanced.dt', + dd : 'advanced.dd', + samp : 'advanced.samp' + }, t = this; + + c = t.editor.controlManager.createListBox('formatselect', {title : 'advanced.block', onselect : function(v) { + t.editor.execCommand('FormatBlock', false, v); + return false; + }}); + + if (c) { + each(t.editor.getParam('theme_advanced_blockformats', t.settings.theme_advanced_blockformats, 'hash'), function(v, k) { + c.add(t.editor.translate(k != v ? k : fmts[v]), v, {'class' : 'mce_formatPreview mce_' + v}); + }); + } + + return c; + }, + + _createForeColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_text_colors) + o.colors = v; + + if (s.theme_advanced_default_foreground_color) + o.default_color = s.theme_advanced_default_foreground_color; + + o.title = 'advanced.forecolor_desc'; + o.cmd = 'ForeColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('forecolor', o); + + return c; + }, + + _createBackColorMenu : function() { + var c, t = this, s = t.settings, o = {}, v; + + if (s.theme_advanced_more_colors) { + o.more_colors_func = function() { + t._mceColorPicker(0, { + color : c.value, + func : function(co) { + c.setColor(co); + } + }); + }; + } + + if (v = s.theme_advanced_background_colors) + o.colors = v; + + if (s.theme_advanced_default_background_color) + o.default_color = s.theme_advanced_default_background_color; + + o.title = 'advanced.backcolor_desc'; + o.cmd = 'HiliteColor'; + o.scope = this; + + c = t.editor.controlManager.createColorSplitButton('backcolor', o); + + return c; + }, + + renderUI : function(o) { + var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p, nl; + + if (ed.settings) { + ed.settings.aria_label = s.aria_label + ed.getLang('advanced.help_shortcut'); + } + + // TODO: ACC Should have an aria-describedby attribute which is user-configurable to describe what this field is actually for. + // Maybe actually inherit it from the original textara? + n = p = DOM.create('span', {role : 'application', 'aria-labelledby' : ed.id + '_voice', id : ed.id + '_parent', 'class' : 'mceEditor ' + ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' + t._ufirst(s.skin_variant) : '')}); + DOM.add(n, 'span', {'class': 'mceVoiceLabel', 'style': 'display:none;', id: ed.id + '_voice'}, s.aria_label); + + if (!DOM.boxModel) + n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'}); + + n = sc = DOM.add(n, 'table', {role : "presentation", id : ed.id + '_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0}); + n = tb = DOM.add(n, 'tbody'); + + switch ((s.theme_advanced_layout_manager || '').toLowerCase()) { + case "rowlayout": + ic = t._rowLayout(s, tb, o); + break; + + case "customlayout": + ic = ed.execCallback("theme_advanced_custom_layout", s, tb, o, p); + break; + + default: + ic = t._simpleLayout(s, tb, o, p); + } + + n = o.targetNode; + + // Add classes to first and last TRs + nl = sc.rows; + DOM.addClass(nl[0], 'mceFirst'); + DOM.addClass(nl[nl.length - 1], 'mceLast'); + + // Add classes to first and last TDs + each(DOM.select('tr', tb), function(n) { + DOM.addClass(n.firstChild, 'mceFirst'); + DOM.addClass(n.childNodes[n.childNodes.length - 1], 'mceLast'); + }); + + if (DOM.get(s.theme_advanced_toolbar_container)) + DOM.get(s.theme_advanced_toolbar_container).appendChild(p); + else + DOM.insertAfter(p, n); + + Event.add(ed.id + '_path_row', 'click', function(e) { + e = e.target; + + if (e.nodeName == 'A') { + t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/, '$1')); + + return Event.cancel(e); + } + }); +/* + if (DOM.get(ed.id + '_path_row')) { + Event.add(ed.id + '_tbl', 'mouseover', function(e) { + var re; + + e = e.target; + + if (e.nodeName == 'SPAN' && DOM.hasClass(e.parentNode, 'mceButton')) { + re = DOM.get(ed.id + '_path_row'); + t.lastPath = re.innerHTML; + DOM.setHTML(re, e.parentNode.title); + } + }); + + Event.add(ed.id + '_tbl', 'mouseout', function(e) { + if (t.lastPath) { + DOM.setHTML(ed.id + '_path_row', t.lastPath); + t.lastPath = 0; + } + }); + } +*/ + + if (!ed.getParam('accessibility_focus')) + Event.add(DOM.add(p, 'a', {href : '#'}, ''), 'focus', function() {tinyMCE.get(ed.id).focus();}); + + if (s.theme_advanced_toolbar_location == 'external') + o.deltaHeight = 0; + + t.deltaHeight = o.deltaHeight; + o.targetNode = null; + + ed.onKeyDown.add(function(ed, evt) { + var DOM_VK_F10 = 121, DOM_VK_F11 = 122; + + if (evt.altKey) { + if (evt.keyCode === DOM_VK_F10) { + // Make sure focus is given to toolbar in Safari. + // We can't do this in IE as it prevents giving focus to toolbar when editor is in a frame + if (tinymce.isWebKit) { + window.focus(); + } + t.toolbarGroup.focus(); + return Event.cancel(evt); + } else if (evt.keyCode === DOM_VK_F11) { + DOM.get(ed.id + '_path_row').focus(); + return Event.cancel(evt); + } + } + }); + + // alt+0 is the UK recommended shortcut for accessing the list of access controls. + ed.addShortcut('alt+0', '', 'mceShortcuts', t); + + return { + iframeContainer : ic, + editorContainer : ed.id + '_parent', + sizeContainer : sc, + deltaHeight : o.deltaHeight + }; + }, + + getInfo : function() { + return { + longname : 'Advanced theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + }, + + resizeBy : function(dw, dh) { + var e = DOM.get(this.editor.id + '_ifr'); + + this.resizeTo(e.clientWidth + dw, e.clientHeight + dh); + }, + + resizeTo : function(w, h, store) { + var ed = this.editor, s = this.settings, e = DOM.get(ed.id + '_tbl'), ifr = DOM.get(ed.id + '_ifr'); + + // Boundery fix box + w = Math.max(s.theme_advanced_resizing_min_width || 100, w); + h = Math.max(s.theme_advanced_resizing_min_height || 100, h); + w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w); + h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h); + + // Resize iframe and container + DOM.setStyle(e, 'height', ''); + DOM.setStyle(ifr, 'height', h); + + if (s.theme_advanced_resize_horizontal) { + DOM.setStyle(e, 'width', ''); + DOM.setStyle(ifr, 'width', w); + + // Make sure that the size is never smaller than the over all ui + if (w < e.clientWidth) { + w = e.clientWidth; + DOM.setStyle(ifr, 'width', e.clientWidth); + } + } + + // Store away the size + if (store && s.theme_advanced_resizing_use_cookie) { + Cookie.setHash("TinyMCE_" + ed.id + "_size", { + cw : w, + ch : h + }); + } + }, + + destroy : function() { + var id = this.editor.id; + + Event.clear(id + '_resize'); + Event.clear(id + '_path_row'); + Event.clear(id + '_external_close'); + }, + + // Internal functions + + _simpleLayout : function(s, tb, o, p) { + var t = this, ed = t.editor, lo = s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic, etb, c; + + if (s.readonly) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + return ic; + } + + // Create toolbar container at top + if (lo == 'top') + t._addToolbars(tb, o); + + // Create external toolbar + if (lo == 'external') { + n = c = DOM.create('div', {style : 'position:relative'}); + n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' : 'mceExternalToolbar'}); + DOM.add(n, 'a', {id : ed.id + '_external_close', href : 'javascript:;', 'class' : 'mceExternalClose'}); + n = DOM.add(n, 'table', {id : ed.id + '_tblext', cellSpacing : 0, cellPadding : 0}); + etb = DOM.add(n, 'tbody'); + + if (p.firstChild.className == 'mceOldBoxModel') + p.firstChild.appendChild(c); + else + p.insertBefore(c, p.firstChild); + + t._addToolbars(etb, o); + + ed.onMouseUp.add(function() { + var e = DOM.get(ed.id + '_external'); + DOM.show(e); + + DOM.hide(lastExtID); + + var f = Event.add(ed.id + '_external_close', 'click', function() { + DOM.hide(ed.id + '_external'); + Event.remove(ed.id + '_external_close', 'click', f); + }); + + DOM.show(e); + DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id + '_tblext').h - 1); + + // Fixes IE rendering bug + DOM.hide(e); + DOM.show(e); + e.style.filter = ''; + + lastExtID = ed.id + '_external'; + + e = null; + }); + } + + if (sl == 'top') + t._addStatusBar(tb, o); + + // Create iframe container + if (!s.theme_advanced_toolbar_container) { + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + } + + // Create toolbar container at bottom + if (lo == 'bottom') + t._addToolbars(tb, o); + + if (sl == 'bottom') + t._addStatusBar(tb, o); + + return ic; + }, + + _rowLayout : function(s, tb, o) { + var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n, ic, to, a; + + dc = s.theme_advanced_containers_default_class || ''; + da = s.theme_advanced_containers_default_align || 'center'; + + each(explode(s.theme_advanced_containers || ''), function(c, i) { + var v = s['theme_advanced_container_' + c] || ''; + + switch (c.toLowerCase()) { + case 'mceeditor': + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'}); + break; + + case 'mceelementpath': + t._addStatusBar(tb, o); + break; + + default: + a = (s['theme_advanced_container_' + c + '_align'] || da).toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(tb, 'tr'), 'td', { + 'class' : 'mceToolbar ' + (s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da + }); + + to = cf.createToolbar("toolbar" + i); + t._addControls(v, to); + DOM.setHTML(n, to.renderHTML()); + o.deltaHeight -= s.theme_advanced_row_height; + } + }); + + return ic; + }, + + _addControls : function(v, tb) { + var t = this, s = t.settings, di, cf = t.editor.controlManager; + + if (s.theme_advanced_disable && !t._disabled) { + di = {}; + + each(explode(s.theme_advanced_disable), function(v) { + di[v] = 1; + }); + + t._disabled = di; + } else + di = t._disabled; + + each(explode(v), function(n) { + var c; + + if (di && di[n]) + return; + + // Compatiblity with 2.x + if (n == 'tablecontrols') { + each(["table","|","row_props","cell_props","|","row_before","row_after","delete_row","|","col_before","col_after","delete_col","|","split_cells","merge_cells"], function(n) { + n = t.createControl(n, cf); + + if (n) + tb.add(n); + }); + + return; + } + + c = t.createControl(n, cf); + + if (c) + tb.add(c); + }); + }, + + _addToolbars : function(c, o) { + var t = this, i, tb, ed = t.editor, s = t.settings, v, cf = ed.controlManager, di, n, h = [], a, toolbarGroup; + + toolbarGroup = cf.createToolbarGroup('toolbargroup', { + 'name': ed.getLang('advanced.toolbar'), + 'tab_focus_toolbar':ed.getParam('theme_advanced_tab_focus_toolbar') + }); + + t.toolbarGroup = toolbarGroup; + + a = s.theme_advanced_toolbar_align.toLowerCase(); + a = 'mce' + t._ufirst(a); + + n = DOM.add(DOM.add(c, 'tr', {role: 'presentation'}), 'td', {'class' : 'mceToolbar ' + a, "role":"presentation"}); + + // Create toolbar and add the controls + for (i=1; (v = s['theme_advanced_buttons' + i]); i++) { + tb = cf.createToolbar("toolbar" + i, {'class' : 'mceToolbarRow' + i}); + + if (s['theme_advanced_buttons' + i + '_add']) + v += ',' + s['theme_advanced_buttons' + i + '_add']; + + if (s['theme_advanced_buttons' + i + '_add_before']) + v = s['theme_advanced_buttons' + i + '_add_before'] + ',' + v; + + t._addControls(v, tb); + toolbarGroup.add(tb); + + o.deltaHeight -= s.theme_advanced_row_height; + } + h.push(toolbarGroup.renderHTML()); + h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title : ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' + ed.id + '\').focus();'}, '')); + DOM.setHTML(n, h.join('')); + }, + + _addStatusBar : function(tb, o) { + var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td; + + n = DOM.add(tb, 'tr'); + n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'}); + n = DOM.add(n, 'div', {id : ed.id + '_path_row', 'role': 'group', 'aria-labelledby': ed.id + '_path_voice'}); + if (s.theme_advanced_path) { + DOM.add(n, 'span', {id: ed.id + '_path_voice'}, ed.translate('advanced.path')); + DOM.add(n, 'span', {}, ': '); + } else { + DOM.add(n, 'span', {}, ' '); + } + + + if (s.theme_advanced_resizing) { + DOM.add(td, 'a', {id : ed.id + '_resize', href : 'javascript:;', onclick : "return false;", 'class' : 'mceResize', tabIndex:"-1"}); + + if (s.theme_advanced_resizing_use_cookie) { + ed.onPostRender.add(function() { + var o = Cookie.getHash("TinyMCE_" + ed.id + "_size"), c = DOM.get(ed.id + '_tbl'); + + if (!o) + return; + + t.resizeTo(o.cw, o.ch); + }); + } + + ed.onPostRender.add(function() { + Event.add(ed.id + '_resize', 'click', function(e) { + e.preventDefault(); + }); + + Event.add(ed.id + '_resize', 'mousedown', function(e) { + var mouseMoveHandler1, mouseMoveHandler2, + mouseUpHandler1, mouseUpHandler2, + startX, startY, startWidth, startHeight, width, height, ifrElm; + + function resizeOnMove(e) { + e.preventDefault(); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + + t.resizeTo(width, height); + }; + + function endResize(e) { + // Stop listening + Event.remove(DOM.doc, 'mousemove', mouseMoveHandler1); + Event.remove(ed.getDoc(), 'mousemove', mouseMoveHandler2); + Event.remove(DOM.doc, 'mouseup', mouseUpHandler1); + Event.remove(ed.getDoc(), 'mouseup', mouseUpHandler2); + + width = startWidth + (e.screenX - startX); + height = startHeight + (e.screenY - startY); + t.resizeTo(width, height, true); + }; + + e.preventDefault(); + + // Get the current rect size + startX = e.screenX; + startY = e.screenY; + ifrElm = DOM.get(t.editor.id + '_ifr'); + startWidth = width = ifrElm.clientWidth; + startHeight = height = ifrElm.clientHeight; + + // Register envent handlers + mouseMoveHandler1 = Event.add(DOM.doc, 'mousemove', resizeOnMove); + mouseMoveHandler2 = Event.add(ed.getDoc(), 'mousemove', resizeOnMove); + mouseUpHandler1 = Event.add(DOM.doc, 'mouseup', endResize); + mouseUpHandler2 = Event.add(ed.getDoc(), 'mouseup', endResize); + }); + }); + } + + o.deltaHeight -= 21; + n = tb = null; + }, + + _updateUndoStatus : function(ed) { + var cm = ed.controlManager, um = ed.undoManager; + + cm.setDisabled('undo', !um.hasUndo() && !um.typing); + cm.setDisabled('redo', !um.hasRedo()); + }, + + _nodeChanged : function(ed, cm, n, co, ob) { + var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn, fc, bc, formatNames, matches; + + tinymce.each(t.stateControls, function(c) { + cm.setActive(c, ed.queryCommandState(t.controls[c][1])); + }); + + function getParent(name) { + var i, parents = ob.parents, func = name; + + if (typeof(name) == 'string') { + func = function(node) { + return node.nodeName == name; + }; + } + + for (i = 0; i < parents.length; i++) { + if (func(parents[i])) + return parents[i]; + } + }; + + cm.setActive('visualaid', ed.hasVisual); + t._updateUndoStatus(ed); + cm.setDisabled('outdent', !ed.queryCommandState('Outdent')); + + p = getParent('A'); + if (c = cm.get('link')) { + if (!p || !p.name) { + c.setDisabled(!p && co); + c.setActive(!!p); + } + } + + if (c = cm.get('unlink')) { + c.setDisabled(!p && co); + c.setActive(!!p && !p.name); + } + + if (c = cm.get('anchor')) { + c.setActive(!co && !!p && p.name); + } + + p = getParent('IMG'); + if (c = cm.get('image')) + c.setActive(!co && !!p && n.className.indexOf('mceItem') == -1); + + if (c = cm.get('styleselect')) { + t._importClasses(); + + formatNames = []; + each(c.items, function(item) { + formatNames.push(item.value); + }); + + matches = ed.formatter.matchAll(formatNames); + c.select(matches[0]); + } + + if (c = cm.get('formatselect')) { + p = getParent(DOM.isBlock); + + if (p) + c.select(p.nodeName.toLowerCase()); + } + + // Find out current fontSize, fontFamily and fontClass + getParent(function(n) { + if (n.nodeName === 'SPAN') { + if (!cl && n.className) + cl = n.className; + } + + if (ed.dom.is(n, s.theme_advanced_font_selector)) { + if (!fz && n.style.fontSize) + fz = n.style.fontSize; + + if (!fn && n.style.fontFamily) + fn = n.style.fontFamily.replace(/[\"\']+/g, '').replace(/^([^,]+).*/, '$1').toLowerCase(); + + if (!fc && n.style.color) + fc = n.style.color; + + if (!bc && n.style.backgroundColor) + bc = n.style.backgroundColor; + } + + return false; + }); + + if (c = cm.get('fontselect')) { + c.select(function(v) { + return v.replace(/^([^,]+).*/, '$1').toLowerCase() == fn; + }); + } + + // Select font size + if (c = cm.get('fontsizeselect')) { + // Use computed style + if (s.theme_advanced_runtime_fontsize && !fz && !cl) + fz = ed.dom.getStyle(n, 'fontSize', true); + + c.select(function(v) { + if (v.fontSize && v.fontSize === fz) + return true; + + if (v['class'] && v['class'] === cl) + return true; + }); + } + + if (s.theme_advanced_show_current_color) { + function updateColor(controlId, color) { + if (c = cm.get(controlId)) { + if (!color) + color = c.settings.default_color; + if (color !== c.value) { + c.displayColor(color); + } + } + } + updateColor('forecolor', fc); + updateColor('backcolor', bc); + } + + if (s.theme_advanced_show_current_color) { + function updateColor(controlId, color) { + if (c = cm.get(controlId)) { + if (!color) + color = c.settings.default_color; + if (color !== c.value) { + c.displayColor(color); + } + } + }; + + updateColor('forecolor', fc); + updateColor('backcolor', bc); + } + + if (s.theme_advanced_path && s.theme_advanced_statusbar_location) { + p = DOM.get(ed.id + '_path') || DOM.add(ed.id + '_path_row', 'span', {id : ed.id + '_path'}); + + if (t.statusKeyboardNavigation) { + t.statusKeyboardNavigation.destroy(); + t.statusKeyboardNavigation = null; + } + + DOM.setHTML(p, ''); + + getParent(function(n) { + var na = n.nodeName.toLowerCase(), u, pi, ti = ''; + + // Ignore non element and bogus/hidden elements + if (n.nodeType != 1 || na === 'br' || n.getAttribute('data-mce-bogus') || DOM.hasClass(n, 'mceItemHidden') || DOM.hasClass(n, 'mceItemRemoved')) + return; + + // Handle prefix + if (tinymce.isIE && n.scopeName !== 'HTML') + na = n.scopeName + ':' + na; + + // Remove internal prefix + na = na.replace(/mce\:/g, ''); + + // Handle node name + switch (na) { + case 'b': + na = 'strong'; + break; + + case 'i': + na = 'em'; + break; + + case 'img': + if (v = DOM.getAttrib(n, 'src')) + ti += 'src: ' + v + ' '; + + break; + + case 'a': + if (v = DOM.getAttrib(n, 'name')) { + ti += 'name: ' + v + ' '; + na += '#' + v; + } + + if (v = DOM.getAttrib(n, 'href')) + ti += 'href: ' + v + ' '; + + break; + + case 'font': + if (v = DOM.getAttrib(n, 'face')) + ti += 'font: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'size')) + ti += 'size: ' + v + ' '; + + if (v = DOM.getAttrib(n, 'color')) + ti += 'color: ' + v + ' '; + + break; + + case 'span': + if (v = DOM.getAttrib(n, 'style')) + ti += 'style: ' + v + ' '; + + break; + } + + if (v = DOM.getAttrib(n, 'id')) + ti += 'id: ' + v + ' '; + + if (v = n.className) { + v = v.replace(/\b\s*(webkit|mce|Apple-)\w+\s*\b/g, '') + + if (v) { + ti += 'class: ' + v + ' '; + + if (DOM.isBlock(n) || na == 'img' || na == 'span') + na += '.' + v; + } + } + + na = na.replace(/(html:)/g, ''); + na = {name : na, node : n, title : ti}; + t.onResolveName.dispatch(t, na); + ti = na.title; + na = na.name; + + //u = "javascript:tinymce.EditorManager.get('" + ed.id + "').theme._sel('" + (de++) + "');"; + pi = DOM.create('a', {'href' : "javascript:;", role: 'button', onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de++)}, na); + + if (p.hasChildNodes()) { + p.insertBefore(DOM.create('span', {'aria-hidden': 'true'}, '\u00a0\u00bb '), p.firstChild); + p.insertBefore(pi, p.firstChild); + } else + p.appendChild(pi); + }, ed.getBody()); + + if (DOM.select('a', p).length > 0) { + t.statusKeyboardNavigation = new tinymce.ui.KeyboardNavigation({ + root: ed.id + "_path_row", + items: DOM.select('a', p), + excludeFromTabOrder: true, + onCancel: function() { + ed.focus(); + } + }, DOM); + } + } + }, + + // Commands gets called by execCommand + + _sel : function(v) { + this.editor.execCommand('mceSelectNodeDepth', false, v); + }, + + _mceInsertAnchor : function(ui, v) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/anchor.htm', + width : 320 + parseInt(ed.getLang('advanced.anchor_delta_width', 0)), + height : 90 + parseInt(ed.getLang('advanced.anchor_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceCharMap : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/charmap.htm', + width : 550 + parseInt(ed.getLang('advanced.charmap_delta_width', 0)), + height : 260 + parseInt(ed.getLang('advanced.charmap_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceHelp : function() { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/about.htm', + width : 480, + height : 380, + inline : true + }, { + theme_url : this.url + }); + }, + + _mceShortcuts : function() { + var ed = this.editor; + ed.windowManager.open({ + url: this.url + '/shortcuts.htm', + width: 480, + height: 380, + inline: true + }, { + theme_url: this.url + }); + }, + + _mceColorPicker : function(u, v) { + var ed = this.editor; + + v = v || {}; + + ed.windowManager.open({ + url : this.url + '/color_picker.htm', + width : 375 + parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)), + height : 250 + parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)), + close_previous : false, + inline : true + }, { + input_color : v.color, + func : v.func, + theme_url : this.url + }); + }, + + _mceCodeEditor : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/source_editor.htm', + width : parseInt(ed.getParam("theme_advanced_source_editor_width", 720)), + height : parseInt(ed.getParam("theme_advanced_source_editor_height", 580)), + inline : true, + resizable : true, + maximizable : true + }, { + theme_url : this.url + }); + }, + + _mceImage : function(ui, val) { + var ed = this.editor; + + // Internal image object like a flash placeholder + if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1) + return; + + ed.windowManager.open({ + url : this.url + '/image.htm', + width : 355 + parseInt(ed.getLang('advanced.image_delta_width', 0)), + height : 275 + parseInt(ed.getLang('advanced.image_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceLink : function(ui, val) { + var ed = this.editor; + + ed.windowManager.open({ + url : this.url + '/link.htm', + width : 310 + parseInt(ed.getLang('advanced.link_delta_width', 0)), + height : 200 + parseInt(ed.getLang('advanced.link_delta_height', 0)), + inline : true + }, { + theme_url : this.url + }); + }, + + _mceNewDocument : function() { + var ed = this.editor; + + ed.windowManager.confirm('advanced.newdocument', function(s) { + if (s) + ed.execCommand('mceSetContent', false, ''); + }); + }, + + _mceForeColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.fgColor, + func : function(co) { + t.fgColor = co; + t.editor.execCommand('ForeColor', false, co); + } + }); + }, + + _mceBackColor : function() { + var t = this; + + this._mceColorPicker(0, { + color: t.bgColor, + func : function(co) { + t.bgColor = co; + t.editor.execCommand('HiliteColor', false, co); + } + }); + }, + + _ufirst : function(s) { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + }); + + tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme); +}(tinymce)); diff --git a/js/tiny_mce/themes/advanced/image.htm b/js/tiny_mce/themes/advanced/image.htm index f30d6706..884890fb 100644 --- a/js/tiny_mce/themes/advanced/image.htm +++ b/js/tiny_mce/themes/advanced/image.htm @@ -1,80 +1,80 @@ - - - - {#advanced_dlg.image_title} - - - - - - -
    - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
     
    - x -
    -
    -
    - -
    - - -
    -
    - - + + + + {#advanced_dlg.image_title} + + + + + + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
     
    + x +
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/themes/advanced/img/colorpicker.jpg b/js/tiny_mce/themes/advanced/img/colorpicker.jpg index b4c542d1..b1a377ab 100644 Binary files a/js/tiny_mce/themes/advanced/img/colorpicker.jpg and b/js/tiny_mce/themes/advanced/img/colorpicker.jpg differ diff --git a/js/tiny_mce/themes/advanced/img/flash.gif b/js/tiny_mce/themes/advanced/img/flash.gif new file mode 100644 index 00000000..dec3f7c7 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/flash.gif differ diff --git a/js/tiny_mce/themes/advanced/img/icons.gif b/js/tiny_mce/themes/advanced/img/icons.gif index e46de533..641a9e3d 100644 Binary files a/js/tiny_mce/themes/advanced/img/icons.gif and b/js/tiny_mce/themes/advanced/img/icons.gif differ diff --git a/js/tiny_mce/themes/advanced/img/iframe.gif b/js/tiny_mce/themes/advanced/img/iframe.gif new file mode 100644 index 00000000..410c7ad0 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/iframe.gif differ diff --git a/js/tiny_mce/themes/advanced/img/pagebreak.gif b/js/tiny_mce/themes/advanced/img/pagebreak.gif new file mode 100644 index 00000000..acdf4085 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/pagebreak.gif differ diff --git a/js/tiny_mce/themes/advanced/img/quicktime.gif b/js/tiny_mce/themes/advanced/img/quicktime.gif new file mode 100644 index 00000000..8f10e7aa Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/quicktime.gif differ diff --git a/js/tiny_mce/themes/advanced/img/realmedia.gif b/js/tiny_mce/themes/advanced/img/realmedia.gif new file mode 100644 index 00000000..fdfe0b9a Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/realmedia.gif differ diff --git a/js/tiny_mce/themes/advanced/img/shockwave.gif b/js/tiny_mce/themes/advanced/img/shockwave.gif new file mode 100644 index 00000000..9314d044 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/shockwave.gif differ diff --git a/js/tiny_mce/themes/advanced/img/trans.gif b/js/tiny_mce/themes/advanced/img/trans.gif new file mode 100644 index 00000000..38848651 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/trans.gif differ diff --git a/js/tiny_mce/themes/advanced/img/video.gif b/js/tiny_mce/themes/advanced/img/video.gif new file mode 100644 index 00000000..35701040 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/video.gif differ diff --git a/js/tiny_mce/themes/advanced/img/windowsmedia.gif b/js/tiny_mce/themes/advanced/img/windowsmedia.gif new file mode 100644 index 00000000..ab50f2d8 Binary files /dev/null and b/js/tiny_mce/themes/advanced/img/windowsmedia.gif differ diff --git a/js/tiny_mce/themes/advanced/js/about.js b/js/tiny_mce/themes/advanced/js/about.js index 5cee9ed8..daf4909a 100644 --- a/js/tiny_mce/themes/advanced/js/about.js +++ b/js/tiny_mce/themes/advanced/js/about.js @@ -1,72 +1,73 @@ -tinyMCEPopup.requireLangPack(); - -function init() { - var ed, tcont; - - tinyMCEPopup.resizeToInnerSize(); - ed = tinyMCEPopup.editor; - - // Give FF some time - window.setTimeout(insertHelpIFrame, 10); - - tcont = document.getElementById('plugintablecontainer'); - document.getElementById('plugins_tab').style.display = 'none'; - - var html = ""; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - - tinymce.each(ed.plugins, function(p, n) { - var info; - - if (!p.getInfo) - return; - - html += ''; - - info = p.getInfo(); - - if (info.infourl != null && info.infourl != '') - html += ''; - else - html += ''; - - if (info.authorurl != null && info.authorurl != '') - html += ''; - else - html += ''; - - html += ''; - html += ''; - - document.getElementById('plugins_tab').style.display = ''; - - }); - - html += ''; - html += '
    ' + ed.getLang('advanced_dlg.about_plugin') + '' + ed.getLang('advanced_dlg.about_author') + '' + ed.getLang('advanced_dlg.about_version') + '
    ' + info.longname + '' + info.longname + '' + info.author + '' + info.author + '' + info.version + '
    '; - - tcont.innerHTML = html; - - tinyMCEPopup.dom.get('version').innerHTML = tinymce.majorVersion + "." + tinymce.minorVersion; - tinyMCEPopup.dom.get('date').innerHTML = tinymce.releaseDate; -} - -function insertHelpIFrame() { - var html; - - if (tinyMCEPopup.getParam('docs_url')) { - html = ''; - document.getElementById('iframecontainer').innerHTML = html; - document.getElementById('help_tab').style.display = 'block'; - } -} - -tinyMCEPopup.onInit.add(init); +tinyMCEPopup.requireLangPack(); + +function init() { + var ed, tcont; + + tinyMCEPopup.resizeToInnerSize(); + ed = tinyMCEPopup.editor; + + // Give FF some time + window.setTimeout(insertHelpIFrame, 10); + + tcont = document.getElementById('plugintablecontainer'); + document.getElementById('plugins_tab').style.display = 'none'; + + var html = ""; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + tinymce.each(ed.plugins, function(p, n) { + var info; + + if (!p.getInfo) + return; + + html += ''; + + info = p.getInfo(); + + if (info.infourl != null && info.infourl != '') + html += ''; + else + html += ''; + + if (info.authorurl != null && info.authorurl != '') + html += ''; + else + html += ''; + + html += ''; + html += ''; + + document.getElementById('plugins_tab').style.display = ''; + + }); + + html += ''; + html += '
    ' + ed.getLang('advanced_dlg.about_plugin') + '' + ed.getLang('advanced_dlg.about_author') + '' + ed.getLang('advanced_dlg.about_version') + '
    ' + info.longname + '' + info.longname + '' + info.author + '' + info.author + '' + info.version + '
    '; + + tcont.innerHTML = html; + + tinyMCEPopup.dom.get('version').innerHTML = tinymce.majorVersion + "." + tinymce.minorVersion; + tinyMCEPopup.dom.get('date').innerHTML = tinymce.releaseDate; +} + +function insertHelpIFrame() { + var html; + + if (tinyMCEPopup.getParam('docs_url')) { + html = ''; + document.getElementById('iframecontainer').innerHTML = html; + document.getElementById('help_tab').style.display = 'block'; + document.getElementById('help_tab').setAttribute("aria-hidden", "false"); + } +} + +tinyMCEPopup.onInit.add(init); diff --git a/js/tiny_mce/themes/advanced/js/anchor.js b/js/tiny_mce/themes/advanced/js/anchor.js index 7fe78105..b6c5b695 100644 --- a/js/tiny_mce/themes/advanced/js/anchor.js +++ b/js/tiny_mce/themes/advanced/js/anchor.js @@ -1,37 +1,43 @@ -tinyMCEPopup.requireLangPack(); - -var AnchorDialog = { - init : function(ed) { - var action, elm, f = document.forms[0]; - - this.editor = ed; - elm = ed.dom.getParent(ed.selection.getNode(), 'A'); - v = ed.dom.getAttrib(elm, 'name'); - - if (v) { - this.action = 'update'; - f.anchorName.value = v; - } - - f.insert.value = ed.getLang(elm ? 'update' : 'insert'); - }, - - update : function() { - var ed = this.editor, elm, name = document.forms[0].anchorName.value; - - tinyMCEPopup.restoreSelection(); - - if (this.action != 'update') - ed.selection.collapse(1); - - elm = ed.dom.getParent(ed.selection.getNode(), 'A'); - if (elm) - elm.name = name; - else - ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('a', {name : name, 'class' : 'mceItemAnchor'}, '')); - - tinyMCEPopup.close(); - } -}; - -tinyMCEPopup.onInit.add(AnchorDialog.init, AnchorDialog); +tinyMCEPopup.requireLangPack(); + +var AnchorDialog = { + init : function(ed) { + var action, elm, f = document.forms[0]; + + this.editor = ed; + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + v = ed.dom.getAttrib(elm, 'name'); + + if (v) { + this.action = 'update'; + f.anchorName.value = v; + } + + f.insert.value = ed.getLang(elm ? 'update' : 'insert'); + }, + + update : function() { + var ed = this.editor, elm, name = document.forms[0].anchorName.value; + + if (!name || !/^[a-z][a-z0-9\-\_:\.]*$/i.test(name)) { + tinyMCEPopup.alert('advanced_dlg.anchor_invalid'); + return; + } + + tinyMCEPopup.restoreSelection(); + + if (this.action != 'update') + ed.selection.collapse(1); + + elm = ed.dom.getParent(ed.selection.getNode(), 'A'); + if (elm) { + elm.setAttribute('name', name); + elm.name = name; + } else + ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('a', {name : name, 'class' : 'mceItemAnchor'}, '')); + + tinyMCEPopup.close(); + } +}; + +tinyMCEPopup.onInit.add(AnchorDialog.init, AnchorDialog); diff --git a/js/tiny_mce/themes/advanced/js/charmap.js b/js/tiny_mce/themes/advanced/js/charmap.js index 8c5aea17..cbb4172b 100644 --- a/js/tiny_mce/themes/advanced/js/charmap.js +++ b/js/tiny_mce/themes/advanced/js/charmap.js @@ -1,335 +1,363 @@ -/** - * charmap.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -tinyMCEPopup.requireLangPack(); - -var charmap = [ - [' ', ' ', true, 'no-break space'], - ['&', '&', true, 'ampersand'], - ['"', '"', true, 'quotation mark'], -// finance - ['¢', '¢', true, 'cent sign'], - ['€', '€', true, 'euro sign'], - ['£', '£', true, 'pound sign'], - ['¥', '¥', true, 'yen sign'], -// signs - ['©', '©', true, 'copyright sign'], - ['®', '®', true, 'registered sign'], - ['™', '™', true, 'trade mark sign'], - ['‰', '‰', true, 'per mille sign'], - ['µ', 'µ', true, 'micro sign'], - ['·', '·', true, 'middle dot'], - ['•', '•', true, 'bullet'], - ['…', '…', true, 'three dot leader'], - ['′', '′', true, 'minutes / feet'], - ['″', '″', true, 'seconds / inches'], - ['§', '§', true, 'section sign'], - ['¶', '¶', true, 'paragraph sign'], - ['ß', 'ß', true, 'sharp s / ess-zed'], -// quotations - ['‹', '‹', true, 'single left-pointing angle quotation mark'], - ['›', '›', true, 'single right-pointing angle quotation mark'], - ['«', '«', true, 'left pointing guillemet'], - ['»', '»', true, 'right pointing guillemet'], - ['‘', '‘', true, 'left single quotation mark'], - ['’', '’', true, 'right single quotation mark'], - ['“', '“', true, 'left double quotation mark'], - ['”', '”', true, 'right double quotation mark'], - ['‚', '‚', true, 'single low-9 quotation mark'], - ['„', '„', true, 'double low-9 quotation mark'], - ['<', '<', true, 'less-than sign'], - ['>', '>', true, 'greater-than sign'], - ['≤', '≤', true, 'less-than or equal to'], - ['≥', '≥', true, 'greater-than or equal to'], - ['–', '–', true, 'en dash'], - ['—', '—', true, 'em dash'], - ['¯', '¯', true, 'macron'], - ['‾', '‾', true, 'overline'], - ['¤', '¤', true, 'currency sign'], - ['¦', '¦', true, 'broken bar'], - ['¨', '¨', true, 'diaeresis'], - ['¡', '¡', true, 'inverted exclamation mark'], - ['¿', '¿', true, 'turned question mark'], - ['ˆ', 'ˆ', true, 'circumflex accent'], - ['˜', '˜', true, 'small tilde'], - ['°', '°', true, 'degree sign'], - ['−', '−', true, 'minus sign'], - ['±', '±', true, 'plus-minus sign'], - ['÷', '÷', true, 'division sign'], - ['⁄', '⁄', true, 'fraction slash'], - ['×', '×', true, 'multiplication sign'], - ['¹', '¹', true, 'superscript one'], - ['²', '²', true, 'superscript two'], - ['³', '³', true, 'superscript three'], - ['¼', '¼', true, 'fraction one quarter'], - ['½', '½', true, 'fraction one half'], - ['¾', '¾', true, 'fraction three quarters'], -// math / logical - ['ƒ', 'ƒ', true, 'function / florin'], - ['∫', '∫', true, 'integral'], - ['∑', '∑', true, 'n-ary sumation'], - ['∞', '∞', true, 'infinity'], - ['√', '√', true, 'square root'], - ['∼', '∼', false,'similar to'], - ['≅', '≅', false,'approximately equal to'], - ['≈', '≈', true, 'almost equal to'], - ['≠', '≠', true, 'not equal to'], - ['≡', '≡', true, 'identical to'], - ['∈', '∈', false,'element of'], - ['∉', '∉', false,'not an element of'], - ['∋', '∋', false,'contains as member'], - ['∏', '∏', true, 'n-ary product'], - ['∧', '∧', false,'logical and'], - ['∨', '∨', false,'logical or'], - ['¬', '¬', true, 'not sign'], - ['∩', '∩', true, 'intersection'], - ['∪', '∪', false,'union'], - ['∂', '∂', true, 'partial differential'], - ['∀', '∀', false,'for all'], - ['∃', '∃', false,'there exists'], - ['∅', '∅', false,'diameter'], - ['∇', '∇', false,'backward difference'], - ['∗', '∗', false,'asterisk operator'], - ['∝', '∝', false,'proportional to'], - ['∠', '∠', false,'angle'], -// undefined - ['´', '´', true, 'acute accent'], - ['¸', '¸', true, 'cedilla'], - ['ª', 'ª', true, 'feminine ordinal indicator'], - ['º', 'º', true, 'masculine ordinal indicator'], - ['†', '†', true, 'dagger'], - ['‡', '‡', true, 'double dagger'], -// alphabetical special chars - ['À', 'À', true, 'A - grave'], - ['Á', 'Á', true, 'A - acute'], - ['Â', 'Â', true, 'A - circumflex'], - ['Ã', 'Ã', true, 'A - tilde'], - ['Ä', 'Ä', true, 'A - diaeresis'], - ['Å', 'Å', true, 'A - ring above'], - ['Æ', 'Æ', true, 'ligature AE'], - ['Ç', 'Ç', true, 'C - cedilla'], - ['È', 'È', true, 'E - grave'], - ['É', 'É', true, 'E - acute'], - ['Ê', 'Ê', true, 'E - circumflex'], - ['Ë', 'Ë', true, 'E - diaeresis'], - ['Ì', 'Ì', true, 'I - grave'], - ['Í', 'Í', true, 'I - acute'], - ['Î', 'Î', true, 'I - circumflex'], - ['Ï', 'Ï', true, 'I - diaeresis'], - ['Ð', 'Ð', true, 'ETH'], - ['Ñ', 'Ñ', true, 'N - tilde'], - ['Ò', 'Ò', true, 'O - grave'], - ['Ó', 'Ó', true, 'O - acute'], - ['Ô', 'Ô', true, 'O - circumflex'], - ['Õ', 'Õ', true, 'O - tilde'], - ['Ö', 'Ö', true, 'O - diaeresis'], - ['Ø', 'Ø', true, 'O - slash'], - ['Œ', 'Œ', true, 'ligature OE'], - ['Š', 'Š', true, 'S - caron'], - ['Ù', 'Ù', true, 'U - grave'], - ['Ú', 'Ú', true, 'U - acute'], - ['Û', 'Û', true, 'U - circumflex'], - ['Ü', 'Ü', true, 'U - diaeresis'], - ['Ý', 'Ý', true, 'Y - acute'], - ['Ÿ', 'Ÿ', true, 'Y - diaeresis'], - ['Þ', 'Þ', true, 'THORN'], - ['à', 'à', true, 'a - grave'], - ['á', 'á', true, 'a - acute'], - ['â', 'â', true, 'a - circumflex'], - ['ã', 'ã', true, 'a - tilde'], - ['ä', 'ä', true, 'a - diaeresis'], - ['å', 'å', true, 'a - ring above'], - ['æ', 'æ', true, 'ligature ae'], - ['ç', 'ç', true, 'c - cedilla'], - ['è', 'è', true, 'e - grave'], - ['é', 'é', true, 'e - acute'], - ['ê', 'ê', true, 'e - circumflex'], - ['ë', 'ë', true, 'e - diaeresis'], - ['ì', 'ì', true, 'i - grave'], - ['í', 'í', true, 'i - acute'], - ['î', 'î', true, 'i - circumflex'], - ['ï', 'ï', true, 'i - diaeresis'], - ['ð', 'ð', true, 'eth'], - ['ñ', 'ñ', true, 'n - tilde'], - ['ò', 'ò', true, 'o - grave'], - ['ó', 'ó', true, 'o - acute'], - ['ô', 'ô', true, 'o - circumflex'], - ['õ', 'õ', true, 'o - tilde'], - ['ö', 'ö', true, 'o - diaeresis'], - ['ø', 'ø', true, 'o slash'], - ['œ', 'œ', true, 'ligature oe'], - ['š', 'š', true, 's - caron'], - ['ù', 'ù', true, 'u - grave'], - ['ú', 'ú', true, 'u - acute'], - ['û', 'û', true, 'u - circumflex'], - ['ü', 'ü', true, 'u - diaeresis'], - ['ý', 'ý', true, 'y - acute'], - ['þ', 'þ', true, 'thorn'], - ['ÿ', 'ÿ', true, 'y - diaeresis'], - ['Α', 'Α', true, 'Alpha'], - ['Β', 'Β', true, 'Beta'], - ['Γ', 'Γ', true, 'Gamma'], - ['Δ', 'Δ', true, 'Delta'], - ['Ε', 'Ε', true, 'Epsilon'], - ['Ζ', 'Ζ', true, 'Zeta'], - ['Η', 'Η', true, 'Eta'], - ['Θ', 'Θ', true, 'Theta'], - ['Ι', 'Ι', true, 'Iota'], - ['Κ', 'Κ', true, 'Kappa'], - ['Λ', 'Λ', true, 'Lambda'], - ['Μ', 'Μ', true, 'Mu'], - ['Ν', 'Ν', true, 'Nu'], - ['Ξ', 'Ξ', true, 'Xi'], - ['Ο', 'Ο', true, 'Omicron'], - ['Π', 'Π', true, 'Pi'], - ['Ρ', 'Ρ', true, 'Rho'], - ['Σ', 'Σ', true, 'Sigma'], - ['Τ', 'Τ', true, 'Tau'], - ['Υ', 'Υ', true, 'Upsilon'], - ['Φ', 'Φ', true, 'Phi'], - ['Χ', 'Χ', true, 'Chi'], - ['Ψ', 'Ψ', true, 'Psi'], - ['Ω', 'Ω', true, 'Omega'], - ['α', 'α', true, 'alpha'], - ['β', 'β', true, 'beta'], - ['γ', 'γ', true, 'gamma'], - ['δ', 'δ', true, 'delta'], - ['ε', 'ε', true, 'epsilon'], - ['ζ', 'ζ', true, 'zeta'], - ['η', 'η', true, 'eta'], - ['θ', 'θ', true, 'theta'], - ['ι', 'ι', true, 'iota'], - ['κ', 'κ', true, 'kappa'], - ['λ', 'λ', true, 'lambda'], - ['μ', 'μ', true, 'mu'], - ['ν', 'ν', true, 'nu'], - ['ξ', 'ξ', true, 'xi'], - ['ο', 'ο', true, 'omicron'], - ['π', 'π', true, 'pi'], - ['ρ', 'ρ', true, 'rho'], - ['ς', 'ς', true, 'final sigma'], - ['σ', 'σ', true, 'sigma'], - ['τ', 'τ', true, 'tau'], - ['υ', 'υ', true, 'upsilon'], - ['φ', 'φ', true, 'phi'], - ['χ', 'χ', true, 'chi'], - ['ψ', 'ψ', true, 'psi'], - ['ω', 'ω', true, 'omega'], -// symbols - ['ℵ', 'ℵ', false,'alef symbol'], - ['ϖ', 'ϖ', false,'pi symbol'], - ['ℜ', 'ℜ', false,'real part symbol'], - ['ϑ','ϑ', false,'theta symbol'], - ['ϒ', 'ϒ', false,'upsilon - hook symbol'], - ['℘', '℘', false,'Weierstrass p'], - ['ℑ', 'ℑ', false,'imaginary part'], -// arrows - ['←', '←', true, 'leftwards arrow'], - ['↑', '↑', true, 'upwards arrow'], - ['→', '→', true, 'rightwards arrow'], - ['↓', '↓', true, 'downwards arrow'], - ['↔', '↔', true, 'left right arrow'], - ['↵', '↵', false,'carriage return'], - ['⇐', '⇐', false,'leftwards double arrow'], - ['⇑', '⇑', false,'upwards double arrow'], - ['⇒', '⇒', false,'rightwards double arrow'], - ['⇓', '⇓', false,'downwards double arrow'], - ['⇔', '⇔', false,'left right double arrow'], - ['∴', '∴', false,'therefore'], - ['⊂', '⊂', false,'subset of'], - ['⊃', '⊃', false,'superset of'], - ['⊄', '⊄', false,'not a subset of'], - ['⊆', '⊆', false,'subset of or equal to'], - ['⊇', '⊇', false,'superset of or equal to'], - ['⊕', '⊕', false,'circled plus'], - ['⊗', '⊗', false,'circled times'], - ['⊥', '⊥', false,'perpendicular'], - ['⋅', '⋅', false,'dot operator'], - ['⌈', '⌈', false,'left ceiling'], - ['⌉', '⌉', false,'right ceiling'], - ['⌊', '⌊', false,'left floor'], - ['⌋', '⌋', false,'right floor'], - ['⟨', '〈', false,'left-pointing angle bracket'], - ['⟩', '〉', false,'right-pointing angle bracket'], - ['◊', '◊', true,'lozenge'], - ['♠', '♠', false,'black spade suit'], - ['♣', '♣', true, 'black club suit'], - ['♥', '♥', true, 'black heart suit'], - ['♦', '♦', true, 'black diamond suit'], - [' ', ' ', false,'en space'], - [' ', ' ', false,'em space'], - [' ', ' ', false,'thin space'], - ['‌', '‌', false,'zero width non-joiner'], - ['‍', '‍', false,'zero width joiner'], - ['‎', '‎', false,'left-to-right mark'], - ['‏', '‏', false,'right-to-left mark'], - ['­', '­', false,'soft hyphen'] -]; - -tinyMCEPopup.onInit.add(function() { - tinyMCEPopup.dom.setHTML('charmapView', renderCharMapHTML()); -}); - -function renderCharMapHTML() { - var charsPerRow = 20, tdWidth=20, tdHeight=20, i; - var html = ''; - var cols=-1; - - for (i=0; i' - + '' - + charmap[i][1] - + ''; - if ((cols+1) % charsPerRow == 0) - html += ''; - } - } - - if (cols % charsPerRow > 0) { - var padd = charsPerRow - (cols % charsPerRow); - for (var i=0; i '; - } - - html += '
    '; - - return html; -} - -function insertChar(chr) { - tinyMCEPopup.execCommand('mceInsertContent', false, '&#' + chr + ';'); - - // Refocus in window - if (tinyMCEPopup.isWindow) - window.focus(); - - tinyMCEPopup.editor.focus(); - tinyMCEPopup.close(); -} - -function previewChar(codeA, codeB, codeN) { - var elmA = document.getElementById('codeA'); - var elmB = document.getElementById('codeB'); - var elmV = document.getElementById('codeV'); - var elmN = document.getElementById('codeN'); - - if (codeA=='#160;') { - elmV.innerHTML = '__'; - } else { - elmV.innerHTML = '&' + codeA; - } - - elmB.innerHTML = '&' + codeA; - elmA.innerHTML = '&' + codeB; - elmN.innerHTML = codeN; -} +/** + * charmap.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +tinyMCEPopup.requireLangPack(); + +var charmap = [ + [' ', ' ', true, 'no-break space'], + ['&', '&', true, 'ampersand'], + ['"', '"', true, 'quotation mark'], +// finance + ['¢', '¢', true, 'cent sign'], + ['€', '€', true, 'euro sign'], + ['£', '£', true, 'pound sign'], + ['¥', '¥', true, 'yen sign'], +// signs + ['©', '©', true, 'copyright sign'], + ['®', '®', true, 'registered sign'], + ['™', '™', true, 'trade mark sign'], + ['‰', '‰', true, 'per mille sign'], + ['µ', 'µ', true, 'micro sign'], + ['·', '·', true, 'middle dot'], + ['•', '•', true, 'bullet'], + ['…', '…', true, 'three dot leader'], + ['′', '′', true, 'minutes / feet'], + ['″', '″', true, 'seconds / inches'], + ['§', '§', true, 'section sign'], + ['¶', '¶', true, 'paragraph sign'], + ['ß', 'ß', true, 'sharp s / ess-zed'], +// quotations + ['‹', '‹', true, 'single left-pointing angle quotation mark'], + ['›', '›', true, 'single right-pointing angle quotation mark'], + ['«', '«', true, 'left pointing guillemet'], + ['»', '»', true, 'right pointing guillemet'], + ['‘', '‘', true, 'left single quotation mark'], + ['’', '’', true, 'right single quotation mark'], + ['“', '“', true, 'left double quotation mark'], + ['”', '”', true, 'right double quotation mark'], + ['‚', '‚', true, 'single low-9 quotation mark'], + ['„', '„', true, 'double low-9 quotation mark'], + ['<', '<', true, 'less-than sign'], + ['>', '>', true, 'greater-than sign'], + ['≤', '≤', true, 'less-than or equal to'], + ['≥', '≥', true, 'greater-than or equal to'], + ['–', '–', true, 'en dash'], + ['—', '—', true, 'em dash'], + ['¯', '¯', true, 'macron'], + ['‾', '‾', true, 'overline'], + ['¤', '¤', true, 'currency sign'], + ['¦', '¦', true, 'broken bar'], + ['¨', '¨', true, 'diaeresis'], + ['¡', '¡', true, 'inverted exclamation mark'], + ['¿', '¿', true, 'turned question mark'], + ['ˆ', 'ˆ', true, 'circumflex accent'], + ['˜', '˜', true, 'small tilde'], + ['°', '°', true, 'degree sign'], + ['−', '−', true, 'minus sign'], + ['±', '±', true, 'plus-minus sign'], + ['÷', '÷', true, 'division sign'], + ['⁄', '⁄', true, 'fraction slash'], + ['×', '×', true, 'multiplication sign'], + ['¹', '¹', true, 'superscript one'], + ['²', '²', true, 'superscript two'], + ['³', '³', true, 'superscript three'], + ['¼', '¼', true, 'fraction one quarter'], + ['½', '½', true, 'fraction one half'], + ['¾', '¾', true, 'fraction three quarters'], +// math / logical + ['ƒ', 'ƒ', true, 'function / florin'], + ['∫', '∫', true, 'integral'], + ['∑', '∑', true, 'n-ary sumation'], + ['∞', '∞', true, 'infinity'], + ['√', '√', true, 'square root'], + ['∼', '∼', false,'similar to'], + ['≅', '≅', false,'approximately equal to'], + ['≈', '≈', true, 'almost equal to'], + ['≠', '≠', true, 'not equal to'], + ['≡', '≡', true, 'identical to'], + ['∈', '∈', false,'element of'], + ['∉', '∉', false,'not an element of'], + ['∋', '∋', false,'contains as member'], + ['∏', '∏', true, 'n-ary product'], + ['∧', '∧', false,'logical and'], + ['∨', '∨', false,'logical or'], + ['¬', '¬', true, 'not sign'], + ['∩', '∩', true, 'intersection'], + ['∪', '∪', false,'union'], + ['∂', '∂', true, 'partial differential'], + ['∀', '∀', false,'for all'], + ['∃', '∃', false,'there exists'], + ['∅', '∅', false,'diameter'], + ['∇', '∇', false,'backward difference'], + ['∗', '∗', false,'asterisk operator'], + ['∝', '∝', false,'proportional to'], + ['∠', '∠', false,'angle'], +// undefined + ['´', '´', true, 'acute accent'], + ['¸', '¸', true, 'cedilla'], + ['ª', 'ª', true, 'feminine ordinal indicator'], + ['º', 'º', true, 'masculine ordinal indicator'], + ['†', '†', true, 'dagger'], + ['‡', '‡', true, 'double dagger'], +// alphabetical special chars + ['À', 'À', true, 'A - grave'], + ['Á', 'Á', true, 'A - acute'], + ['Â', 'Â', true, 'A - circumflex'], + ['Ã', 'Ã', true, 'A - tilde'], + ['Ä', 'Ä', true, 'A - diaeresis'], + ['Å', 'Å', true, 'A - ring above'], + ['Æ', 'Æ', true, 'ligature AE'], + ['Ç', 'Ç', true, 'C - cedilla'], + ['È', 'È', true, 'E - grave'], + ['É', 'É', true, 'E - acute'], + ['Ê', 'Ê', true, 'E - circumflex'], + ['Ë', 'Ë', true, 'E - diaeresis'], + ['Ì', 'Ì', true, 'I - grave'], + ['Í', 'Í', true, 'I - acute'], + ['Î', 'Î', true, 'I - circumflex'], + ['Ï', 'Ï', true, 'I - diaeresis'], + ['Ð', 'Ð', true, 'ETH'], + ['Ñ', 'Ñ', true, 'N - tilde'], + ['Ò', 'Ò', true, 'O - grave'], + ['Ó', 'Ó', true, 'O - acute'], + ['Ô', 'Ô', true, 'O - circumflex'], + ['Õ', 'Õ', true, 'O - tilde'], + ['Ö', 'Ö', true, 'O - diaeresis'], + ['Ø', 'Ø', true, 'O - slash'], + ['Œ', 'Œ', true, 'ligature OE'], + ['Š', 'Š', true, 'S - caron'], + ['Ù', 'Ù', true, 'U - grave'], + ['Ú', 'Ú', true, 'U - acute'], + ['Û', 'Û', true, 'U - circumflex'], + ['Ü', 'Ü', true, 'U - diaeresis'], + ['Ý', 'Ý', true, 'Y - acute'], + ['Ÿ', 'Ÿ', true, 'Y - diaeresis'], + ['Þ', 'Þ', true, 'THORN'], + ['à', 'à', true, 'a - grave'], + ['á', 'á', true, 'a - acute'], + ['â', 'â', true, 'a - circumflex'], + ['ã', 'ã', true, 'a - tilde'], + ['ä', 'ä', true, 'a - diaeresis'], + ['å', 'å', true, 'a - ring above'], + ['æ', 'æ', true, 'ligature ae'], + ['ç', 'ç', true, 'c - cedilla'], + ['è', 'è', true, 'e - grave'], + ['é', 'é', true, 'e - acute'], + ['ê', 'ê', true, 'e - circumflex'], + ['ë', 'ë', true, 'e - diaeresis'], + ['ì', 'ì', true, 'i - grave'], + ['í', 'í', true, 'i - acute'], + ['î', 'î', true, 'i - circumflex'], + ['ï', 'ï', true, 'i - diaeresis'], + ['ð', 'ð', true, 'eth'], + ['ñ', 'ñ', true, 'n - tilde'], + ['ò', 'ò', true, 'o - grave'], + ['ó', 'ó', true, 'o - acute'], + ['ô', 'ô', true, 'o - circumflex'], + ['õ', 'õ', true, 'o - tilde'], + ['ö', 'ö', true, 'o - diaeresis'], + ['ø', 'ø', true, 'o slash'], + ['œ', 'œ', true, 'ligature oe'], + ['š', 'š', true, 's - caron'], + ['ù', 'ù', true, 'u - grave'], + ['ú', 'ú', true, 'u - acute'], + ['û', 'û', true, 'u - circumflex'], + ['ü', 'ü', true, 'u - diaeresis'], + ['ý', 'ý', true, 'y - acute'], + ['þ', 'þ', true, 'thorn'], + ['ÿ', 'ÿ', true, 'y - diaeresis'], + ['Α', 'Α', true, 'Alpha'], + ['Β', 'Β', true, 'Beta'], + ['Γ', 'Γ', true, 'Gamma'], + ['Δ', 'Δ', true, 'Delta'], + ['Ε', 'Ε', true, 'Epsilon'], + ['Ζ', 'Ζ', true, 'Zeta'], + ['Η', 'Η', true, 'Eta'], + ['Θ', 'Θ', true, 'Theta'], + ['Ι', 'Ι', true, 'Iota'], + ['Κ', 'Κ', true, 'Kappa'], + ['Λ', 'Λ', true, 'Lambda'], + ['Μ', 'Μ', true, 'Mu'], + ['Ν', 'Ν', true, 'Nu'], + ['Ξ', 'Ξ', true, 'Xi'], + ['Ο', 'Ο', true, 'Omicron'], + ['Π', 'Π', true, 'Pi'], + ['Ρ', 'Ρ', true, 'Rho'], + ['Σ', 'Σ', true, 'Sigma'], + ['Τ', 'Τ', true, 'Tau'], + ['Υ', 'Υ', true, 'Upsilon'], + ['Φ', 'Φ', true, 'Phi'], + ['Χ', 'Χ', true, 'Chi'], + ['Ψ', 'Ψ', true, 'Psi'], + ['Ω', 'Ω', true, 'Omega'], + ['α', 'α', true, 'alpha'], + ['β', 'β', true, 'beta'], + ['γ', 'γ', true, 'gamma'], + ['δ', 'δ', true, 'delta'], + ['ε', 'ε', true, 'epsilon'], + ['ζ', 'ζ', true, 'zeta'], + ['η', 'η', true, 'eta'], + ['θ', 'θ', true, 'theta'], + ['ι', 'ι', true, 'iota'], + ['κ', 'κ', true, 'kappa'], + ['λ', 'λ', true, 'lambda'], + ['μ', 'μ', true, 'mu'], + ['ν', 'ν', true, 'nu'], + ['ξ', 'ξ', true, 'xi'], + ['ο', 'ο', true, 'omicron'], + ['π', 'π', true, 'pi'], + ['ρ', 'ρ', true, 'rho'], + ['ς', 'ς', true, 'final sigma'], + ['σ', 'σ', true, 'sigma'], + ['τ', 'τ', true, 'tau'], + ['υ', 'υ', true, 'upsilon'], + ['φ', 'φ', true, 'phi'], + ['χ', 'χ', true, 'chi'], + ['ψ', 'ψ', true, 'psi'], + ['ω', 'ω', true, 'omega'], +// symbols + ['ℵ', 'ℵ', false,'alef symbol'], + ['ϖ', 'ϖ', false,'pi symbol'], + ['ℜ', 'ℜ', false,'real part symbol'], + ['ϑ','ϑ', false,'theta symbol'], + ['ϒ', 'ϒ', false,'upsilon - hook symbol'], + ['℘', '℘', false,'Weierstrass p'], + ['ℑ', 'ℑ', false,'imaginary part'], +// arrows + ['←', '←', true, 'leftwards arrow'], + ['↑', '↑', true, 'upwards arrow'], + ['→', '→', true, 'rightwards arrow'], + ['↓', '↓', true, 'downwards arrow'], + ['↔', '↔', true, 'left right arrow'], + ['↵', '↵', false,'carriage return'], + ['⇐', '⇐', false,'leftwards double arrow'], + ['⇑', '⇑', false,'upwards double arrow'], + ['⇒', '⇒', false,'rightwards double arrow'], + ['⇓', '⇓', false,'downwards double arrow'], + ['⇔', '⇔', false,'left right double arrow'], + ['∴', '∴', false,'therefore'], + ['⊂', '⊂', false,'subset of'], + ['⊃', '⊃', false,'superset of'], + ['⊄', '⊄', false,'not a subset of'], + ['⊆', '⊆', false,'subset of or equal to'], + ['⊇', '⊇', false,'superset of or equal to'], + ['⊕', '⊕', false,'circled plus'], + ['⊗', '⊗', false,'circled times'], + ['⊥', '⊥', false,'perpendicular'], + ['⋅', '⋅', false,'dot operator'], + ['⌈', '⌈', false,'left ceiling'], + ['⌉', '⌉', false,'right ceiling'], + ['⌊', '⌊', false,'left floor'], + ['⌋', '⌋', false,'right floor'], + ['⟨', '〈', false,'left-pointing angle bracket'], + ['⟩', '〉', false,'right-pointing angle bracket'], + ['◊', '◊', true, 'lozenge'], + ['♠', '♠', true, 'black spade suit'], + ['♣', '♣', true, 'black club suit'], + ['♥', '♥', true, 'black heart suit'], + ['♦', '♦', true, 'black diamond suit'], + [' ', ' ', false,'en space'], + [' ', ' ', false,'em space'], + [' ', ' ', false,'thin space'], + ['‌', '‌', false,'zero width non-joiner'], + ['‍', '‍', false,'zero width joiner'], + ['‎', '‎', false,'left-to-right mark'], + ['‏', '‏', false,'right-to-left mark'], + ['­', '­', false,'soft hyphen'] +]; + +tinyMCEPopup.onInit.add(function() { + tinyMCEPopup.dom.setHTML('charmapView', renderCharMapHTML()); + addKeyboardNavigation(); +}); + +function addKeyboardNavigation(){ + var tableElm, cells, settings; + + cells = tinyMCEPopup.dom.select("a.charmaplink", "charmapgroup"); + + settings ={ + root: "charmapgroup", + items: cells + }; + cells[0].tabindex=0; + tinyMCEPopup.dom.addClass(cells[0], "mceFocus"); + if (tinymce.isGecko) { + cells[0].focus(); + } else { + setTimeout(function(){ + cells[0].focus(); + }, 100); + } + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', settings, tinyMCEPopup.dom); +} + +function renderCharMapHTML() { + var charsPerRow = 20, tdWidth=20, tdHeight=20, i; + var html = '
    '+ + ''; + var cols=-1; + + for (i=0; i' + + '' + + charmap[i][1] + + ''; + if ((cols+1) % charsPerRow == 0) + html += ''; + } + } + + if (cols % charsPerRow > 0) { + var padd = charsPerRow - (cols % charsPerRow); + for (var i=0; i '; + } + + html += '
    '; + html = html.replace(/<\/tr>/g, ''); + + return html; +} + +function insertChar(chr) { + tinyMCEPopup.execCommand('mceInsertContent', false, '&#' + chr + ';'); + + // Refocus in window + if (tinyMCEPopup.isWindow) + window.focus(); + + tinyMCEPopup.editor.focus(); + tinyMCEPopup.close(); +} + +function previewChar(codeA, codeB, codeN) { + var elmA = document.getElementById('codeA'); + var elmB = document.getElementById('codeB'); + var elmV = document.getElementById('codeV'); + var elmN = document.getElementById('codeN'); + + if (codeA=='#160;') { + elmV.innerHTML = '__'; + } else { + elmV.innerHTML = '&' + codeA; + } + + elmB.innerHTML = '&' + codeA; + elmA.innerHTML = '&' + codeB; + elmN.innerHTML = codeN; +} diff --git a/js/tiny_mce/themes/advanced/js/color_picker.js b/js/tiny_mce/themes/advanced/js/color_picker.js index fd9700f2..3cbf32c4 100644 --- a/js/tiny_mce/themes/advanced/js/color_picker.js +++ b/js/tiny_mce/themes/advanced/js/color_picker.js @@ -1,253 +1,329 @@ -tinyMCEPopup.requireLangPack(); - -var detail = 50, strhex = "0123456789abcdef", i, isMouseDown = false, isMouseOver = false; - -var colors = [ - "#000000","#000033","#000066","#000099","#0000cc","#0000ff","#330000","#330033", - "#330066","#330099","#3300cc","#3300ff","#660000","#660033","#660066","#660099", - "#6600cc","#6600ff","#990000","#990033","#990066","#990099","#9900cc","#9900ff", - "#cc0000","#cc0033","#cc0066","#cc0099","#cc00cc","#cc00ff","#ff0000","#ff0033", - "#ff0066","#ff0099","#ff00cc","#ff00ff","#003300","#003333","#003366","#003399", - "#0033cc","#0033ff","#333300","#333333","#333366","#333399","#3333cc","#3333ff", - "#663300","#663333","#663366","#663399","#6633cc","#6633ff","#993300","#993333", - "#993366","#993399","#9933cc","#9933ff","#cc3300","#cc3333","#cc3366","#cc3399", - "#cc33cc","#cc33ff","#ff3300","#ff3333","#ff3366","#ff3399","#ff33cc","#ff33ff", - "#006600","#006633","#006666","#006699","#0066cc","#0066ff","#336600","#336633", - "#336666","#336699","#3366cc","#3366ff","#666600","#666633","#666666","#666699", - "#6666cc","#6666ff","#996600","#996633","#996666","#996699","#9966cc","#9966ff", - "#cc6600","#cc6633","#cc6666","#cc6699","#cc66cc","#cc66ff","#ff6600","#ff6633", - "#ff6666","#ff6699","#ff66cc","#ff66ff","#009900","#009933","#009966","#009999", - "#0099cc","#0099ff","#339900","#339933","#339966","#339999","#3399cc","#3399ff", - "#669900","#669933","#669966","#669999","#6699cc","#6699ff","#999900","#999933", - "#999966","#999999","#9999cc","#9999ff","#cc9900","#cc9933","#cc9966","#cc9999", - "#cc99cc","#cc99ff","#ff9900","#ff9933","#ff9966","#ff9999","#ff99cc","#ff99ff", - "#00cc00","#00cc33","#00cc66","#00cc99","#00cccc","#00ccff","#33cc00","#33cc33", - "#33cc66","#33cc99","#33cccc","#33ccff","#66cc00","#66cc33","#66cc66","#66cc99", - "#66cccc","#66ccff","#99cc00","#99cc33","#99cc66","#99cc99","#99cccc","#99ccff", - "#cccc00","#cccc33","#cccc66","#cccc99","#cccccc","#ccccff","#ffcc00","#ffcc33", - "#ffcc66","#ffcc99","#ffcccc","#ffccff","#00ff00","#00ff33","#00ff66","#00ff99", - "#00ffcc","#00ffff","#33ff00","#33ff33","#33ff66","#33ff99","#33ffcc","#33ffff", - "#66ff00","#66ff33","#66ff66","#66ff99","#66ffcc","#66ffff","#99ff00","#99ff33", - "#99ff66","#99ff99","#99ffcc","#99ffff","#ccff00","#ccff33","#ccff66","#ccff99", - "#ccffcc","#ccffff","#ffff00","#ffff33","#ffff66","#ffff99","#ffffcc","#ffffff" -]; - -var named = { - '#F0F8FF':'AliceBlue','#FAEBD7':'AntiqueWhite','#00FFFF':'Aqua','#7FFFD4':'Aquamarine','#F0FFFF':'Azure','#F5F5DC':'Beige', - '#FFE4C4':'Bisque','#000000':'Black','#FFEBCD':'BlanchedAlmond','#0000FF':'Blue','#8A2BE2':'BlueViolet','#A52A2A':'Brown', - '#DEB887':'BurlyWood','#5F9EA0':'CadetBlue','#7FFF00':'Chartreuse','#D2691E':'Chocolate','#FF7F50':'Coral','#6495ED':'CornflowerBlue', - '#FFF8DC':'Cornsilk','#DC143C':'Crimson','#00FFFF':'Cyan','#00008B':'DarkBlue','#008B8B':'DarkCyan','#B8860B':'DarkGoldenRod', - '#A9A9A9':'DarkGray','#A9A9A9':'DarkGrey','#006400':'DarkGreen','#BDB76B':'DarkKhaki','#8B008B':'DarkMagenta','#556B2F':'DarkOliveGreen', - '#FF8C00':'Darkorange','#9932CC':'DarkOrchid','#8B0000':'DarkRed','#E9967A':'DarkSalmon','#8FBC8F':'DarkSeaGreen','#483D8B':'DarkSlateBlue', - '#2F4F4F':'DarkSlateGray','#2F4F4F':'DarkSlateGrey','#00CED1':'DarkTurquoise','#9400D3':'DarkViolet','#FF1493':'DeepPink','#00BFFF':'DeepSkyBlue', - '#696969':'DimGray','#696969':'DimGrey','#1E90FF':'DodgerBlue','#B22222':'FireBrick','#FFFAF0':'FloralWhite','#228B22':'ForestGreen', - '#FF00FF':'Fuchsia','#DCDCDC':'Gainsboro','#F8F8FF':'GhostWhite','#FFD700':'Gold','#DAA520':'GoldenRod','#808080':'Gray','#808080':'Grey', - '#008000':'Green','#ADFF2F':'GreenYellow','#F0FFF0':'HoneyDew','#FF69B4':'HotPink','#CD5C5C':'IndianRed','#4B0082':'Indigo','#FFFFF0':'Ivory', - '#F0E68C':'Khaki','#E6E6FA':'Lavender','#FFF0F5':'LavenderBlush','#7CFC00':'LawnGreen','#FFFACD':'LemonChiffon','#ADD8E6':'LightBlue', - '#F08080':'LightCoral','#E0FFFF':'LightCyan','#FAFAD2':'LightGoldenRodYellow','#D3D3D3':'LightGray','#D3D3D3':'LightGrey','#90EE90':'LightGreen', - '#FFB6C1':'LightPink','#FFA07A':'LightSalmon','#20B2AA':'LightSeaGreen','#87CEFA':'LightSkyBlue','#778899':'LightSlateGray','#778899':'LightSlateGrey', - '#B0C4DE':'LightSteelBlue','#FFFFE0':'LightYellow','#00FF00':'Lime','#32CD32':'LimeGreen','#FAF0E6':'Linen','#FF00FF':'Magenta','#800000':'Maroon', - '#66CDAA':'MediumAquaMarine','#0000CD':'MediumBlue','#BA55D3':'MediumOrchid','#9370D8':'MediumPurple','#3CB371':'MediumSeaGreen','#7B68EE':'MediumSlateBlue', - '#00FA9A':'MediumSpringGreen','#48D1CC':'MediumTurquoise','#C71585':'MediumVioletRed','#191970':'MidnightBlue','#F5FFFA':'MintCream','#FFE4E1':'MistyRose','#FFE4B5':'Moccasin', - '#FFDEAD':'NavajoWhite','#000080':'Navy','#FDF5E6':'OldLace','#808000':'Olive','#6B8E23':'OliveDrab','#FFA500':'Orange','#FF4500':'OrangeRed','#DA70D6':'Orchid', - '#EEE8AA':'PaleGoldenRod','#98FB98':'PaleGreen','#AFEEEE':'PaleTurquoise','#D87093':'PaleVioletRed','#FFEFD5':'PapayaWhip','#FFDAB9':'PeachPuff', - '#CD853F':'Peru','#FFC0CB':'Pink','#DDA0DD':'Plum','#B0E0E6':'PowderBlue','#800080':'Purple','#FF0000':'Red','#BC8F8F':'RosyBrown','#4169E1':'RoyalBlue', - '#8B4513':'SaddleBrown','#FA8072':'Salmon','#F4A460':'SandyBrown','#2E8B57':'SeaGreen','#FFF5EE':'SeaShell','#A0522D':'Sienna','#C0C0C0':'Silver', - '#87CEEB':'SkyBlue','#6A5ACD':'SlateBlue','#708090':'SlateGray','#708090':'SlateGrey','#FFFAFA':'Snow','#00FF7F':'SpringGreen', - '#4682B4':'SteelBlue','#D2B48C':'Tan','#008080':'Teal','#D8BFD8':'Thistle','#FF6347':'Tomato','#40E0D0':'Turquoise','#EE82EE':'Violet', - '#F5DEB3':'Wheat','#FFFFFF':'White','#F5F5F5':'WhiteSmoke','#FFFF00':'Yellow','#9ACD32':'YellowGreen' -}; - -function init() { - var inputColor = convertRGBToHex(tinyMCEPopup.getWindowArg('input_color')); - - tinyMCEPopup.resizeToInnerSize(); - - generatePicker(); - - if (inputColor) { - changeFinalColor(inputColor); - - col = convertHexToRGB(inputColor); - - if (col) - updateLight(col.r, col.g, col.b); - } -} - -function insertAction() { - var color = document.getElementById("color").value, f = tinyMCEPopup.getWindowArg('func'); - - tinyMCEPopup.restoreSelection(); - - if (f) - f(color); - - tinyMCEPopup.close(); -} - -function showColor(color, name) { - if (name) - document.getElementById("colorname").innerHTML = name; - - document.getElementById("preview").style.backgroundColor = color; - document.getElementById("color").value = color.toLowerCase(); -} - -function convertRGBToHex(col) { - var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi"); - - if (!col) - return col; - - var rgb = col.replace(re, "$1,$2,$3").split(','); - if (rgb.length == 3) { - r = parseInt(rgb[0]).toString(16); - g = parseInt(rgb[1]).toString(16); - b = parseInt(rgb[2]).toString(16); - - r = r.length == 1 ? '0' + r : r; - g = g.length == 1 ? '0' + g : g; - b = b.length == 1 ? '0' + b : b; - - return "#" + r + g + b; - } - - return col; -} - -function convertHexToRGB(col) { - if (col.indexOf('#') != -1) { - col = col.replace(new RegExp('[^0-9A-F]', 'gi'), ''); - - r = parseInt(col.substring(0, 2), 16); - g = parseInt(col.substring(2, 4), 16); - b = parseInt(col.substring(4, 6), 16); - - return {r : r, g : g, b : b}; - } - - return null; -} - -function generatePicker() { - var el = document.getElementById('light'), h = '', i; - - for (i = 0; i < detail; i++){ - h += '
    '; - } - - el.innerHTML = h; -} - -function generateWebColors() { - var el = document.getElementById('webcolors'), h = '', i; - - if (el.className == 'generated') - return; - - h += '' - + ''; - - for (i=0; i' - + '' - + ''; - if ((i+1) % 18 == 0) - h += ''; - } - - h += '
    '; - - el.innerHTML = h; - el.className = 'generated'; -} - -function generateNamedColors() { - var el = document.getElementById('namedcolors'), h = '', n, v, i = 0; - - if (el.className == 'generated') - return; - - for (n in named) { - v = named[n]; - h += '' - } - - el.innerHTML = h; - el.className = 'generated'; -} - -function dechex(n) { - return strhex.charAt(Math.floor(n / 16)) + strhex.charAt(n % 16); -} - -function computeColor(e) { - var x, y, partWidth, partDetail, imHeight, r, g, b, coef, i, finalCoef, finalR, finalG, finalB; - - x = e.offsetX ? e.offsetX : (e.target ? e.clientX - e.target.x : 0); - y = e.offsetY ? e.offsetY : (e.target ? e.clientY - e.target.y : 0); - - partWidth = document.getElementById('colors').width / 6; - partDetail = detail / 2; - imHeight = document.getElementById('colors').height; - - r = (x >= 0)*(x < partWidth)*255 + (x >= partWidth)*(x < 2*partWidth)*(2*255 - x * 255 / partWidth) + (x >= 4*partWidth)*(x < 5*partWidth)*(-4*255 + x * 255 / partWidth) + (x >= 5*partWidth)*(x < 6*partWidth)*255; - g = (x >= 0)*(x < partWidth)*(x * 255 / partWidth) + (x >= partWidth)*(x < 3*partWidth)*255 + (x >= 3*partWidth)*(x < 4*partWidth)*(4*255 - x * 255 / partWidth); - b = (x >= 2*partWidth)*(x < 3*partWidth)*(-2*255 + x * 255 / partWidth) + (x >= 3*partWidth)*(x < 5*partWidth)*255 + (x >= 5*partWidth)*(x < 6*partWidth)*(6*255 - x * 255 / partWidth); - - coef = (imHeight - y) / imHeight; - r = 128 + (r - 128) * coef; - g = 128 + (g - 128) * coef; - b = 128 + (b - 128) * coef; - - changeFinalColor('#' + dechex(r) + dechex(g) + dechex(b)); - updateLight(r, g, b); -} - -function updateLight(r, g, b) { - var i, partDetail = detail / 2, finalCoef, finalR, finalG, finalB, color; - - for (i=0; i=0) && (i 1 ? value : '0' + value; // Padd with leading zero + }; + + color = color.replace(/[\s#]+/g, '').toLowerCase(); + color = namedLookup[color] || color; + matches = /^rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)|([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})|([a-f0-9])([a-f0-9])([a-f0-9])$/.exec(color); + + if (matches) { + if (matches[1]) { + red = toInt(matches[1]); + green = toInt(matches[2]); + blue = toInt(matches[3]); + } else if (matches[4]) { + red = toInt(matches[4], 16); + green = toInt(matches[5], 16); + blue = toInt(matches[6], 16); + } else if (matches[7]) { + red = toInt(matches[7] + matches[7], 16); + green = toInt(matches[8] + matches[8], 16); + blue = toInt(matches[9] + matches[9], 16); + } + + return '#' + hex(red) + hex(green) + hex(blue); + } + + return ''; +} + +function insertAction() { + var color = document.getElementById("color").value, f = tinyMCEPopup.getWindowArg('func'); + + tinyMCEPopup.restoreSelection(); + + if (f) + f(toHexColor(color)); + + tinyMCEPopup.close(); +} + +function showColor(color, name) { + if (name) + document.getElementById("colorname").innerHTML = name; + + document.getElementById("preview").style.backgroundColor = color; + document.getElementById("color").value = color.toUpperCase(); +} + +function convertRGBToHex(col) { + var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi"); + + if (!col) + return col; + + var rgb = col.replace(re, "$1,$2,$3").split(','); + if (rgb.length == 3) { + r = parseInt(rgb[0]).toString(16); + g = parseInt(rgb[1]).toString(16); + b = parseInt(rgb[2]).toString(16); + + r = r.length == 1 ? '0' + r : r; + g = g.length == 1 ? '0' + g : g; + b = b.length == 1 ? '0' + b : b; + + return "#" + r + g + b; + } + + return col; +} + +function convertHexToRGB(col) { + if (col.indexOf('#') != -1) { + col = col.replace(new RegExp('[^0-9A-F]', 'gi'), ''); + + r = parseInt(col.substring(0, 2), 16); + g = parseInt(col.substring(2, 4), 16); + b = parseInt(col.substring(4, 6), 16); + + return {r : r, g : g, b : b}; + } + + return null; +} + +function generatePicker() { + var el = document.getElementById('light'), h = '', i; + + for (i = 0; i < detail; i++){ + h += '
    '; + } + + el.innerHTML = h; +} + +function generateWebColors() { + var el = document.getElementById('webcolors'), h = '', i; + + if (el.className == 'generated') + return; + + // TODO: VoiceOver doesn't seem to support legend as a label referenced by labelledby. + h += '
    ' + + ''; + + for (i=0; i' + + ''; + if (tinyMCEPopup.editor.forcedHighContrastMode) { + h += ''; + } + h += ''; + h += ''; + if ((i+1) % 18 == 0) + h += ''; + } + + h += '
    '; + + el.innerHTML = h; + el.className = 'generated'; + + paintCanvas(el); + enableKeyboardNavigation(el.firstChild); +} + +function paintCanvas(el) { + tinyMCEPopup.getWin().tinymce.each(tinyMCEPopup.dom.select('canvas.mceColorSwatch', el), function(canvas) { + var context; + if (canvas.getContext && (context = canvas.getContext("2d"))) { + context.fillStyle = canvas.getAttribute('data-color'); + context.fillRect(0, 0, 10, 10); + } + }); +} +function generateNamedColors() { + var el = document.getElementById('namedcolors'), h = '', n, v, i = 0; + + if (el.className == 'generated') + return; + + for (n in named) { + v = named[n]; + h += ''; + if (tinyMCEPopup.editor.forcedHighContrastMode) { + h += ''; + } + h += ''; + h += ''; + i++; + } + + el.innerHTML = h; + el.className = 'generated'; + + paintCanvas(el); + enableKeyboardNavigation(el); +} + +function enableKeyboardNavigation(el) { + tinyMCEPopup.editor.windowManager.createInstance('tinymce.ui.KeyboardNavigation', { + root: el, + items: tinyMCEPopup.dom.select('a', el) + }, tinyMCEPopup.dom); +} + +function dechex(n) { + return strhex.charAt(Math.floor(n / 16)) + strhex.charAt(n % 16); +} + +function computeColor(e) { + var x, y, partWidth, partDetail, imHeight, r, g, b, coef, i, finalCoef, finalR, finalG, finalB, pos = tinyMCEPopup.dom.getPos(e.target); + + x = e.offsetX ? e.offsetX : (e.target ? e.clientX - pos.x : 0); + y = e.offsetY ? e.offsetY : (e.target ? e.clientY - pos.y : 0); + + partWidth = document.getElementById('colors').width / 6; + partDetail = detail / 2; + imHeight = document.getElementById('colors').height; + + r = (x >= 0)*(x < partWidth)*255 + (x >= partWidth)*(x < 2*partWidth)*(2*255 - x * 255 / partWidth) + (x >= 4*partWidth)*(x < 5*partWidth)*(-4*255 + x * 255 / partWidth) + (x >= 5*partWidth)*(x < 6*partWidth)*255; + g = (x >= 0)*(x < partWidth)*(x * 255 / partWidth) + (x >= partWidth)*(x < 3*partWidth)*255 + (x >= 3*partWidth)*(x < 4*partWidth)*(4*255 - x * 255 / partWidth); + b = (x >= 2*partWidth)*(x < 3*partWidth)*(-2*255 + x * 255 / partWidth) + (x >= 3*partWidth)*(x < 5*partWidth)*255 + (x >= 5*partWidth)*(x < 6*partWidth)*(6*255 - x * 255 / partWidth); + + coef = (imHeight - y) / imHeight; + r = 128 + (r - 128) * coef; + g = 128 + (g - 128) * coef; + b = 128 + (b - 128) * coef; + + changeFinalColor('#' + dechex(r) + dechex(g) + dechex(b)); + updateLight(r, g, b); +} + +function updateLight(r, g, b) { + var i, partDetail = detail / 2, finalCoef, finalR, finalG, finalB, color; + + for (i=0; i=0) && (i'); - }, - - init : function() { - var f = document.forms[0], ed = tinyMCEPopup.editor; - - // Setup browse button - document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); - if (isVisible('srcbrowser')) - document.getElementById('src').style.width = '180px'; - - e = ed.selection.getNode(); - - this.fillFileList('image_list', 'tinyMCEImageList'); - - if (e.nodeName == 'IMG') { - f.src.value = ed.dom.getAttrib(e, 'src'); - f.alt.value = ed.dom.getAttrib(e, 'alt'); - f.border.value = this.getAttrib(e, 'border'); - f.vspace.value = this.getAttrib(e, 'vspace'); - f.hspace.value = this.getAttrib(e, 'hspace'); - f.width.value = ed.dom.getAttrib(e, 'width'); - f.height.value = ed.dom.getAttrib(e, 'height'); - f.insert.value = ed.getLang('update'); - this.styleVal = ed.dom.getAttrib(e, 'style'); - selectByValue(f, 'image_list', f.src.value); - selectByValue(f, 'align', this.getAttrib(e, 'align')); - this.updateStyle(); - } - }, - - fillFileList : function(id, l) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; - - l = window[l]; - - if (l && l.length > 0) { - lst.options[lst.options.length] = new Option('', ''); - - tinymce.each(l, function(o) { - lst.options[lst.options.length] = new Option(o[0], o[1]); - }); - } else - dom.remove(dom.getParent(id, 'tr')); - }, - - update : function() { - var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, args = {}, el; - - tinyMCEPopup.restoreSelection(); - - if (f.src.value === '') { - if (ed.selection.getNode().nodeName == 'IMG') { - ed.dom.remove(ed.selection.getNode()); - ed.execCommand('mceRepaint'); - } - - tinyMCEPopup.close(); - return; - } - - if (!ed.settings.inline_styles) { - args = tinymce.extend(args, { - vspace : nl.vspace.value, - hspace : nl.hspace.value, - border : nl.border.value, - align : getSelectValue(f, 'align') - }); - } else - args.style = this.styleVal; - - tinymce.extend(args, { - src : f.src.value, - alt : f.alt.value, - width : f.width.value, - height : f.height.value - }); - - el = ed.selection.getNode(); - - if (el && el.nodeName == 'IMG') { - ed.dom.setAttribs(el, args); - } else { - ed.execCommand('mceInsertContent', false, '', {skip_undo : 1}); - ed.dom.setAttribs('__mce_tmp', args); - ed.dom.setAttrib('__mce_tmp', 'id', ''); - ed.undoManager.add(); - } - - tinyMCEPopup.close(); - }, - - updateStyle : function() { - var dom = tinyMCEPopup.dom, st, v, f = document.forms[0]; - - if (tinyMCEPopup.editor.settings.inline_styles) { - st = tinyMCEPopup.dom.parseStyle(this.styleVal); - - // Handle align - v = getSelectValue(f, 'align'); - if (v) { - if (v == 'left' || v == 'right') { - st['float'] = v; - delete st['vertical-align']; - } else { - st['vertical-align'] = v; - delete st['float']; - } - } else { - delete st['float']; - delete st['vertical-align']; - } - - // Handle border - v = f.border.value; - if (v || v == '0') { - if (v == '0') - st['border'] = '0'; - else - st['border'] = v + 'px solid black'; - } else - delete st['border']; - - // Handle hspace - v = f.hspace.value; - if (v) { - delete st['margin']; - st['margin-left'] = v + 'px'; - st['margin-right'] = v + 'px'; - } else { - delete st['margin-left']; - delete st['margin-right']; - } - - // Handle vspace - v = f.vspace.value; - if (v) { - delete st['margin']; - st['margin-top'] = v + 'px'; - st['margin-bottom'] = v + 'px'; - } else { - delete st['margin-top']; - delete st['margin-bottom']; - } - - // Merge - st = tinyMCEPopup.dom.parseStyle(dom.serializeStyle(st), 'img'); - this.styleVal = dom.serializeStyle(st, 'img'); - } - }, - - getAttrib : function(e, at) { - var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; - - if (ed.settings.inline_styles) { - switch (at) { - case 'align': - if (v = dom.getStyle(e, 'float')) - return v; - - if (v = dom.getStyle(e, 'vertical-align')) - return v; - - break; - - case 'hspace': - v = dom.getStyle(e, 'margin-left') - v2 = dom.getStyle(e, 'margin-right'); - if (v && v == v2) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - - case 'vspace': - v = dom.getStyle(e, 'margin-top') - v2 = dom.getStyle(e, 'margin-bottom'); - if (v && v == v2) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - - case 'border': - v = 0; - - tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { - sv = dom.getStyle(e, 'border-' + sv + '-width'); - - // False or not the same as prev - if (!sv || (sv != v && v !== 0)) { - v = 0; - return false; - } - - if (sv) - v = sv; - }); - - if (v) - return parseInt(v.replace(/[^0-9]/g, '')); - - break; - } - } - - if (v = dom.getAttrib(e, at)) - return v; - - return ''; - }, - - resetImageData : function() { - var f = document.forms[0]; - - f.width.value = f.height.value = ""; - }, - - updateImageData : function() { - var f = document.forms[0], t = ImageDialog; - - if (f.width.value == "") - f.width.value = t.preloadImg.width; - - if (f.height.value == "") - f.height.value = t.preloadImg.height; - }, - - getImageData : function() { - var f = document.forms[0]; - - this.preloadImg = new Image(); - this.preloadImg.onload = this.updateImageData; - this.preloadImg.onerror = this.resetImageData; - this.preloadImg.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(f.src.value); - } -}; - -ImageDialog.preInit(); -tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); +var ImageDialog = { + preInit : function() { + var url; + + tinyMCEPopup.requireLangPack(); + + if (url = tinyMCEPopup.getParam("external_image_list_url")) + document.write(''); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image'); + if (isVisible('srcbrowser')) + document.getElementById('src').style.width = '180px'; + + e = ed.selection.getNode(); + + this.fillFileList('image_list', tinyMCEPopup.getParam('external_image_list', 'tinyMCEImageList')); + + if (e.nodeName == 'IMG') { + f.src.value = ed.dom.getAttrib(e, 'src'); + f.alt.value = ed.dom.getAttrib(e, 'alt'); + f.border.value = this.getAttrib(e, 'border'); + f.vspace.value = this.getAttrib(e, 'vspace'); + f.hspace.value = this.getAttrib(e, 'hspace'); + f.width.value = ed.dom.getAttrib(e, 'width'); + f.height.value = ed.dom.getAttrib(e, 'height'); + f.insert.value = ed.getLang('update'); + this.styleVal = ed.dom.getAttrib(e, 'style'); + selectByValue(f, 'image_list', f.src.value); + selectByValue(f, 'align', this.getAttrib(e, 'align')); + this.updateStyle(); + } + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = typeof(l) === 'function' ? l() : window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + update : function() { + var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, args = {}, el; + + tinyMCEPopup.restoreSelection(); + + if (f.src.value === '') { + if (ed.selection.getNode().nodeName == 'IMG') { + ed.dom.remove(ed.selection.getNode()); + ed.execCommand('mceRepaint'); + } + + tinyMCEPopup.close(); + return; + } + + if (!ed.settings.inline_styles) { + args = tinymce.extend(args, { + vspace : nl.vspace.value, + hspace : nl.hspace.value, + border : nl.border.value, + align : getSelectValue(f, 'align') + }); + } else + args.style = this.styleVal; + + tinymce.extend(args, { + src : f.src.value.replace(/ /g, '%20'), + alt : f.alt.value, + width : f.width.value, + height : f.height.value + }); + + el = ed.selection.getNode(); + + if (el && el.nodeName == 'IMG') { + ed.dom.setAttribs(el, args); + tinyMCEPopup.editor.execCommand('mceRepaint'); + tinyMCEPopup.editor.focus(); + } else { + tinymce.each(args, function(value, name) { + if (value === "") { + delete args[name]; + } + }); + + ed.execCommand('mceInsertContent', false, tinyMCEPopup.editor.dom.createHTML('img', args), {skip_undo : 1}); + ed.undoManager.add(); + } + + tinyMCEPopup.close(); + }, + + updateStyle : function() { + var dom = tinyMCEPopup.dom, st, v, f = document.forms[0]; + + if (tinyMCEPopup.editor.settings.inline_styles) { + st = tinyMCEPopup.dom.parseStyle(this.styleVal); + + // Handle align + v = getSelectValue(f, 'align'); + if (v) { + if (v == 'left' || v == 'right') { + st['float'] = v; + delete st['vertical-align']; + } else { + st['vertical-align'] = v; + delete st['float']; + } + } else { + delete st['float']; + delete st['vertical-align']; + } + + // Handle border + v = f.border.value; + if (v || v == '0') { + if (v == '0') + st['border'] = '0'; + else + st['border'] = v + 'px solid black'; + } else + delete st['border']; + + // Handle hspace + v = f.hspace.value; + if (v) { + delete st['margin']; + st['margin-left'] = v + 'px'; + st['margin-right'] = v + 'px'; + } else { + delete st['margin-left']; + delete st['margin-right']; + } + + // Handle vspace + v = f.vspace.value; + if (v) { + delete st['margin']; + st['margin-top'] = v + 'px'; + st['margin-bottom'] = v + 'px'; + } else { + delete st['margin-top']; + delete st['margin-bottom']; + } + + // Merge + st = tinyMCEPopup.dom.parseStyle(dom.serializeStyle(st), 'img'); + this.styleVal = dom.serializeStyle(st, 'img'); + } + }, + + getAttrib : function(e, at) { + var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2; + + if (ed.settings.inline_styles) { + switch (at) { + case 'align': + if (v = dom.getStyle(e, 'float')) + return v; + + if (v = dom.getStyle(e, 'vertical-align')) + return v; + + break; + + case 'hspace': + v = dom.getStyle(e, 'margin-left') + v2 = dom.getStyle(e, 'margin-right'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'vspace': + v = dom.getStyle(e, 'margin-top') + v2 = dom.getStyle(e, 'margin-bottom'); + if (v && v == v2) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + + case 'border': + v = 0; + + tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) { + sv = dom.getStyle(e, 'border-' + sv + '-width'); + + // False or not the same as prev + if (!sv || (sv != v && v !== 0)) { + v = 0; + return false; + } + + if (sv) + v = sv; + }); + + if (v) + return parseInt(v.replace(/[^0-9]/g, '')); + + break; + } + } + + if (v = dom.getAttrib(e, at)) + return v; + + return ''; + }, + + resetImageData : function() { + var f = document.forms[0]; + + f.width.value = f.height.value = ""; + }, + + updateImageData : function() { + var f = document.forms[0], t = ImageDialog; + + if (f.width.value == "") + f.width.value = t.preloadImg.width; + + if (f.height.value == "") + f.height.value = t.preloadImg.height; + }, + + getImageData : function() { + var f = document.forms[0]; + + this.preloadImg = new Image(); + this.preloadImg.onload = this.updateImageData; + this.preloadImg.onerror = this.resetImageData; + this.preloadImg.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(f.src.value); + } +}; + +ImageDialog.preInit(); +tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog); diff --git a/js/tiny_mce/themes/advanced/js/link.js b/js/tiny_mce/themes/advanced/js/link.js index f67a5bc8..e67d868a 100644 --- a/js/tiny_mce/themes/advanced/js/link.js +++ b/js/tiny_mce/themes/advanced/js/link.js @@ -1,156 +1,153 @@ -tinyMCEPopup.requireLangPack(); - -var LinkDialog = { - preInit : function() { - var url; - - if (url = tinyMCEPopup.getParam("external_link_list_url")) - document.write(''); - }, - - init : function() { - var f = document.forms[0], ed = tinyMCEPopup.editor; - - // Setup browse button - document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser', 'href', 'file', 'theme_advanced_link'); - if (isVisible('hrefbrowser')) - document.getElementById('href').style.width = '180px'; - - this.fillClassList('class_list'); - this.fillFileList('link_list', 'tinyMCELinkList'); - this.fillTargetList('target_list'); - - if (e = ed.dom.getParent(ed.selection.getNode(), 'A')) { - f.href.value = ed.dom.getAttrib(e, 'href'); - f.linktitle.value = ed.dom.getAttrib(e, 'title'); - f.insert.value = ed.getLang('update'); - selectByValue(f, 'link_list', f.href.value); - selectByValue(f, 'target_list', ed.dom.getAttrib(e, 'target')); - selectByValue(f, 'class_list', ed.dom.getAttrib(e, 'class')); - } - }, - - update : function() { - var f = document.forms[0], ed = tinyMCEPopup.editor, e, b; - - tinyMCEPopup.restoreSelection(); - e = ed.dom.getParent(ed.selection.getNode(), 'A'); - - // Remove element if there is no href - if (!f.href.value) { - if (e) { - tinyMCEPopup.execCommand("mceBeginUndoLevel"); - b = ed.selection.getBookmark(); - ed.dom.remove(e, 1); - ed.selection.moveToBookmark(b); - tinyMCEPopup.execCommand("mceEndUndoLevel"); - tinyMCEPopup.close(); - return; - } - } - - tinyMCEPopup.execCommand("mceBeginUndoLevel"); - - // Create new anchor elements - if (e == null) { - ed.getDoc().execCommand("unlink", false, null); - tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1}); - - tinymce.each(ed.dom.select("a"), function(n) { - if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') { - e = n; - - ed.dom.setAttribs(e, { - href : f.href.value, - title : f.linktitle.value, - target : f.target_list ? getSelectValue(f, "target_list") : null, - 'class' : f.class_list ? getSelectValue(f, "class_list") : null - }); - } - }); - } else { - ed.dom.setAttribs(e, { - href : f.href.value, - title : f.linktitle.value, - target : f.target_list ? getSelectValue(f, "target_list") : null, - 'class' : f.class_list ? getSelectValue(f, "class_list") : null - }); - } - - // Don't move caret if selection was image - if (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') { - ed.focus(); - ed.selection.select(e); - ed.selection.collapse(0); - tinyMCEPopup.storeSelection(); - } - - tinyMCEPopup.execCommand("mceEndUndoLevel"); - tinyMCEPopup.close(); - }, - - checkPrefix : function(n) { - if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_email'))) - n.value = 'mailto:' + n.value; - - if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_external'))) - n.value = 'http://' + n.value; - }, - - fillFileList : function(id, l) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; - - l = window[l]; - - if (l && l.length > 0) { - lst.options[lst.options.length] = new Option('', ''); - - tinymce.each(l, function(o) { - lst.options[lst.options.length] = new Option(o[0], o[1]); - }); - } else - dom.remove(dom.getParent(id, 'tr')); - }, - - fillClassList : function(id) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; - - if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { - cl = []; - - tinymce.each(v.split(';'), function(v) { - var p = v.split('='); - - cl.push({'title' : p[0], 'class' : p[1]}); - }); - } else - cl = tinyMCEPopup.editor.dom.getClasses(); - - if (cl.length > 0) { - lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); - - tinymce.each(cl, function(o) { - lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); - }); - } else - dom.remove(dom.getParent(id, 'tr')); - }, - - fillTargetList : function(id) { - var dom = tinyMCEPopup.dom, lst = dom.get(id), v; - - lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); - lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_same'), '_self'); - lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_blank'), '_blank'); - - if (v = tinyMCEPopup.getParam('theme_advanced_link_targets')) { - tinymce.each(v.split(','), function(v) { - v = v.split('='); - lst.options[lst.options.length] = new Option(v[0], v[1]); - }); - } - } -}; - -LinkDialog.preInit(); -tinyMCEPopup.onInit.add(LinkDialog.init, LinkDialog); +tinyMCEPopup.requireLangPack(); + +var LinkDialog = { + preInit : function() { + var url; + + if (url = tinyMCEPopup.getParam("external_link_list_url")) + document.write(''); + }, + + init : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor; + + // Setup browse button + document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser', 'href', 'file', 'theme_advanced_link'); + if (isVisible('hrefbrowser')) + document.getElementById('href').style.width = '180px'; + + this.fillClassList('class_list'); + this.fillFileList('link_list', 'tinyMCELinkList'); + this.fillTargetList('target_list'); + + if (e = ed.dom.getParent(ed.selection.getNode(), 'A')) { + f.href.value = ed.dom.getAttrib(e, 'href'); + f.linktitle.value = ed.dom.getAttrib(e, 'title'); + f.insert.value = ed.getLang('update'); + selectByValue(f, 'link_list', f.href.value); + selectByValue(f, 'target_list', ed.dom.getAttrib(e, 'target')); + selectByValue(f, 'class_list', ed.dom.getAttrib(e, 'class')); + } + }, + + update : function() { + var f = document.forms[0], ed = tinyMCEPopup.editor, e, b, href = f.href.value.replace(/ /g, '%20'); + + tinyMCEPopup.restoreSelection(); + e = ed.dom.getParent(ed.selection.getNode(), 'A'); + + // Remove element if there is no href + if (!f.href.value) { + if (e) { + b = ed.selection.getBookmark(); + ed.dom.remove(e, 1); + ed.selection.moveToBookmark(b); + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + return; + } + } + + // Create new anchor elements + if (e == null) { + ed.getDoc().execCommand("unlink", false, null); + tinyMCEPopup.execCommand("mceInsertLink", false, "#mce_temp_url#", {skip_undo : 1}); + + tinymce.each(ed.dom.select("a"), function(n) { + if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') { + e = n; + + ed.dom.setAttribs(e, { + href : href, + title : f.linktitle.value, + target : f.target_list ? getSelectValue(f, "target_list") : null, + 'class' : f.class_list ? getSelectValue(f, "class_list") : null + }); + } + }); + } else { + ed.dom.setAttribs(e, { + href : href, + title : f.linktitle.value, + target : f.target_list ? getSelectValue(f, "target_list") : null, + 'class' : f.class_list ? getSelectValue(f, "class_list") : null + }); + } + + // Don't move caret if selection was image + if (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') { + ed.focus(); + ed.selection.select(e); + ed.selection.collapse(0); + tinyMCEPopup.storeSelection(); + } + + tinyMCEPopup.execCommand("mceEndUndoLevel"); + tinyMCEPopup.close(); + }, + + checkPrefix : function(n) { + if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_email'))) + n.value = 'mailto:' + n.value; + + if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_external'))) + n.value = 'http://' + n.value; + }, + + fillFileList : function(id, l) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + l = window[l]; + + if (l && l.length > 0) { + lst.options[lst.options.length] = new Option('', ''); + + tinymce.each(l, function(o) { + lst.options[lst.options.length] = new Option(o[0], o[1]); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillClassList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl; + + if (v = tinyMCEPopup.getParam('theme_advanced_styles')) { + cl = []; + + tinymce.each(v.split(';'), function(v) { + var p = v.split('='); + + cl.push({'title' : p[0], 'class' : p[1]}); + }); + } else + cl = tinyMCEPopup.editor.dom.getClasses(); + + if (cl.length > 0) { + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + + tinymce.each(cl, function(o) { + lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']); + }); + } else + dom.remove(dom.getParent(id, 'tr')); + }, + + fillTargetList : function(id) { + var dom = tinyMCEPopup.dom, lst = dom.get(id), v; + + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), ''); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_same'), '_self'); + lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_blank'), '_blank'); + + if (v = tinyMCEPopup.getParam('theme_advanced_link_targets')) { + tinymce.each(v.split(','), function(v) { + v = v.split('='); + lst.options[lst.options.length] = new Option(v[0], v[1]); + }); + } + } +}; + +LinkDialog.preInit(); +tinyMCEPopup.onInit.add(LinkDialog.init, LinkDialog); diff --git a/js/tiny_mce/themes/advanced/js/source_editor.js b/js/tiny_mce/themes/advanced/js/source_editor.js index 27932861..9cf6b1a2 100644 --- a/js/tiny_mce/themes/advanced/js/source_editor.js +++ b/js/tiny_mce/themes/advanced/js/source_editor.js @@ -1,62 +1,56 @@ -tinyMCEPopup.requireLangPack(); -tinyMCEPopup.onInit.add(onLoadInit); - -function saveContent() { - tinyMCEPopup.editor.setContent(document.getElementById('htmlSource').value, {source_view : true}); - tinyMCEPopup.close(); -} - -function onLoadInit() { - tinyMCEPopup.resizeToInnerSize(); - - // Remove Gecko spellchecking - if (tinymce.isGecko) - document.body.spellcheck = tinyMCEPopup.editor.getParam("gecko_spellcheck"); - - document.getElementById('htmlSource').value = tinyMCEPopup.editor.getContent({source_view : true}); - - if (tinyMCEPopup.editor.getParam("theme_advanced_source_editor_wrap", true)) { - setWrap('soft'); - document.getElementById('wraped').checked = true; - } - - resizeInputs(); -} - -function setWrap(val) { - var v, n, s = document.getElementById('htmlSource'); - - s.wrap = val; - - if (!tinymce.isIE) { - v = s.value; - n = s.cloneNode(false); - n.setAttribute("wrap", val); - s.parentNode.replaceChild(n, s); - n.value = v; - } -} - -function toggleWordWrap(elm) { - if (elm.checked) - setWrap('soft'); - else - setWrap('off'); -} - -var wHeight=0, wWidth=0, owHeight=0, owWidth=0; - -function resizeInputs() { - var el = document.getElementById('htmlSource'); - - if (!tinymce.isIE) { - wHeight = self.innerHeight - 65; - wWidth = self.innerWidth - 16; - } else { - wHeight = document.body.clientHeight - 70; - wWidth = document.body.clientWidth - 16; - } - - el.style.height = Math.abs(wHeight) + 'px'; - el.style.width = Math.abs(wWidth) + 'px'; -} +tinyMCEPopup.requireLangPack(); +tinyMCEPopup.onInit.add(onLoadInit); + +function saveContent() { + tinyMCEPopup.editor.setContent(document.getElementById('htmlSource').value, {source_view : true}); + tinyMCEPopup.close(); +} + +function onLoadInit() { + tinyMCEPopup.resizeToInnerSize(); + + // Remove Gecko spellchecking + if (tinymce.isGecko) + document.body.spellcheck = tinyMCEPopup.editor.getParam("gecko_spellcheck"); + + document.getElementById('htmlSource').value = tinyMCEPopup.editor.getContent({source_view : true}); + + if (tinyMCEPopup.editor.getParam("theme_advanced_source_editor_wrap", true)) { + setWrap('soft'); + document.getElementById('wraped').checked = true; + } + + resizeInputs(); +} + +function setWrap(val) { + var v, n, s = document.getElementById('htmlSource'); + + s.wrap = val; + + if (!tinymce.isIE) { + v = s.value; + n = s.cloneNode(false); + n.setAttribute("wrap", val); + s.parentNode.replaceChild(n, s); + n.value = v; + } +} + +function toggleWordWrap(elm) { + if (elm.checked) + setWrap('soft'); + else + setWrap('off'); +} + +function resizeInputs() { + var vp = tinyMCEPopup.dom.getViewPort(window), el; + + el = document.getElementById('htmlSource'); + + if (el) { + el.style.width = (vp.w - 20) + 'px'; + el.style.height = (vp.h - 65) + 'px'; + } +} diff --git a/js/tiny_mce/themes/advanced/langs/en.js b/js/tiny_mce/themes/advanced/langs/en.js index 69694b1f..6e584818 100644 --- a/js/tiny_mce/themes/advanced/langs/en.js +++ b/js/tiny_mce/themes/advanced/langs/en.js @@ -1,62 +1 @@ -tinyMCE.addI18n('en.advanced',{ -style_select:"Styles", -font_size:"Font size", -fontdefault:"Font family", -block:"Format", -paragraph:"Paragraph", -div:"Div", -address:"Address", -pre:"Preformatted", -h1:"Heading 1", -h2:"Heading 2", -h3:"Heading 3", -h4:"Heading 4", -h5:"Heading 5", -h6:"Heading 6", -blockquote:"Blockquote", -code:"Code", -samp:"Code sample", -dt:"Definition term ", -dd:"Definition description", -bold_desc:"Bold (Ctrl+B)", -italic_desc:"Italic (Ctrl+I)", -underline_desc:"Underline (Ctrl+U)", -striketrough_desc:"Strikethrough", -justifyleft_desc:"Align left", -justifycenter_desc:"Align center", -justifyright_desc:"Align right", -justifyfull_desc:"Align full", -bullist_desc:"Unordered list", -numlist_desc:"Ordered list", -outdent_desc:"Outdent", -indent_desc:"Indent", -undo_desc:"Undo (Ctrl+Z)", -redo_desc:"Redo (Ctrl+Y)", -link_desc:"Insert/edit link", -unlink_desc:"Unlink", -image_desc:"Insert/edit image", -cleanup_desc:"Cleanup messy code", -code_desc:"Edit HTML Source", -sub_desc:"Subscript", -sup_desc:"Superscript", -hr_desc:"Insert horizontal ruler", -removeformat_desc:"Remove formatting", -custom1_desc:"Your custom description here", -forecolor_desc:"Select text color", -backcolor_desc:"Select background color", -charmap_desc:"Insert custom character", -visualaid_desc:"Toggle guidelines/invisible elements", -anchor_desc:"Insert/edit anchor", -cut_desc:"Cut", -copy_desc:"Copy", -paste_desc:"Paste", -image_props_desc:"Image properties", -newdocument_desc:"New document", -help_desc:"Help", -blockquote_desc:"Blockquote", -clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\r\nDo you want more information about this issue?", -path:"Path", -newdocument:"Are you sure you want clear all contents?", -toolbar_focus:"Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X", -more_colors:"More colors" -}); \ No newline at end of file +tinyMCE.addI18n('en.advanced',{"underline_desc":"Underline (Ctrl+U)","italic_desc":"Italic (Ctrl+I)","bold_desc":"Bold (Ctrl+B)",dd:"Definition Description",dt:"Definition Term ",samp:"Code Sample",code:"Code",blockquote:"Block Quote",h6:"Heading 6",h5:"Heading 5",h4:"Heading 4",h3:"Heading 3",h2:"Heading 2",h1:"Heading 1",pre:"Preformatted",address:"Address",div:"DIV",paragraph:"Paragraph",block:"Format",fontdefault:"Font Family","font_size":"Font Size","style_select":"Styles","anchor_delta_height":"","anchor_delta_width":"","charmap_delta_height":"","charmap_delta_width":"","colorpicker_delta_height":"","colorpicker_delta_width":"","link_delta_height":"","link_delta_width":"","image_delta_height":"","image_delta_width":"","more_colors":"More Colors...","toolbar_focus":"Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X",newdocument:"Are you sure you want clear all contents?",path:"Path","clipboard_msg":"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?","blockquote_desc":"Block Quote","help_desc":"Help","newdocument_desc":"New Document","image_props_desc":"Image Properties","paste_desc":"Paste (Ctrl+V)","copy_desc":"Copy (Ctrl+C)","cut_desc":"Cut (Ctrl+X)","anchor_desc":"Insert/Edit Anchor","visualaid_desc":"show/Hide Guidelines/Invisible Elements","charmap_desc":"Insert Special Character","backcolor_desc":"Select Background Color","forecolor_desc":"Select Text Color","custom1_desc":"Your Custom Description Here","removeformat_desc":"Remove Formatting","hr_desc":"Insert Horizontal Line","sup_desc":"Superscript","sub_desc":"Subscript","code_desc":"Edit HTML Source","cleanup_desc":"Cleanup Messy Code","image_desc":"Insert/Edit Image","unlink_desc":"Unlink","link_desc":"Insert/Edit Link","redo_desc":"Redo (Ctrl+Y)","undo_desc":"Undo (Ctrl+Z)","indent_desc":"Increase Indent","outdent_desc":"Decrease Indent","numlist_desc":"Insert/Remove Numbered List","bullist_desc":"Insert/Remove Bulleted List","justifyfull_desc":"Align Full","justifyright_desc":"Align Right","justifycenter_desc":"Align Center","justifyleft_desc":"Align Left","striketrough_desc":"Strikethrough","help_shortcut":"Press ALT-F10 for toolbar. Press ALT-0 for help","rich_text_area":"Rich Text Area","shortcuts_desc":"Accessability Help",toolbar:"Toolbar"}); \ No newline at end of file diff --git a/js/tiny_mce/themes/advanced/langs/en_dlg.js b/js/tiny_mce/themes/advanced/langs/en_dlg.js index 9d124d7d..42c9a13c 100644 --- a/js/tiny_mce/themes/advanced/langs/en_dlg.js +++ b/js/tiny_mce/themes/advanced/langs/en_dlg.js @@ -1,51 +1 @@ -tinyMCE.addI18n('en.advanced_dlg',{ -about_title:"About TinyMCE", -about_general:"About", -about_help:"Help", -about_license:"License", -about_plugins:"Plugins", -about_plugin:"Plugin", -about_author:"Author", -about_version:"Version", -about_loaded:"Loaded plugins", -anchor_title:"Insert/edit anchor", -anchor_name:"Anchor name", -code_title:"HTML Source Editor", -code_wordwrap:"Word wrap", -colorpicker_title:"Select a color", -colorpicker_picker_tab:"Picker", -colorpicker_picker_title:"Color picker", -colorpicker_palette_tab:"Palette", -colorpicker_palette_title:"Palette colors", -colorpicker_named_tab:"Named", -colorpicker_named_title:"Named colors", -colorpicker_color:"Color:", -colorpicker_name:"Name:", -charmap_title:"Select custom character", -image_title:"Insert/edit image", -image_src:"Image URL", -image_alt:"Image description", -image_list:"Image list", -image_border:"Border", -image_dimensions:"Dimensions", -image_vspace:"Vertical space", -image_hspace:"Horizontal space", -image_align:"Alignment", -image_align_baseline:"Baseline", -image_align_top:"Top", -image_align_middle:"Middle", -image_align_bottom:"Bottom", -image_align_texttop:"Text top", -image_align_textbottom:"Text bottom", -image_align_left:"Left", -image_align_right:"Right", -link_title:"Insert/edit link", -link_url:"Link URL", -link_target:"Target", -link_target_same:"Open link in the same window", -link_target_blank:"Open link in a new window", -link_titlefield:"Title", -link_is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?", -link_is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?", -link_list:"Link list" -}); \ No newline at end of file +tinyMCE.addI18n('en.advanced_dlg', {"link_list":"Link List","link_is_external":"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?","link_is_email":"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?","link_titlefield":"Title","link_target_blank":"Open Link in a New Window","link_target_same":"Open Link in the Same Window","link_target":"Target","link_url":"Link URL","link_title":"Insert/Edit Link","image_align_right":"Right","image_align_left":"Left","image_align_textbottom":"Text Bottom","image_align_texttop":"Text Top","image_align_bottom":"Bottom","image_align_middle":"Middle","image_align_top":"Top","image_align_baseline":"Baseline","image_align":"Alignment","image_hspace":"Horizontal Space","image_vspace":"Vertical Space","image_dimensions":"Dimensions","image_alt":"Image Description","image_list":"Image List","image_border":"Border","image_src":"Image URL","image_title":"Insert/Edit Image","charmap_title":"Select Special Character", "charmap_usage":"Use left and right arrows to navigate.","colorpicker_name":"Name:","colorpicker_color":"Color:","colorpicker_named_title":"Named Colors","colorpicker_named_tab":"Named","colorpicker_palette_title":"Palette Colors","colorpicker_palette_tab":"Palette","colorpicker_picker_title":"Color Picker","colorpicker_picker_tab":"Picker","colorpicker_title":"Select a Color","code_wordwrap":"Word Wrap","code_title":"HTML Source Editor","anchor_name":"Anchor Name","anchor_title":"Insert/Edit Anchor","about_loaded":"Loaded Plugins","about_version":"Version","about_author":"Author","about_plugin":"Plugin","about_plugins":"Plugins","about_license":"License","about_help":"Help","about_general":"About","about_title":"About TinyMCE","anchor_invalid":"Please specify a valid anchor name.","accessibility_help":"Accessibility Help","accessibility_usage_title":"General Usage","":""}); diff --git a/js/tiny_mce/themes/advanced/link.htm b/js/tiny_mce/themes/advanced/link.htm index 7565b9ae..4a2459f8 100644 --- a/js/tiny_mce/themes/advanced/link.htm +++ b/js/tiny_mce/themes/advanced/link.htm @@ -1,58 +1,57 @@ - - - - {#advanced_dlg.link_title} - - - - - - - -
    - - -
    -
    - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
     
    -
    -
    - -
    - - -
    -
    - - + + + + {#advanced_dlg.link_title} + + + + + + + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
     
    +
    +
    + +
    + + +
    +
    + + diff --git a/js/tiny_mce/themes/advanced/shortcuts.htm b/js/tiny_mce/themes/advanced/shortcuts.htm new file mode 100644 index 00000000..436091f1 --- /dev/null +++ b/js/tiny_mce/themes/advanced/shortcuts.htm @@ -0,0 +1,47 @@ + + + + {#advanced_dlg.accessibility_help} + + + + +

    {#advanced_dlg.accessibility_usage_title}

    +

    Toolbars

    +

    Press ALT-F10 to move focus to the toolbars. Navigate through the buttons using the arrow keys. + Press enter to activate a button and return focus to the editor. + Press escape to return focus to the editor without performing any actions.

    + +

    Status Bar

    +

    To access the editor status bar, press ALT-F11. Use the left and right arrow keys to navigate between elements in the path. + Press enter or space to select an element. Press escape to return focus to the editor without changing the selection.

    + +

    Context Menu

    +

    Press shift-F10 to activate the context menu. Use the up and down arrow keys to move between menu items. To open sub-menus press the right arrow key. + To close submenus press the left arrow key. Press escape to close the context menu.

    + +

    Keyboard Shortcuts

    + + + + + + + + + + + + + + + + + + + + + +
    KeystrokeFunction
    Control-BBold
    Control-IItalic
    Control-ZUndo
    Control-YRedo
    + + diff --git a/js/tiny_mce/themes/advanced/skins/default/content.css b/js/tiny_mce/themes/advanced/skins/default/content.css index 36f38aba..4d63ca98 100644 --- a/js/tiny_mce/themes/advanced/skins/default/content.css +++ b/js/tiny_mce/themes/advanced/skins/default/content.css @@ -1,35 +1,50 @@ -body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} -body {background:#FFF;} -body.mceForceColors {background:#FFF; color:#000;} -h1 {font-size: 2em} -h2 {font-size: 1.5em} -h3 {font-size: 1.17em} -h4 {font-size: 1em} -h5 {font-size: .83em} -h6 {font-size: .75em} -.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} -a.mceItemAnchor {display:inline-block; width:11px !important; height:11px !important; background:url(img/items.gif) no-repeat 0 0;} -td.mceSelected, th.mceSelected {background-color:#3399ff !important} -img {border:0;} -table {cursor:default} -table td, table th {cursor:text} -ins {border-bottom:1px solid green; text-decoration: none; color:green} -del {color:red; text-decoration:line-through} -cite {border-bottom:1px dashed blue} -acronym {border-bottom:1px dotted #CCC; cursor:help} -abbr {border-bottom:1px dashed #CCC; cursor:help} - -/* IE */ -* html body { -scrollbar-3dlight-color:#F0F0EE; -scrollbar-arrow-color:#676662; -scrollbar-base-color:#F0F0EE; -scrollbar-darkshadow-color:#DDD; -scrollbar-face-color:#E0E0DD; -scrollbar-highlight-color:#F0F0EE; -scrollbar-shadow-color:#F0F0EE; -scrollbar-track-color:#F5F5F5; -} - -img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} -font[face=mceinline] {font-family:inherit !important} +body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} +body {background:#FFF;} +body.mceForceColors {background:#FFF; color:#000;} +body.mceBrowserDefaults {background:transparent; color:inherit; font-size:inherit; font-family:inherit;} +h1 {font-size: 2em} +h2 {font-size: 1.5em} +h3 {font-size: 1.17em} +h4 {font-size: 1em} +h5 {font-size: .83em} +h6 {font-size: .75em} +.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} +a.mceItemAnchor {display:inline-block; -webkit-user-select:all; -webkit-user-modify:read-only; -moz-user-select:all; -moz-user-modify:read-only; width:11px !important; height:11px !important; background:url(img/items.gif) no-repeat center center} +span.mceItemNbsp {background: #DDD} +td.mceSelected, th.mceSelected {background-color:#3399ff !important} +img {border:0;} +table, img, hr, .mceItemAnchor {cursor:default} +table td, table th {cursor:text} +ins {border-bottom:1px solid green; text-decoration: none; color:green} +del {color:red; text-decoration:line-through} +cite {border-bottom:1px dashed blue} +acronym {border-bottom:1px dotted #CCC; cursor:help} +abbr {border-bottom:1px dashed #CCC; cursor:help} + +/* IE */ +* html body { +scrollbar-3dlight-color:#F0F0EE; +scrollbar-arrow-color:#676662; +scrollbar-base-color:#F0F0EE; +scrollbar-darkshadow-color:#DDD; +scrollbar-face-color:#E0E0DD; +scrollbar-highlight-color:#F0F0EE; +scrollbar-shadow-color:#F0F0EE; +scrollbar-track-color:#F5F5F5; +} + +img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} +font[face=mceinline] {font-family:inherit !important} +*[contentEditable]:focus {outline:0} + +.mceItemMedia {border:1px dotted #cc0000; background-position:center; background-repeat:no-repeat; background-color:#ffffcc} +.mceItemShockWave {background-image:url(../../img/shockwave.gif)} +.mceItemFlash {background-image:url(../../img/flash.gif)} +.mceItemQuickTime {background-image:url(../../img/quicktime.gif)} +.mceItemWindowsMedia {background-image:url(../../img/windowsmedia.gif)} +.mceItemRealMedia {background-image:url(../../img/realmedia.gif)} +.mceItemVideo {background-image:url(../../img/video.gif)} +.mceItemAudio {background-image:url(../../img/video.gif)} +.mceItemEmbeddedAudio {background-image:url(../../img/video.gif)} +.mceItemIframe {background-image:url(../../img/iframe.gif)} +.mcePageBreak {display:block;border:0;width:100%;height:12px;border-top:1px dotted #ccc;margin-top:15px;background:#fff url(../../img/pagebreak.gif) no-repeat center top;} diff --git a/js/tiny_mce/themes/advanced/skins/default/img/buttons.png b/js/tiny_mce/themes/advanced/skins/default/img/buttons.png index 7dd58418..1e53560e 100644 Binary files a/js/tiny_mce/themes/advanced/skins/default/img/buttons.png and b/js/tiny_mce/themes/advanced/skins/default/img/buttons.png differ diff --git a/js/tiny_mce/themes/advanced/skins/default/img/items.gif b/js/tiny_mce/themes/advanced/skins/default/img/items.gif index 2eafd795..d2f93671 100644 Binary files a/js/tiny_mce/themes/advanced/skins/default/img/items.gif and b/js/tiny_mce/themes/advanced/skins/default/img/items.gif differ diff --git a/js/tiny_mce/themes/advanced/skins/default/img/tabs.gif b/js/tiny_mce/themes/advanced/skins/default/img/tabs.gif index ce4be635..06812cb4 100644 Binary files a/js/tiny_mce/themes/advanced/skins/default/img/tabs.gif and b/js/tiny_mce/themes/advanced/skins/default/img/tabs.gif differ diff --git a/js/tiny_mce/themes/advanced/skins/default/ui.css b/js/tiny_mce/themes/advanced/skins/default/ui.css index 01095aee..ab2204d9 100644 --- a/js/tiny_mce/themes/advanced/skins/default/ui.css +++ b/js/tiny_mce/themes/advanced/skins/default/ui.css @@ -1,213 +1,214 @@ -/* Reset */ -.defaultSkin table, .defaultSkin tbody, .defaultSkin a, .defaultSkin img, .defaultSkin tr, .defaultSkin div, .defaultSkin td, .defaultSkin iframe, .defaultSkin span, .defaultSkin *, .defaultSkin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left} -.defaultSkin a:hover, .defaultSkin a:link, .defaultSkin a:visited, .defaultSkin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000} -.defaultSkin table td {vertical-align:middle} - -/* Containers */ -.defaultSkin table {direction:ltr; background:#F0F0EE} -.defaultSkin iframe {display:block; background:#FFF} -.defaultSkin .mceToolbar {height:26px} -.defaultSkin .mceLeft {text-align:left} -.defaultSkin .mceRight {text-align:right} - -/* External */ -.defaultSkin .mceExternalToolbar {position:absolute; border:1px solid #CCC; border-bottom:0; display:none;} -.defaultSkin .mceExternalToolbar td.mceToolbar {padding-right:13px;} -.defaultSkin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0} - -/* Layout */ -.defaultSkin table.mceLayout {border:0; border-left:1px solid #CCC; border-right:1px solid #CCC} -.defaultSkin table.mceLayout tr.mceFirst td {border-top:1px solid #CCC} -.defaultSkin table.mceLayout tr.mceLast td {border-bottom:1px solid #CCC} -.defaultSkin table.mceToolbar, .defaultSkin tr.mceFirst .mceToolbar tr td, .defaultSkin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0;} -.defaultSkin td.mceToolbar {padding-top:1px; vertical-align:top} -.defaultSkin .mceIframeContainer {border-top:1px solid #CCC; border-bottom:1px solid #CCC} -.defaultSkin .mceStatusbar {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; display:block; height:20px} -.defaultSkin .mceStatusbar div {float:left; margin:2px} -.defaultSkin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0} -.defaultSkin .mceStatusbar a:hover {text-decoration:underline} -.defaultSkin table.mceToolbar {margin-left:3px} -.defaultSkin span.mceIcon, .defaultSkin img.mceIcon {display:block; width:20px; height:20px} -.defaultSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} -.defaultSkin td.mceCenter {text-align:center;} -.defaultSkin td.mceCenter table {margin:0 auto; text-align:left;} -.defaultSkin td.mceRight table {margin:0 0 0 auto;} - -/* Button */ -.defaultSkin .mceButton {display:block; border:1px solid #F0F0EE; width:20px; height:20px; margin-right:1px} -.defaultSkin a.mceButtonEnabled:hover {border:1px solid #0A246A; background-color:#B2BBD0} -.defaultSkin a.mceButtonActive, .defaultSkin a.mceButtonSelected {border:1px solid #0A246A; background-color:#C2CBE0} -.defaultSkin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} -.defaultSkin .mceButtonLabeled {width:auto} -.defaultSkin .mceButtonLabeled span.mceIcon {float:left} -.defaultSkin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica} -.defaultSkin .mceButtonDisabled .mceButtonLabel {color:#888} - -/* Separator */ -.defaultSkin .mceSeparator {display:block; background:url(../../img/icons.gif) -180px 0; width:2px; height:20px; margin:2px 2px 0 4px} - -/* ListBox */ -.defaultSkin .mceListBox, .defaultSkin .mceListBox a {display:block} -.defaultSkin .mceListBox .mceText {padding-left:4px; width:70px; text-align:left; border:1px solid #CCC; border-right:0; background:#FFF; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} -.defaultSkin .mceListBox .mceOpen {width:9px; height:20px; background:url(../../img/icons.gif) -741px 0; margin-right:2px; border:1px solid #CCC;} -.defaultSkin table.mceListBoxEnabled:hover .mceText, .defaultSkin .mceListBoxHover .mceText, .defaultSkin .mceListBoxSelected .mceText {border:1px solid #A2ABC0; border-right:0; background:#FFF} -.defaultSkin table.mceListBoxEnabled:hover .mceOpen, .defaultSkin .mceListBoxHover .mceOpen, .defaultSkin .mceListBoxSelected .mceOpen {background-color:#FFF; border:1px solid #A2ABC0} -.defaultSkin .mceListBoxDisabled a.mceText {color:gray; background-color:transparent;} -.defaultSkin .mceListBoxMenu {overflow:auto; overflow-x:hidden} -.defaultSkin .mceOldBoxModel .mceListBox .mceText {height:22px} -.defaultSkin .mceOldBoxModel .mceListBox .mceOpen {width:11px; height:22px;} -.defaultSkin select.mceNativeListBox {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:7pt; background:#F0F0EE; border:1px solid gray; margin-right:2px;} - -/* SplitButton */ -.defaultSkin .mceSplitButton {width:32px; height:20px; direction:ltr} -.defaultSkin .mceSplitButton a, .defaultSkin .mceSplitButton span {height:20px; display:block} -.defaultSkin .mceSplitButton a.mceAction {width:20px; border:1px solid #F0F0EE; border-right:0;} -.defaultSkin .mceSplitButton span.mceAction {width:20px; background-image:url(../../img/icons.gif);} -.defaultSkin .mceSplitButton a.mceOpen {width:9px; background:url(../../img/icons.gif) -741px 0; border:1px solid #F0F0EE;} -.defaultSkin .mceSplitButton span.mceOpen {display:none} -.defaultSkin table.mceSplitButtonEnabled:hover a.mceAction, .defaultSkin .mceSplitButtonHover a.mceAction, .defaultSkin .mceSplitButtonSelected a.mceAction {border:1px solid #0A246A; border-right:0; background-color:#B2BBD0} -.defaultSkin table.mceSplitButtonEnabled:hover a.mceOpen, .defaultSkin .mceSplitButtonHover a.mceOpen, .defaultSkin .mceSplitButtonSelected a.mceOpen {background-color:#B2BBD0; border:1px solid #0A246A;} -.defaultSkin .mceSplitButtonDisabled .mceAction, .defaultSkin .mceSplitButtonDisabled a.mceOpen {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} -.defaultSkin .mceSplitButtonActive a.mceAction {border:1px solid #0A246A; background-color:#C2CBE0} -.defaultSkin .mceSplitButtonActive a.mceOpen {border-left:0;} - -/* ColorSplitButton */ -.defaultSkin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray} -.defaultSkin .mceColorSplitMenu td {padding:2px} -.defaultSkin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080} -.defaultSkin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} -.defaultSkin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} -.defaultSkin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2} -.defaultSkin a.mceMoreColors:hover {border:1px solid #0A246A} -.defaultSkin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a} -.defaultSkin .mce_forecolor span.mceAction, .defaultSkin .mce_backcolor span.mceAction {overflow:hidden; height:16px} - -/* Menu */ -.defaultSkin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #D4D0C8} -.defaultSkin .mceNoIcons span.mceIcon {width:0;} -.defaultSkin .mceNoIcons a .mceText {padding-left:10px} -.defaultSkin .mceMenu table {background:#FFF} -.defaultSkin .mceMenu a, .defaultSkin .mceMenu span, .defaultSkin .mceMenu {display:block} -.defaultSkin .mceMenu td {height:20px} -.defaultSkin .mceMenu a {position:relative;padding:3px 0 4px 0} -.defaultSkin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} -.defaultSkin .mceMenu span.mceText, .defaultSkin .mceMenu .mcePreview {font-size:11px} -.defaultSkin .mceMenu pre.mceText {font-family:Monospace} -.defaultSkin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;} -.defaultSkin .mceMenu .mceMenuItemEnabled a:hover, .defaultSkin .mceMenu .mceMenuItemActive {background-color:#dbecf3} -.defaultSkin td.mceMenuItemSeparator {background:#DDD; height:1px} -.defaultSkin .mceMenuItemTitle a {border:0; background:#EEE; border-bottom:1px solid #DDD} -.defaultSkin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px} -.defaultSkin .mceMenuItemDisabled .mceText {color:#888} -.defaultSkin .mceMenuItemSelected .mceIcon {background:url(img/menu_check.gif)} -.defaultSkin .mceNoIcons .mceMenuItemSelected a {background:url(img/menu_arrow.gif) no-repeat -6px center} -.defaultSkin .mceMenu span.mceMenuLine {display:none} -.defaultSkin .mceMenuItemSub a {background:url(img/menu_arrow.gif) no-repeat top right;} - -/* Progress,Resize */ -.defaultSkin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=50)'; filter:alpha(opacity=50); background:#FFF} -.defaultSkin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} - -/* Formats */ -.defaultSkin .mce_formatPreview a {font-size:10px} -.defaultSkin .mce_p span.mceText {} -.defaultSkin .mce_address span.mceText {font-style:italic} -.defaultSkin .mce_pre span.mceText {font-family:monospace} -.defaultSkin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} -.defaultSkin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} -.defaultSkin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} -.defaultSkin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} -.defaultSkin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} -.defaultSkin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} - -/* Theme */ -.defaultSkin span.mce_bold {background-position:0 0} -.defaultSkin span.mce_italic {background-position:-60px 0} -.defaultSkin span.mce_underline {background-position:-140px 0} -.defaultSkin span.mce_strikethrough {background-position:-120px 0} -.defaultSkin span.mce_undo {background-position:-160px 0} -.defaultSkin span.mce_redo {background-position:-100px 0} -.defaultSkin span.mce_cleanup {background-position:-40px 0} -.defaultSkin span.mce_bullist {background-position:-20px 0} -.defaultSkin span.mce_numlist {background-position:-80px 0} -.defaultSkin span.mce_justifyleft {background-position:-460px 0} -.defaultSkin span.mce_justifyright {background-position:-480px 0} -.defaultSkin span.mce_justifycenter {background-position:-420px 0} -.defaultSkin span.mce_justifyfull {background-position:-440px 0} -.defaultSkin span.mce_anchor {background-position:-200px 0} -.defaultSkin span.mce_indent {background-position:-400px 0} -.defaultSkin span.mce_outdent {background-position:-540px 0} -.defaultSkin span.mce_link {background-position:-500px 0} -.defaultSkin span.mce_unlink {background-position:-640px 0} -.defaultSkin span.mce_sub {background-position:-600px 0} -.defaultSkin span.mce_sup {background-position:-620px 0} -.defaultSkin span.mce_removeformat {background-position:-580px 0} -.defaultSkin span.mce_newdocument {background-position:-520px 0} -.defaultSkin span.mce_image {background-position:-380px 0} -.defaultSkin span.mce_help {background-position:-340px 0} -.defaultSkin span.mce_code {background-position:-260px 0} -.defaultSkin span.mce_hr {background-position:-360px 0} -.defaultSkin span.mce_visualaid {background-position:-660px 0} -.defaultSkin span.mce_charmap {background-position:-240px 0} -.defaultSkin span.mce_paste {background-position:-560px 0} -.defaultSkin span.mce_copy {background-position:-700px 0} -.defaultSkin span.mce_cut {background-position:-680px 0} -.defaultSkin span.mce_blockquote {background-position:-220px 0} -.defaultSkin .mce_forecolor span.mceAction {background-position:-720px 0} -.defaultSkin .mce_backcolor span.mceAction {background-position:-760px 0} -.defaultSkin span.mce_forecolorpicker {background-position:-720px 0} -.defaultSkin span.mce_backcolorpicker {background-position:-760px 0} - -/* Plugins */ -.defaultSkin span.mce_advhr {background-position:-0px -20px} -.defaultSkin span.mce_ltr {background-position:-20px -20px} -.defaultSkin span.mce_rtl {background-position:-40px -20px} -.defaultSkin span.mce_emotions {background-position:-60px -20px} -.defaultSkin span.mce_fullpage {background-position:-80px -20px} -.defaultSkin span.mce_fullscreen {background-position:-100px -20px} -.defaultSkin span.mce_iespell {background-position:-120px -20px} -.defaultSkin span.mce_insertdate {background-position:-140px -20px} -.defaultSkin span.mce_inserttime {background-position:-160px -20px} -.defaultSkin span.mce_absolute {background-position:-180px -20px} -.defaultSkin span.mce_backward {background-position:-200px -20px} -.defaultSkin span.mce_forward {background-position:-220px -20px} -.defaultSkin span.mce_insert_layer {background-position:-240px -20px} -.defaultSkin span.mce_insertlayer {background-position:-260px -20px} -.defaultSkin span.mce_movebackward {background-position:-280px -20px} -.defaultSkin span.mce_moveforward {background-position:-300px -20px} -.defaultSkin span.mce_media {background-position:-320px -20px} -.defaultSkin span.mce_nonbreaking {background-position:-340px -20px} -.defaultSkin span.mce_pastetext {background-position:-360px -20px} -.defaultSkin span.mce_pasteword {background-position:-380px -20px} -.defaultSkin span.mce_selectall {background-position:-400px -20px} -.defaultSkin span.mce_preview {background-position:-420px -20px} -.defaultSkin span.mce_print {background-position:-440px -20px} -.defaultSkin span.mce_cancel {background-position:-460px -20px} -.defaultSkin span.mce_save {background-position:-480px -20px} -.defaultSkin span.mce_replace {background-position:-500px -20px} -.defaultSkin span.mce_search {background-position:-520px -20px} -.defaultSkin span.mce_styleprops {background-position:-560px -20px} -.defaultSkin span.mce_table {background-position:-580px -20px} -.defaultSkin span.mce_cell_props {background-position:-600px -20px} -.defaultSkin span.mce_delete_table {background-position:-620px -20px} -.defaultSkin span.mce_delete_col {background-position:-640px -20px} -.defaultSkin span.mce_delete_row {background-position:-660px -20px} -.defaultSkin span.mce_col_after {background-position:-680px -20px} -.defaultSkin span.mce_col_before {background-position:-700px -20px} -.defaultSkin span.mce_row_after {background-position:-720px -20px} -.defaultSkin span.mce_row_before {background-position:-740px -20px} -.defaultSkin span.mce_merge_cells {background-position:-760px -20px} -.defaultSkin span.mce_table_props {background-position:-980px -20px} -.defaultSkin span.mce_row_props {background-position:-780px -20px} -.defaultSkin span.mce_split_cells {background-position:-800px -20px} -.defaultSkin span.mce_template {background-position:-820px -20px} -.defaultSkin span.mce_visualchars {background-position:-840px -20px} -.defaultSkin span.mce_abbr {background-position:-860px -20px} -.defaultSkin span.mce_acronym {background-position:-880px -20px} -.defaultSkin span.mce_attribs {background-position:-900px -20px} -.defaultSkin span.mce_cite {background-position:-920px -20px} -.defaultSkin span.mce_del {background-position:-940px -20px} -.defaultSkin span.mce_ins {background-position:-960px -20px} -.defaultSkin span.mce_pagebreak {background-position:0 -40px} -.defaultSkin span.mce_restoredraft {background-position:-20px -40px} -.defaultSkin .mce_spellchecker span.mceAction {background-position:-540px -20px} +/* Reset */ +.defaultSkin table, .defaultSkin tbody, .defaultSkin a, .defaultSkin img, .defaultSkin tr, .defaultSkin div, .defaultSkin td, .defaultSkin iframe, .defaultSkin span, .defaultSkin *, .defaultSkin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left} +.defaultSkin a:hover, .defaultSkin a:link, .defaultSkin a:visited, .defaultSkin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000} +.defaultSkin table td {vertical-align:middle} + +/* Containers */ +.defaultSkin table {direction:ltr;background:transparent} +.defaultSkin iframe {display:block;} +.defaultSkin .mceToolbar {height:26px} +.defaultSkin .mceLeft {text-align:left} +.defaultSkin .mceRight {text-align:right} + +/* External */ +.defaultSkin .mceExternalToolbar {position:absolute; border:1px solid #CCC; border-bottom:0; display:none;} +.defaultSkin .mceExternalToolbar td.mceToolbar {padding-right:13px;} +.defaultSkin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0} + +/* Layout */ +.defaultSkin table.mceLayout {border:0; border-left:1px solid #CCC; border-right:1px solid #CCC} +.defaultSkin table.mceLayout tr.mceFirst td {border-top:1px solid #CCC} +.defaultSkin table.mceLayout tr.mceLast td {border-bottom:1px solid #CCC} +.defaultSkin table.mceToolbar, .defaultSkin tr.mceFirst .mceToolbar tr td, .defaultSkin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0;} +.defaultSkin td.mceToolbar {background:#F0F0EE; padding-top:1px; vertical-align:top} +.defaultSkin .mceIframeContainer {border-top:1px solid #CCC; border-bottom:1px solid #CCC} +.defaultSkin .mceStatusbar {background:#F0F0EE; font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; display:block; height:20px} +.defaultSkin .mceStatusbar div {float:left; margin:2px} +.defaultSkin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0} +.defaultSkin .mceStatusbar a:hover {text-decoration:underline} +.defaultSkin table.mceToolbar {margin-left:3px} +.defaultSkin span.mceIcon, .defaultSkin img.mceIcon {display:block; width:20px; height:20px} +.defaultSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} +.defaultSkin td.mceCenter {text-align:center;} +.defaultSkin td.mceCenter table {margin:0 auto; text-align:left;} +.defaultSkin td.mceRight table {margin:0 0 0 auto;} + +/* Button */ +.defaultSkin .mceButton {display:block; border:1px solid #F0F0EE; width:20px; height:20px; margin-right:1px} +.defaultSkin a.mceButtonEnabled:hover {border:1px solid #0A246A; background-color:#B2BBD0} +.defaultSkin a.mceButtonActive, .defaultSkin a.mceButtonSelected {border:1px solid #0A246A; background-color:#C2CBE0} +.defaultSkin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.defaultSkin .mceButtonLabeled {width:auto} +.defaultSkin .mceButtonLabeled span.mceIcon {float:left} +.defaultSkin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica} +.defaultSkin .mceButtonDisabled .mceButtonLabel {color:#888} + +/* Separator */ +.defaultSkin .mceSeparator {display:block; background:url(../../img/icons.gif) -180px 0; width:2px; height:20px; margin:2px 2px 0 4px} + +/* ListBox */ +.defaultSkin .mceListBox, .defaultSkin .mceListBox a {display:block} +.defaultSkin .mceListBox .mceText {padding-left:4px; width:70px; text-align:left; border:1px solid #CCC; border-right:0; background:#FFF; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} +.defaultSkin .mceListBox .mceOpen {width:9px; height:20px; background:url(../../img/icons.gif) -741px 0; margin-right:2px; border:1px solid #CCC;} +.defaultSkin table.mceListBoxEnabled:hover .mceText, .defaultSkin .mceListBoxHover .mceText, .defaultSkin .mceListBoxSelected .mceText {border:1px solid #A2ABC0; border-right:0; background:#FFF} +.defaultSkin table.mceListBoxEnabled:hover .mceOpen, .defaultSkin .mceListBoxHover .mceOpen, .defaultSkin .mceListBoxSelected .mceOpen {background-color:#FFF; border:1px solid #A2ABC0} +.defaultSkin .mceListBoxDisabled a.mceText {color:gray; background-color:transparent;} +.defaultSkin .mceListBoxMenu {overflow:auto; overflow-x:hidden} +.defaultSkin .mceOldBoxModel .mceListBox .mceText {height:22px} +.defaultSkin .mceOldBoxModel .mceListBox .mceOpen {width:11px; height:22px;} +.defaultSkin select.mceNativeListBox {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:7pt; background:#F0F0EE; border:1px solid gray; margin-right:2px;} + +/* SplitButton */ +.defaultSkin .mceSplitButton {width:32px; height:20px; direction:ltr} +.defaultSkin .mceSplitButton a, .defaultSkin .mceSplitButton span {height:20px; display:block} +.defaultSkin .mceSplitButton a.mceAction {width:20px; border:1px solid #F0F0EE; border-right:0;} +.defaultSkin .mceSplitButton span.mceAction {width:20px; background-image:url(../../img/icons.gif);} +.defaultSkin .mceSplitButton a.mceOpen {width:9px; background:url(../../img/icons.gif) -741px 0; border:1px solid #F0F0EE;} +.defaultSkin .mceSplitButton span.mceOpen {display:none} +.defaultSkin table.mceSplitButtonEnabled:hover a.mceAction, .defaultSkin .mceSplitButtonHover a.mceAction, .defaultSkin .mceSplitButtonSelected a.mceAction {border:1px solid #0A246A; border-right:0; background-color:#B2BBD0} +.defaultSkin table.mceSplitButtonEnabled:hover a.mceOpen, .defaultSkin .mceSplitButtonHover a.mceOpen, .defaultSkin .mceSplitButtonSelected a.mceOpen {background-color:#B2BBD0; border:1px solid #0A246A;} +.defaultSkin .mceSplitButtonDisabled .mceAction, .defaultSkin .mceSplitButtonDisabled a.mceOpen {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.defaultSkin .mceSplitButtonActive a.mceAction {border:1px solid #0A246A; background-color:#C2CBE0} +.defaultSkin .mceSplitButtonActive a.mceOpen {border-left:0;} + +/* ColorSplitButton */ +.defaultSkin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray} +.defaultSkin .mceColorSplitMenu td {padding:2px} +.defaultSkin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080} +.defaultSkin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} +.defaultSkin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} +.defaultSkin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2} +.defaultSkin a.mceMoreColors:hover {border:1px solid #0A246A} +.defaultSkin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a} +.defaultSkin .mce_forecolor span.mceAction, .defaultSkin .mce_backcolor span.mceAction {overflow:hidden; height:16px} + +/* Menu */ +.defaultSkin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #D4D0C8} +.defaultSkin .mceNoIcons span.mceIcon {width:0;} +.defaultSkin .mceNoIcons a .mceText {padding-left:10px} +.defaultSkin .mceMenu table {background:#FFF} +.defaultSkin .mceMenu a, .defaultSkin .mceMenu span, .defaultSkin .mceMenu {display:block} +.defaultSkin .mceMenu td {height:20px} +.defaultSkin .mceMenu a {position:relative;padding:3px 0 4px 0} +.defaultSkin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} +.defaultSkin .mceMenu span.mceText, .defaultSkin .mceMenu .mcePreview {font-size:11px} +.defaultSkin .mceMenu pre.mceText {font-family:Monospace} +.defaultSkin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;} +.defaultSkin .mceMenu .mceMenuItemEnabled a:hover, .defaultSkin .mceMenu .mceMenuItemActive {background-color:#dbecf3} +.defaultSkin td.mceMenuItemSeparator {background:#DDD; height:1px} +.defaultSkin .mceMenuItemTitle a {border:0; background:#EEE; border-bottom:1px solid #DDD} +.defaultSkin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px} +.defaultSkin .mceMenuItemDisabled .mceText {color:#888} +.defaultSkin .mceMenuItemSelected .mceIcon {background:url(img/menu_check.gif)} +.defaultSkin .mceNoIcons .mceMenuItemSelected a {background:url(img/menu_arrow.gif) no-repeat -6px center} +.defaultSkin .mceMenu span.mceMenuLine {display:none} +.defaultSkin .mceMenuItemSub a {background:url(img/menu_arrow.gif) no-repeat top right;} +.defaultSkin .mceMenuItem td, .defaultSkin .mceMenuItem th {line-height: normal} + +/* Progress,Resize */ +.defaultSkin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=50)'; filter:alpha(opacity=50); background:#FFF} +.defaultSkin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} + +/* Formats */ +.defaultSkin .mce_formatPreview a {font-size:10px} +.defaultSkin .mce_p span.mceText {} +.defaultSkin .mce_address span.mceText {font-style:italic} +.defaultSkin .mce_pre span.mceText {font-family:monospace} +.defaultSkin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} +.defaultSkin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} +.defaultSkin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} +.defaultSkin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} +.defaultSkin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} +.defaultSkin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} + +/* Theme */ +.defaultSkin span.mce_bold {background-position:0 0} +.defaultSkin span.mce_italic {background-position:-60px 0} +.defaultSkin span.mce_underline {background-position:-140px 0} +.defaultSkin span.mce_strikethrough {background-position:-120px 0} +.defaultSkin span.mce_undo {background-position:-160px 0} +.defaultSkin span.mce_redo {background-position:-100px 0} +.defaultSkin span.mce_cleanup {background-position:-40px 0} +.defaultSkin span.mce_bullist {background-position:-20px 0} +.defaultSkin span.mce_numlist {background-position:-80px 0} +.defaultSkin span.mce_justifyleft {background-position:-460px 0} +.defaultSkin span.mce_justifyright {background-position:-480px 0} +.defaultSkin span.mce_justifycenter {background-position:-420px 0} +.defaultSkin span.mce_justifyfull {background-position:-440px 0} +.defaultSkin span.mce_anchor {background-position:-200px 0} +.defaultSkin span.mce_indent {background-position:-400px 0} +.defaultSkin span.mce_outdent {background-position:-540px 0} +.defaultSkin span.mce_link {background-position:-500px 0} +.defaultSkin span.mce_unlink {background-position:-640px 0} +.defaultSkin span.mce_sub {background-position:-600px 0} +.defaultSkin span.mce_sup {background-position:-620px 0} +.defaultSkin span.mce_removeformat {background-position:-580px 0} +.defaultSkin span.mce_newdocument {background-position:-520px 0} +.defaultSkin span.mce_image {background-position:-380px 0} +.defaultSkin span.mce_help {background-position:-340px 0} +.defaultSkin span.mce_code {background-position:-260px 0} +.defaultSkin span.mce_hr {background-position:-360px 0} +.defaultSkin span.mce_visualaid {background-position:-660px 0} +.defaultSkin span.mce_charmap {background-position:-240px 0} +.defaultSkin span.mce_paste {background-position:-560px 0} +.defaultSkin span.mce_copy {background-position:-700px 0} +.defaultSkin span.mce_cut {background-position:-680px 0} +.defaultSkin span.mce_blockquote {background-position:-220px 0} +.defaultSkin .mce_forecolor span.mceAction {background-position:-720px 0} +.defaultSkin .mce_backcolor span.mceAction {background-position:-760px 0} +.defaultSkin span.mce_forecolorpicker {background-position:-720px 0} +.defaultSkin span.mce_backcolorpicker {background-position:-760px 0} + +/* Plugins */ +.defaultSkin span.mce_advhr {background-position:-0px -20px} +.defaultSkin span.mce_ltr {background-position:-20px -20px} +.defaultSkin span.mce_rtl {background-position:-40px -20px} +.defaultSkin span.mce_emotions {background-position:-60px -20px} +.defaultSkin span.mce_fullpage {background-position:-80px -20px} +.defaultSkin span.mce_fullscreen {background-position:-100px -20px} +.defaultSkin span.mce_iespell {background-position:-120px -20px} +.defaultSkin span.mce_insertdate {background-position:-140px -20px} +.defaultSkin span.mce_inserttime {background-position:-160px -20px} +.defaultSkin span.mce_absolute {background-position:-180px -20px} +.defaultSkin span.mce_backward {background-position:-200px -20px} +.defaultSkin span.mce_forward {background-position:-220px -20px} +.defaultSkin span.mce_insert_layer {background-position:-240px -20px} +.defaultSkin span.mce_insertlayer {background-position:-260px -20px} +.defaultSkin span.mce_movebackward {background-position:-280px -20px} +.defaultSkin span.mce_moveforward {background-position:-300px -20px} +.defaultSkin span.mce_media {background-position:-320px -20px} +.defaultSkin span.mce_nonbreaking {background-position:-340px -20px} +.defaultSkin span.mce_pastetext {background-position:-360px -20px} +.defaultSkin span.mce_pasteword {background-position:-380px -20px} +.defaultSkin span.mce_selectall {background-position:-400px -20px} +.defaultSkin span.mce_preview {background-position:-420px -20px} +.defaultSkin span.mce_print {background-position:-440px -20px} +.defaultSkin span.mce_cancel {background-position:-460px -20px} +.defaultSkin span.mce_save {background-position:-480px -20px} +.defaultSkin span.mce_replace {background-position:-500px -20px} +.defaultSkin span.mce_search {background-position:-520px -20px} +.defaultSkin span.mce_styleprops {background-position:-560px -20px} +.defaultSkin span.mce_table {background-position:-580px -20px} +.defaultSkin span.mce_cell_props {background-position:-600px -20px} +.defaultSkin span.mce_delete_table {background-position:-620px -20px} +.defaultSkin span.mce_delete_col {background-position:-640px -20px} +.defaultSkin span.mce_delete_row {background-position:-660px -20px} +.defaultSkin span.mce_col_after {background-position:-680px -20px} +.defaultSkin span.mce_col_before {background-position:-700px -20px} +.defaultSkin span.mce_row_after {background-position:-720px -20px} +.defaultSkin span.mce_row_before {background-position:-740px -20px} +.defaultSkin span.mce_merge_cells {background-position:-760px -20px} +.defaultSkin span.mce_table_props {background-position:-980px -20px} +.defaultSkin span.mce_row_props {background-position:-780px -20px} +.defaultSkin span.mce_split_cells {background-position:-800px -20px} +.defaultSkin span.mce_template {background-position:-820px -20px} +.defaultSkin span.mce_visualchars {background-position:-840px -20px} +.defaultSkin span.mce_abbr {background-position:-860px -20px} +.defaultSkin span.mce_acronym {background-position:-880px -20px} +.defaultSkin span.mce_attribs {background-position:-900px -20px} +.defaultSkin span.mce_cite {background-position:-920px -20px} +.defaultSkin span.mce_del {background-position:-940px -20px} +.defaultSkin span.mce_ins {background-position:-960px -20px} +.defaultSkin span.mce_pagebreak {background-position:0 -40px} +.defaultSkin span.mce_restoredraft {background-position:-20px -40px} +.defaultSkin span.mce_spellchecker {background-position:-540px -20px} diff --git a/js/tiny_mce/themes/advanced/skins/highcontrast/content.css b/js/tiny_mce/themes/advanced/skins/highcontrast/content.css new file mode 100644 index 00000000..ee3d369d --- /dev/null +++ b/js/tiny_mce/themes/advanced/skins/highcontrast/content.css @@ -0,0 +1,24 @@ +body, td, pre { margin:8px;} +body.mceForceColors {background:#FFF; color:#000;} +h1 {font-size: 2em} +h2 {font-size: 1.5em} +h3 {font-size: 1.17em} +h4 {font-size: 1em} +h5 {font-size: .83em} +h6 {font-size: .75em} +.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} +a.mceItemAnchor {display:inline-block; width:11px !important; height:11px !important; background:url(../default/img/items.gif) no-repeat 0 0;} +span.mceItemNbsp {background: #DDD} +td.mceSelected, th.mceSelected {background-color:#3399ff !important} +img {border:0;} +table, img, hr, .mceItemAnchor {cursor:default} +table td, table th {cursor:text} +ins {border-bottom:1px solid green; text-decoration: none; color:green} +del {color:red; text-decoration:line-through} +cite {border-bottom:1px dashed blue} +acronym {border-bottom:1px dotted #CCC; cursor:help} +abbr {border-bottom:1px dashed #CCC; cursor:help} + +img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} +font[face=mceinline] {font-family:inherit !important} +*[contentEditable]:focus {outline:0} diff --git a/js/tiny_mce/themes/advanced/skins/highcontrast/dialog.css b/js/tiny_mce/themes/advanced/skins/highcontrast/dialog.css new file mode 100644 index 00000000..dafcd280 --- /dev/null +++ b/js/tiny_mce/themes/advanced/skins/highcontrast/dialog.css @@ -0,0 +1,105 @@ +/* Generic */ +body { +font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px; +background:#F0F0EE; +color: black; +padding:0; +margin:8px 8px 0 8px; +} + +html {background:#F0F0EE; color:#000;} +td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +textarea {resize:none;outline:none;} +a:link, a:visited {color:black;background-color:transparent;} +a:hover {color:#2B6FB6;background-color:transparent;} +.nowrap {white-space: nowrap} + +/* Forms */ +fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;} +legend {color:#2B6FB6; font-weight:bold;} +label.msg {display:none;} +label.invalid {color:#EE0000; display:inline;background-color:transparent;} +input.invalid {border:1px solid #EE0000;background-color:transparent;} +input {background:#FFF; border:1px solid #CCC;color:black;} +input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +input, select, textarea {border:1px solid #808080;} +input.radio {border:1px none #000000; background:transparent; vertical-align:middle;} +input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;} +.input_noborder {border:0;} + +/* Buttons */ +#insert, #cancel, input.button, .updateButton { +font-weight:bold; +width:94px; height:23px; +cursor:pointer; +padding-bottom:2px; +float:left; +} + +#cancel {float:right} + +/* Browse */ +a.pickcolor, a.browse {text-decoration:none} +a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;} +.mceOldBoxModel a.browse span {width:22px; height:20px;} +a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;} +a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +a.browse:hover span.disabled {border:1px solid white; background-color:transparent;} +a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;} +.mceOldBoxModel a.pickcolor span {width:21px; height:17px;} +a.pickcolor:hover span {background-color:#B2BBD0;} +a.pickcolor:hover span.disabled {} + +/* Charmap */ +table.charmap {border:1px solid #AAA; text-align:center} +td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;} +#charmap a {display:block; color:#000; text-decoration:none; border:0} +#charmap a:hover {background:#CCC;color:#2B6FB6} +#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center} +#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center} + +/* Source */ +.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;} +.mceActionPanel {margin-top:5px;} + +/* Tabs classes */ +.tabs {width:100%; height:18px; line-height:normal;} +.tabs ul {margin:0; padding:0; list-style:none;} +.tabs li {float:left; border: 1px solid black; border-bottom:0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block; cursor:pointer;} +.tabs li.current {font-weight: bold; margin-right:2px;} +.tabs span {float:left; display:block; padding:0px 10px 0 0;} +.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;} +.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;} + +/* Panels */ +.panel_wrapper div.panel {display:none;} +.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;} +.panel_wrapper {border:1px solid #919B9C; padding:10px; padding-top:5px; clear:both; background:white;} + +/* Columns */ +.column {float:left;} +.properties {width:100%;} +.properties .column1 {} +.properties .column2 {text-align:left;} + +/* Titles */ +h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;} +h3 {font-size:14px;} +.title {font-size:12px; font-weight:bold; color:#2B6FB6;} + +/* Dialog specific */ +#link .panel_wrapper, #link div.current {height:125px;} +#image .panel_wrapper, #image div.current {height:200px;} +#plugintable thead {font-weight:bold; background:#DDD;} +#plugintable, #about #plugintable td {border:1px solid #919B9C;} +#plugintable {width:96%; margin-top:10px;} +#pluginscontainer {height:290px; overflow:auto;} +#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;} +#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;} +#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;} +#colorpicker #light div {overflow:hidden;} +#colorpicker #previewblock {float:right; padding-left:10px; height:20px;} +#colorpicker .panel_wrapper div.current {height:175px;} +#colorpicker #namedcolors {width:150px;} +#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;} +#colorpicker #colornamecontainer {margin-top:5px;} diff --git a/js/tiny_mce/themes/advanced/skins/highcontrast/ui.css b/js/tiny_mce/themes/advanced/skins/highcontrast/ui.css new file mode 100644 index 00000000..81da151f --- /dev/null +++ b/js/tiny_mce/themes/advanced/skins/highcontrast/ui.css @@ -0,0 +1,102 @@ +/* Reset */ +.highcontrastSkin table, .highcontrastSkin tbody, .highcontrastSkin a, .highcontrastSkin img, .highcontrastSkin tr, .highcontrastSkin div, .highcontrastSkin td, .highcontrastSkin iframe, .highcontrastSkin span, .highcontrastSkin *, .highcontrastSkin .mceText {border:0; margin:0; padding:0; vertical-align:baseline; border-collapse:separate;} +.highcontrastSkin a:hover, .highcontrastSkin a:link, .highcontrastSkin a:visited, .highcontrastSkin a:active {text-decoration:none; font-weight:normal; cursor:default;} +.highcontrastSkin table td {vertical-align:middle} + +.highcontrastSkin .mceIconOnly {display: block !important;} + +/* External */ +.highcontrastSkin .mceExternalToolbar {position:absolute; border:1px solid; border-bottom:0; display:none; background-color: white;} +.highcontrastSkin .mceExternalToolbar td.mceToolbar {padding-right:13px;} +.highcontrastSkin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px;} + +/* Layout */ +.highcontrastSkin table.mceLayout {border: 1px solid;} +.highcontrastSkin .mceIframeContainer {border-top:1px solid; border-bottom:1px solid} +.highcontrastSkin .mceStatusbar a:hover {text-decoration:underline} +.highcontrastSkin .mceStatusbar {display:block; line-height:1.5em; overflow:visible;} +.highcontrastSkin .mceStatusbar div {float:left} +.highcontrastSkin .mceStatusbar a.mceResize {display:block; float:right; width:20px; height:20px; cursor:se-resize; outline:0} + +.highcontrastSkin .mceToolbar td { display: inline-block; float: left;} +.highcontrastSkin .mceToolbar tr { display: block;} +.highcontrastSkin .mceToolbar table { display: block; } + +/* Button */ + +.highcontrastSkin .mceButton { display:block; margin: 2px; padding: 5px 10px;border: 1px solid; border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; -ms-border-radius: 3px; height: 2em;} +.highcontrastSkin .mceButton .mceVoiceLabel { height: 100%; vertical-align: center; line-height: 2em} +.highcontrastSkin .mceButtonDisabled .mceVoiceLabel { opacity:0.6; -ms-filter:'alpha(opacity=60)'; filter:alpha(opacity=60);} +.highcontrastSkin .mceButtonActive, .highcontrastSkin .mceButton:focus, .highcontrastSkin .mceButton:active { border: 5px solid; padding: 1px 6px;-webkit-focus-ring-color:none;outline:none;} + +/* Separator */ +.highcontrastSkin .mceSeparator {display:block; width:16px; height:26px;} + +/* ListBox */ +.highcontrastSkin .mceListBox { display: block; margin:2px;-webkit-focus-ring-color:none;outline:none;} +.highcontrastSkin .mceListBox .mceText {padding: 5px 6px; line-height: 2em; width: 15ex; overflow: hidden;} +.highcontrastSkin .mceListBoxDisabled .mceText { opacity:0.6; -ms-filter:'alpha(opacity=60)'; filter:alpha(opacity=60);} +.highcontrastSkin .mceListBox a.mceText { padding: 5px 10px; display: block; height: 2em; line-height: 2em; border: 1px solid; border-right: 0; border-radius: 3px 0px 0px 3px; -moz-border-radius: 3px 0px 0px 3px; -webkit-border-radius: 3px 0px 0px 3px; -ms-border-radius: 3px 0px 0px 3px;} +.highcontrastSkin .mceListBox a.mceOpen { padding: 5px 4px; display: block; height: 2em; line-height: 2em; border: 1px solid; border-left: 0; border-radius: 0px 3px 3px 0px; -moz-border-radius: 0px 3px 3px 0px; -webkit-border-radius: 0px 3px 3px 0px; -ms-border-radius: 0px 3px 3px 0px;} +.highcontrastSkin .mceListBox:focus a.mceText, .highcontrastSkin .mceListBox:active a.mceText { border-width: 5px; padding: 1px 10px 1px 6px;} +.highcontrastSkin .mceListBox:focus a.mceOpen, .highcontrastSkin .mceListBox:active a.mceOpen { border-width: 5px; padding: 1px 0px 1px 4px;} + +.highcontrastSkin .mceListBoxMenu {overflow-y:auto} + +/* SplitButton */ +.highcontrastSkin .mceSplitButtonDisabled .mceAction {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} + +.highcontrastSkin .mceSplitButton { border-collapse: collapse; margin: 2px; height: 2em; line-height: 2em;-webkit-focus-ring-color:none;outline:none;} +.highcontrastSkin .mceSplitButton td { display: table-cell; float: none; margin: 0; padding: 0; height: 2em;} +.highcontrastSkin .mceSplitButton tr { display: table-row; } +.highcontrastSkin table.mceSplitButton { display: table; } +.highcontrastSkin .mceSplitButton a.mceAction { padding: 5px 10px; display: block; height: 2em; line-height: 2em; overflow: hidden; border: 1px solid; border-right: 0; border-radius: 3px 0px 0px 3px; -moz-border-radius: 3px 0px 0px 3px; -webkit-border-radius: 3px 0px 0px 3px; -ms-border-radius: 3px 0px 0px 3px;} +.highcontrastSkin .mceSplitButton a.mceOpen { padding: 5px 4px; display: block; height: 2em; line-height: 2em; border: 1px solid; border-radius: 0px 3px 3px 0px; -moz-border-radius: 0px 3px 3px 0px; -webkit-border-radius: 0px 3px 3px 0px; -ms-border-radius: 0px 3px 3px 0px;} +.highcontrastSkin .mceSplitButton .mceVoiceLabel { height: 2em; vertical-align: center; line-height: 2em; } +.highcontrastSkin .mceSplitButton:focus a.mceAction, .highcontrastSkin .mceSplitButton:active a.mceAction { border-width: 5px; border-right-width: 1px; padding: 1px 10px 1px 6px;-webkit-focus-ring-color:none;outline:none;} +.highcontrastSkin .mceSplitButton:focus a.mceOpen, .highcontrastSkin .mceSplitButton:active a.mceOpen { border-width: 5px; border-left-width: 1px; padding: 1px 0px 1px 4px;-webkit-focus-ring-color:none;outline:none;} + +/* Menu */ +.highcontrastSkin .mceNoIcons span.mceIcon {width:0;} +.highcontrastSkin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid; } +.highcontrastSkin .mceMenu table {background:white; color: black} +.highcontrastSkin .mceNoIcons a .mceText {padding-left:10px} +.highcontrastSkin .mceMenu a, .highcontrastSkin .mceMenu span, .highcontrastSkin .mceMenu {display:block;background:white; color: black} +.highcontrastSkin .mceMenu td {height:2em} +.highcontrastSkin .mceMenu a {position:relative;padding:3px 0 4px 0; display: block;} +.highcontrastSkin .mceMenu .mceText {position:relative; display:block; cursor:default; margin:0; padding:0 25px 0 25px;} +.highcontrastSkin .mceMenu pre.mceText {font-family:Monospace} +.highcontrastSkin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:26px;} +.highcontrastSkin td.mceMenuItemSeparator {border-top:1px solid; height:1px} +.highcontrastSkin .mceMenuItemTitle a {border:0; border-bottom:1px solid} +.highcontrastSkin .mceMenuItemTitle span.mceText {font-weight:bold; padding-left:4px} +.highcontrastSkin .mceNoIcons .mceMenuItemSelected span.mceText:before {content: "\2713\A0";} +.highcontrastSkin .mceMenu span.mceMenuLine {display:none} +.highcontrastSkin .mceMenuItemSub a .mceText:after {content: "\A0\25B8"} +.highcontrastSkin .mceMenuItem td, .highcontrastSkin .mceMenuItem th {line-height: normal} + +/* ColorSplitButton */ +.highcontrastSkin div.mceColorSplitMenu table {background:#FFF; border:1px solid; color: #000} +.highcontrastSkin .mceColorSplitMenu td {padding:2px} +.highcontrastSkin .mceColorSplitMenu a {display:block; width:16px; height:16px; overflow:hidden; color:#000; margin: 0; padding: 0;} +.highcontrastSkin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} +.highcontrastSkin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} +.highcontrastSkin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid; background-color:#B6BDD2} +.highcontrastSkin a.mceMoreColors:hover {border:1px solid #0A246A; color: #000;} +.highcontrastSkin .mceColorPreview {display:none;} +.highcontrastSkin .mce_forecolor span.mceAction, .highcontrastSkin .mce_backcolor span.mceAction {height:17px;overflow:hidden} + +/* Progress,Resize */ +.highcontrastSkin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=50); background:#FFF} +.highcontrastSkin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(../default/img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} + +/* Formats */ +.highcontrastSkin .mce_p span.mceText {} +.highcontrastSkin .mce_address span.mceText {font-style:italic} +.highcontrastSkin .mce_pre span.mceText {font-family:monospace} +.highcontrastSkin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} +.highcontrastSkin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} +.highcontrastSkin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} +.highcontrastSkin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} +.highcontrastSkin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} +.highcontrastSkin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/content.css b/js/tiny_mce/themes/advanced/skins/o2k7/content.css index 3cea5ff1..631fa0ec 100644 --- a/js/tiny_mce/themes/advanced/skins/o2k7/content.css +++ b/js/tiny_mce/themes/advanced/skins/o2k7/content.css @@ -1,35 +1,48 @@ -body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} -body {background:#FFF;} -body.mceForceColors {background:#FFF; color:#000;} -h1 {font-size: 2em} -h2 {font-size: 1.5em} -h3 {font-size: 1.17em} -h4 {font-size: 1em} -h5 {font-size: .83em} -h6 {font-size: .75em} -.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} -a.mceItemAnchor {display:inline-block; width:11px !important; height:11px !important; background:url(../default/img/items.gif) no-repeat 0 0;} -td.mceSelected, th.mceSelected {background-color:#3399ff !important} -img {border:0;} -table {cursor:default} -table td, table th {cursor:text} -ins {border-bottom:1px solid green; text-decoration: none; color:green} -del {color:red; text-decoration:line-through} -cite {border-bottom:1px dashed blue} -acronym {border-bottom:1px dotted #CCC; cursor:help} -abbr {border-bottom:1px dashed #CCC; cursor:help} - -/* IE */ -* html body { -scrollbar-3dlight-color:#F0F0EE; -scrollbar-arrow-color:#676662; -scrollbar-base-color:#F0F0EE; -scrollbar-darkshadow-color:#DDD; -scrollbar-face-color:#E0E0DD; -scrollbar-highlight-color:#F0F0EE; -scrollbar-shadow-color:#F0F0EE; -scrollbar-track-color:#F5F5F5; -} - -img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} -font[face=mceinline] {font-family:inherit !important} +body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;} +body {background:#FFF;} +body.mceForceColors {background:#FFF; color:#000;} +h1 {font-size: 2em} +h2 {font-size: 1.5em} +h3 {font-size: 1.17em} +h4 {font-size: 1em} +h5 {font-size: .83em} +h6 {font-size: .75em} +.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;} +a.mceItemAnchor {display:inline-block; width:11px !important; height:11px !important; background:url(../default/img/items.gif) no-repeat 0 0;} +span.mceItemNbsp {background: #DDD} +td.mceSelected, th.mceSelected {background-color:#3399ff !important} +img {border:0;} +table, img, hr, .mceItemAnchor {cursor:default} +table td, table th {cursor:text} +ins {border-bottom:1px solid green; text-decoration: none; color:green} +del {color:red; text-decoration:line-through} +cite {border-bottom:1px dashed blue} +acronym {border-bottom:1px dotted #CCC; cursor:help} +abbr {border-bottom:1px dashed #CCC; cursor:help} + +/* IE */ +* html body { +scrollbar-3dlight-color:#F0F0EE; +scrollbar-arrow-color:#676662; +scrollbar-base-color:#F0F0EE; +scrollbar-darkshadow-color:#DDD; +scrollbar-face-color:#E0E0DD; +scrollbar-highlight-color:#F0F0EE; +scrollbar-shadow-color:#F0F0EE; +scrollbar-track-color:#F5F5F5; +} + +img:-moz-broken {-moz-force-broken-image-icon:1; width:24px; height:24px} +font[face=mceinline] {font-family:inherit !important} +*[contentEditable]:focus {outline:0} + +.mceItemMedia {border:1px dotted #cc0000; background-position:center; background-repeat:no-repeat; background-color:#ffffcc} +.mceItemShockWave {background-image:url(../../img/shockwave.gif)} +.mceItemFlash {background-image:url(../../img/flash.gif)} +.mceItemQuickTime {background-image:url(../../img/quicktime.gif)} +.mceItemWindowsMedia {background-image:url(../../img/windowsmedia.gif)} +.mceItemRealMedia {background-image:url(../../img/realmedia.gif)} +.mceItemVideo {background-image:url(../../img/video.gif)} +.mceItemAudio {background-image:url(../../img/video.gif)} +.mceItemIframe {background-image:url(../../img/iframe.gif)} +.mcePageBreak {display:block;border:0;width:100%;height:12px;border-top:1px dotted #ccc;margin-top:15px;background:#fff url(../../img/pagebreak.gif) no-repeat center top;} diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/dialog.css b/js/tiny_mce/themes/advanced/skins/o2k7/dialog.css index e3af1396..c97d38e8 100644 --- a/js/tiny_mce/themes/advanced/skins/o2k7/dialog.css +++ b/js/tiny_mce/themes/advanced/skins/o2k7/dialog.css @@ -1,116 +1,117 @@ -/* Generic */ -body { -font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px; -scrollbar-3dlight-color:#F0F0EE; -scrollbar-arrow-color:#676662; -scrollbar-base-color:#F0F0EE; -scrollbar-darkshadow-color:#DDDDDD; -scrollbar-face-color:#E0E0DD; -scrollbar-highlight-color:#F0F0EE; -scrollbar-shadow-color:#F0F0EE; -scrollbar-track-color:#F5F5F5; -background:#F0F0EE; -padding:0; -margin:8px 8px 0 8px; -} - -html {background:#F0F0EE;} -td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} -textarea {resize:none;outline:none;} -a:link, a:visited {color:black;} -a:hover {color:#2B6FB6;} -.nowrap {white-space: nowrap} - -/* Forms */ -fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;} -legend {color:#2B6FB6; font-weight:bold;} -label.msg {display:none;} -label.invalid {color:#EE0000; display:inline;} -input.invalid {border:1px solid #EE0000;} -input {background:#FFF; border:1px solid #CCC;} -input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} -input, select, textarea {border:1px solid #808080;} -input.radio {border:1px none #000000; background:transparent; vertical-align:middle;} -input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;} -.input_noborder {border:0;} - -/* Buttons */ -#insert, #cancel, input.button, .updateButton { -border:0; margin:0; padding:0; -font-weight:bold; -width:94px; height:26px; -background:url(../default/img/buttons.png) 0 -26px; -cursor:pointer; -padding-bottom:2px; -float:left; -} - -#insert {background:url(../default/img/buttons.png) 0 -52px} -#cancel {background:url(../default/img/buttons.png) 0 0; float:right} - -/* Browse */ -a.pickcolor, a.browse {text-decoration:none} -a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;} -.mceOldBoxModel a.browse span {width:22px; height:20px;} -a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;} -a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} -a.browse:hover span.disabled {border:1px solid white; background-color:transparent;} -a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;} -.mceOldBoxModel a.pickcolor span {width:21px; height:17px;} -a.pickcolor:hover span {background-color:#B2BBD0;} -a.pickcolor:hover span.disabled {} - -/* Charmap */ -table.charmap {border:1px solid #AAA; text-align:center} -td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;} -#charmap a {display:block; color:#000; text-decoration:none; border:0} -#charmap a:hover {background:#CCC;color:#2B6FB6} -#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center} -#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center} - -/* Source */ -.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;} -.mceActionPanel {margin-top:5px;} - -/* Tabs classes */ -.tabs {width:100%; height:18px; line-height:normal; background:url(../default/img/tabs.gif) repeat-x 0 -72px;} -.tabs ul {margin:0; padding:0; list-style:none;} -.tabs li {float:left; background:url(../default/img/tabs.gif) no-repeat 0 0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block;} -.tabs li.current {background:url(../default/img/tabs.gif) no-repeat 0 -18px; margin-right:2px;} -.tabs span {float:left; display:block; background:url(../default/img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;} -.tabs .current span {background:url(../default/img/tabs.gif) no-repeat right -54px;} -.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;} -.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;} - -/* Panels */ -.panel_wrapper div.panel {display:none;} -.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;} -.panel_wrapper {border:1px solid #919B9C; border-top:0px; padding:10px; padding-top:5px; clear:both; background:white;} - -/* Columns */ -.column {float:left;} -.properties {width:100%;} -.properties .column1 {} -.properties .column2 {text-align:left;} - -/* Titles */ -h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;} -h3 {font-size:14px;} -.title {font-size:12px; font-weight:bold; color:#2B6FB6;} - -/* Dialog specific */ -#link .panel_wrapper, #link div.current {height:125px;} -#image .panel_wrapper, #image div.current {height:200px;} -#plugintable thead {font-weight:bold; background:#DDD;} -#plugintable, #about #plugintable td {border:1px solid #919B9C;} -#plugintable {width:96%; margin-top:10px;} -#pluginscontainer {height:290px; overflow:auto;} -#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;} -#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;} -#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;} -#colorpicker #light div {overflow:hidden;} -#colorpicker #previewblock {float:right; padding-left:10px; height:20px;} -#colorpicker .panel_wrapper div.current {height:175px;} -#colorpicker #namedcolors {width:150px;} -#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;} -#colorpicker #colornamecontainer {margin-top:5px;} +/* Generic */ +body { +font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px; +scrollbar-3dlight-color:#F0F0EE; +scrollbar-arrow-color:#676662; +scrollbar-base-color:#F0F0EE; +scrollbar-darkshadow-color:#DDDDDD; +scrollbar-face-color:#E0E0DD; +scrollbar-highlight-color:#F0F0EE; +scrollbar-shadow-color:#F0F0EE; +scrollbar-track-color:#F5F5F5; +background:#F0F0EE; +padding:0; +margin:8px 8px 0 8px; +} + +html {background:#F0F0EE;} +td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +textarea {resize:none;outline:none;} +a:link, a:visited {color:black;} +a:hover {color:#2B6FB6;} +.nowrap {white-space: nowrap} + +/* Forms */ +fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;} +legend {color:#2B6FB6; font-weight:bold;} +label.msg {display:none;} +label.invalid {color:#EE0000; display:inline;} +input.invalid {border:1px solid #EE0000;} +input {background:#FFF; border:1px solid #CCC;} +input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;} +input, select, textarea {border:1px solid #808080;} +input.radio {border:1px none #000000; background:transparent; vertical-align:middle;} +input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;} +.input_noborder {border:0;} + +/* Buttons */ +#insert, #cancel, input.button, .updateButton { +border:0; margin:0; padding:0; +font-weight:bold; +width:94px; height:26px; +background:url(../default/img/buttons.png) 0 -26px; +cursor:pointer; +padding-bottom:2px; +float:left; +} + +#insert {background:url(../default/img/buttons.png) 0 -52px} +#cancel {background:url(../default/img/buttons.png) 0 0; float:right} + +/* Browse */ +a.pickcolor, a.browse {text-decoration:none} +a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;} +.mceOldBoxModel a.browse span {width:22px; height:20px;} +a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;} +a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +a.browse:hover span.disabled {border:1px solid white; background-color:transparent;} +a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;} +.mceOldBoxModel a.pickcolor span {width:21px; height:17px;} +a.pickcolor:hover span {background-color:#B2BBD0;} +a.pickcolor:hover span.disabled {} + +/* Charmap */ +table.charmap {border:1px solid #AAA; text-align:center} +td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;} +#charmap a {display:block; color:#000; text-decoration:none; border:0} +#charmap a:hover {background:#CCC;color:#2B6FB6} +#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center} +#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center} + +/* Source */ +.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;} +.mceActionPanel {margin-top:5px;} + +/* Tabs classes */ +.tabs {width:100%; height:18px; line-height:normal; background:url(../default/img/tabs.gif) repeat-x 0 -72px;} +.tabs ul {margin:0; padding:0; list-style:none;} +.tabs li {float:left; background:url(../default/img/tabs.gif) no-repeat 0 0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block;} +.tabs li.current {background:url(../default/img/tabs.gif) no-repeat 0 -18px; margin-right:2px;} +.tabs span {float:left; display:block; background:url(../default/img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;} +.tabs .current span {background:url(../default/img/tabs.gif) no-repeat right -54px;} +.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;} +.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;} + +/* Panels */ +.panel_wrapper div.panel {display:none;} +.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;} +.panel_wrapper {border:1px solid #919B9C; border-top:0px; padding:10px; padding-top:5px; clear:both; background:white;} + +/* Columns */ +.column {float:left;} +.properties {width:100%;} +.properties .column1 {} +.properties .column2 {text-align:left;} + +/* Titles */ +h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;} +h3 {font-size:14px;} +.title {font-size:12px; font-weight:bold; color:#2B6FB6;} + +/* Dialog specific */ +#link .panel_wrapper, #link div.current {height:125px;} +#image .panel_wrapper, #image div.current {height:200px;} +#plugintable thead {font-weight:bold; background:#DDD;} +#plugintable, #about #plugintable td {border:1px solid #919B9C;} +#plugintable {width:96%; margin-top:10px;} +#pluginscontainer {height:290px; overflow:auto;} +#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;} +#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;} +#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;} +#colorpicker #light div {overflow:hidden;} +#colorpicker #previewblock {float:right; padding-left:10px; height:20px;} +#colorpicker .panel_wrapper div.current {height:175px;} +#colorpicker #namedcolors {width:150px;} +#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;} +#colorpicker #colornamecontainer {margin-top:5px;} +#colorpicker #picker_panel fieldset {margin:auto;width:325px;} diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg.png b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg.png index 12cfb419..13a5cb03 100644 Binary files a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg.png and b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg.png differ diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_black.png b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_black.png index 8996c749..7fc57f2b 100644 Binary files a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_black.png and b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_black.png differ diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_silver.png b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_silver.png index bd5d2550..c0dcc6ca 100644 Binary files a/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_silver.png and b/js/tiny_mce/themes/advanced/skins/o2k7/img/button_bg_silver.png differ diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/ui.css b/js/tiny_mce/themes/advanced/skins/o2k7/ui.css index aae0e2bf..c0156d2c 100644 --- a/js/tiny_mce/themes/advanced/skins/o2k7/ui.css +++ b/js/tiny_mce/themes/advanced/skins/o2k7/ui.css @@ -1,215 +1,217 @@ -/* Reset */ -.o2k7Skin table, .o2k7Skin tbody, .o2k7Skin a, .o2k7Skin img, .o2k7Skin tr, .o2k7Skin div, .o2k7Skin td, .o2k7Skin iframe, .o2k7Skin span, .o2k7Skin *, .o2k7Skin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left} -.o2k7Skin a:hover, .o2k7Skin a:link, .o2k7Skin a:visited, .o2k7Skin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000} -.o2k7Skin table td {vertical-align:middle} - -/* Containers */ -.o2k7Skin table {background:#E5EFFD} -.o2k7Skin iframe {display:block; background:#FFF} -.o2k7Skin .mceToolbar {height:26px} - -/* External */ -.o2k7Skin .mceExternalToolbar {position:absolute; border:1px solid #ABC6DD; border-bottom:0; display:none} -.o2k7Skin .mceExternalToolbar td.mceToolbar {padding-right:13px;} -.o2k7Skin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0} - -/* Layout */ -.o2k7Skin table.mceLayout {border:0; border-left:1px solid #ABC6DD; border-right:1px solid #ABC6DD} -.o2k7Skin table.mceLayout tr.mceFirst td {border-top:1px solid #ABC6DD} -.o2k7Skin table.mceLayout tr.mceLast td {border-bottom:1px solid #ABC6DD} -.o2k7Skin table.mceToolbar, .o2k7Skin tr.mceFirst .mceToolbar tr td, .o2k7Skin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0} -.o2k7Skin .mceIframeContainer {border-top:1px solid #ABC6DD; border-bottom:1px solid #ABC6DD} -.o2k7Skin .mceStatusbar {display:block; font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; height:20px} -.o2k7Skin .mceStatusbar div {float:left; padding:2px} -.o2k7Skin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0} -.o2k7Skin .mceStatusbar a:hover {text-decoration:underline} -.o2k7Skin table.mceToolbar {margin-left:3px} -.o2k7Skin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; margin-left:3px;} -.o2k7Skin .mceToolbar td.mceFirst span {margin:0} -.o2k7Skin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px} -.o2k7Skin .mceToolbar .mceToolbarEndListBox span, .o2k7Skin .mceToolbar .mceToolbarStartListBox span {display:none} -.o2k7Skin span.mceIcon, .o2k7Skin img.mceIcon {display:block; width:20px; height:20px} -.o2k7Skin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} -.o2k7Skin td.mceCenter {text-align:center;} -.o2k7Skin td.mceCenter table {margin:0 auto; text-align:left;} -.o2k7Skin td.mceRight table {margin:0 0 0 auto;} - -/* Button */ -.o2k7Skin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px} -.o2k7Skin a.mceButton span, .o2k7Skin a.mceButton img {margin-left:1px} -.o2k7Skin .mceOldBoxModel a.mceButton span, .o2k7Skin .mceOldBoxModel a.mceButton img {margin:0 0 0 1px} -.o2k7Skin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px} -.o2k7Skin a.mceButtonActive, .o2k7Skin a.mceButtonSelected {background-position:0 -44px} -.o2k7Skin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} -.o2k7Skin .mceButtonLabeled {width:auto} -.o2k7Skin .mceButtonLabeled span.mceIcon {float:left} -.o2k7Skin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica} -.o2k7Skin .mceButtonDisabled .mceButtonLabel {color:#888} - -/* Separator */ -.o2k7Skin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px} - -/* ListBox */ -.o2k7Skin .mceListBox {margin-left:3px} -.o2k7Skin .mceListBox, .o2k7Skin .mceListBox a {display:block} -.o2k7Skin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; border:1px solid #b3c7e1; border-right:0; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} -.o2k7Skin .mceListBox .mceOpen {width:14px; height:22px; background:url(img/button_bg.png) -66px 0} -.o2k7Skin table.mceListBoxEnabled:hover .mceText, .o2k7Skin .mceListBoxHover .mceText, .o2k7Skin .mceListBoxSelected .mceText {background:#FFF} -.o2k7Skin table.mceListBoxEnabled:hover .mceOpen, .o2k7Skin .mceListBoxHover .mceOpen, .o2k7Skin .mceListBoxSelected .mceOpen {background-position:-66px -22px} -.o2k7Skin .mceListBoxDisabled .mceText {color:gray} -.o2k7Skin .mceListBoxMenu {overflow:auto; overflow-x:hidden} -.o2k7Skin .mceOldBoxModel .mceListBox .mceText {height:22px} -.o2k7Skin select.mceListBox {font-family:Tahoma,Verdana,Arial,Helvetica; font-size:12px; border:1px solid #b3c7e1; background:#FFF;} - -/* SplitButton */ -.o2k7Skin .mceSplitButton, .o2k7Skin .mceSplitButton a, .o2k7Skin .mceSplitButton span {display:block; height:22px} -.o2k7Skin .mceSplitButton {background:url(img/button_bg.png)} -.o2k7Skin .mceSplitButton a.mceAction {width:22px} -.o2k7Skin .mceSplitButton span.mceAction {width:22px; background-image:url(../../img/icons.gif)} -.o2k7Skin .mceSplitButton a.mceOpen {width:10px; background:url(img/button_bg.png) -44px 0} -.o2k7Skin .mceSplitButton span.mceOpen {display:none} -.o2k7Skin table.mceSplitButtonEnabled:hover a.mceAction, .o2k7Skin .mceSplitButtonHover a.mceAction, .o2k7Skin .mceSplitButtonSelected {background:url(img/button_bg.png) 0 -22px} -.o2k7Skin table.mceSplitButtonEnabled:hover a.mceOpen, .o2k7Skin .mceSplitButtonHover a.mceOpen, .o2k7Skin .mceSplitButtonSelected a.mceOpen {background-position:-44px -44px} -.o2k7Skin .mceSplitButtonDisabled .mceAction {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} -.o2k7Skin .mceSplitButtonActive {background-position:0 -44px} - -/* ColorSplitButton */ -.o2k7Skin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray} -.o2k7Skin .mceColorSplitMenu td {padding:2px} -.o2k7Skin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080} -.o2k7Skin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} -.o2k7Skin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} -.o2k7Skin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2} -.o2k7Skin a.mceMoreColors:hover {border:1px solid #0A246A} -.o2k7Skin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a;overflow:hidden} -.o2k7Skin .mce_forecolor span.mceAction, .o2k7Skin .mce_backcolor span.mceAction {height:15px;overflow:hidden} - -/* Menu */ -.o2k7Skin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #ABC6DD} -.o2k7Skin .mceNoIcons span.mceIcon {width:0;} -.o2k7Skin .mceNoIcons a .mceText {padding-left:10px} -.o2k7Skin .mceMenu table {background:#FFF} -.o2k7Skin .mceMenu a, .o2k7Skin .mceMenu span, .o2k7Skin .mceMenu {display:block} -.o2k7Skin .mceMenu td {height:20px} -.o2k7Skin .mceMenu a {position:relative;padding:3px 0 4px 0} -.o2k7Skin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} -.o2k7Skin .mceMenu span.mceText, .o2k7Skin .mceMenu .mcePreview {font-size:11px} -.o2k7Skin .mceMenu pre.mceText {font-family:Monospace} -.o2k7Skin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;} -.o2k7Skin .mceMenu .mceMenuItemEnabled a:hover, .o2k7Skin .mceMenu .mceMenuItemActive {background-color:#dbecf3} -.o2k7Skin td.mceMenuItemSeparator {background:#DDD; height:1px} -.o2k7Skin .mceMenuItemTitle a {border:0; background:#E5EFFD; border-bottom:1px solid #ABC6DD} -.o2k7Skin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px} -.o2k7Skin .mceMenuItemDisabled .mceText {color:#888} -.o2k7Skin .mceMenuItemSelected .mceIcon {background:url(../default/img/menu_check.gif)} -.o2k7Skin .mceNoIcons .mceMenuItemSelected a {background:url(../default/img/menu_arrow.gif) no-repeat -6px center} -.o2k7Skin .mceMenu span.mceMenuLine {display:none} -.o2k7Skin .mceMenuItemSub a {background:url(../default/img/menu_arrow.gif) no-repeat top right;} - -/* Progress,Resize */ -.o2k7Skin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=50); background:#FFF} -.o2k7Skin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(../default/img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} - -/* Formats */ -.o2k7Skin .mce_formatPreview a {font-size:10px} -.o2k7Skin .mce_p span.mceText {} -.o2k7Skin .mce_address span.mceText {font-style:italic} -.o2k7Skin .mce_pre span.mceText {font-family:monospace} -.o2k7Skin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} -.o2k7Skin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} -.o2k7Skin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} -.o2k7Skin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} -.o2k7Skin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} -.o2k7Skin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} - -/* Theme */ -.o2k7Skin span.mce_bold {background-position:0 0} -.o2k7Skin span.mce_italic {background-position:-60px 0} -.o2k7Skin span.mce_underline {background-position:-140px 0} -.o2k7Skin span.mce_strikethrough {background-position:-120px 0} -.o2k7Skin span.mce_undo {background-position:-160px 0} -.o2k7Skin span.mce_redo {background-position:-100px 0} -.o2k7Skin span.mce_cleanup {background-position:-40px 0} -.o2k7Skin span.mce_bullist {background-position:-20px 0} -.o2k7Skin span.mce_numlist {background-position:-80px 0} -.o2k7Skin span.mce_justifyleft {background-position:-460px 0} -.o2k7Skin span.mce_justifyright {background-position:-480px 0} -.o2k7Skin span.mce_justifycenter {background-position:-420px 0} -.o2k7Skin span.mce_justifyfull {background-position:-440px 0} -.o2k7Skin span.mce_anchor {background-position:-200px 0} -.o2k7Skin span.mce_indent {background-position:-400px 0} -.o2k7Skin span.mce_outdent {background-position:-540px 0} -.o2k7Skin span.mce_link {background-position:-500px 0} -.o2k7Skin span.mce_unlink {background-position:-640px 0} -.o2k7Skin span.mce_sub {background-position:-600px 0} -.o2k7Skin span.mce_sup {background-position:-620px 0} -.o2k7Skin span.mce_removeformat {background-position:-580px 0} -.o2k7Skin span.mce_newdocument {background-position:-520px 0} -.o2k7Skin span.mce_image {background-position:-380px 0} -.o2k7Skin span.mce_help {background-position:-340px 0} -.o2k7Skin span.mce_code {background-position:-260px 0} -.o2k7Skin span.mce_hr {background-position:-360px 0} -.o2k7Skin span.mce_visualaid {background-position:-660px 0} -.o2k7Skin span.mce_charmap {background-position:-240px 0} -.o2k7Skin span.mce_paste {background-position:-560px 0} -.o2k7Skin span.mce_copy {background-position:-700px 0} -.o2k7Skin span.mce_cut {background-position:-680px 0} -.o2k7Skin span.mce_blockquote {background-position:-220px 0} -.o2k7Skin .mce_forecolor span.mceAction {background-position:-720px 0} -.o2k7Skin .mce_backcolor span.mceAction {background-position:-760px 0} -.o2k7Skin span.mce_forecolorpicker {background-position:-720px 0} -.o2k7Skin span.mce_backcolorpicker {background-position:-760px 0} - -/* Plugins */ -.o2k7Skin span.mce_advhr {background-position:-0px -20px} -.o2k7Skin span.mce_ltr {background-position:-20px -20px} -.o2k7Skin span.mce_rtl {background-position:-40px -20px} -.o2k7Skin span.mce_emotions {background-position:-60px -20px} -.o2k7Skin span.mce_fullpage {background-position:-80px -20px} -.o2k7Skin span.mce_fullscreen {background-position:-100px -20px} -.o2k7Skin span.mce_iespell {background-position:-120px -20px} -.o2k7Skin span.mce_insertdate {background-position:-140px -20px} -.o2k7Skin span.mce_inserttime {background-position:-160px -20px} -.o2k7Skin span.mce_absolute {background-position:-180px -20px} -.o2k7Skin span.mce_backward {background-position:-200px -20px} -.o2k7Skin span.mce_forward {background-position:-220px -20px} -.o2k7Skin span.mce_insert_layer {background-position:-240px -20px} -.o2k7Skin span.mce_insertlayer {background-position:-260px -20px} -.o2k7Skin span.mce_movebackward {background-position:-280px -20px} -.o2k7Skin span.mce_moveforward {background-position:-300px -20px} -.o2k7Skin span.mce_media {background-position:-320px -20px} -.o2k7Skin span.mce_nonbreaking {background-position:-340px -20px} -.o2k7Skin span.mce_pastetext {background-position:-360px -20px} -.o2k7Skin span.mce_pasteword {background-position:-380px -20px} -.o2k7Skin span.mce_selectall {background-position:-400px -20px} -.o2k7Skin span.mce_preview {background-position:-420px -20px} -.o2k7Skin span.mce_print {background-position:-440px -20px} -.o2k7Skin span.mce_cancel {background-position:-460px -20px} -.o2k7Skin span.mce_save {background-position:-480px -20px} -.o2k7Skin span.mce_replace {background-position:-500px -20px} -.o2k7Skin span.mce_search {background-position:-520px -20px} -.o2k7Skin span.mce_styleprops {background-position:-560px -20px} -.o2k7Skin span.mce_table {background-position:-580px -20px} -.o2k7Skin span.mce_cell_props {background-position:-600px -20px} -.o2k7Skin span.mce_delete_table {background-position:-620px -20px} -.o2k7Skin span.mce_delete_col {background-position:-640px -20px} -.o2k7Skin span.mce_delete_row {background-position:-660px -20px} -.o2k7Skin span.mce_col_after {background-position:-680px -20px} -.o2k7Skin span.mce_col_before {background-position:-700px -20px} -.o2k7Skin span.mce_row_after {background-position:-720px -20px} -.o2k7Skin span.mce_row_before {background-position:-740px -20px} -.o2k7Skin span.mce_merge_cells {background-position:-760px -20px} -.o2k7Skin span.mce_table_props {background-position:-980px -20px} -.o2k7Skin span.mce_row_props {background-position:-780px -20px} -.o2k7Skin span.mce_split_cells {background-position:-800px -20px} -.o2k7Skin span.mce_template {background-position:-820px -20px} -.o2k7Skin span.mce_visualchars {background-position:-840px -20px} -.o2k7Skin span.mce_abbr {background-position:-860px -20px} -.o2k7Skin span.mce_acronym {background-position:-880px -20px} -.o2k7Skin span.mce_attribs {background-position:-900px -20px} -.o2k7Skin span.mce_cite {background-position:-920px -20px} -.o2k7Skin span.mce_del {background-position:-940px -20px} -.o2k7Skin span.mce_ins {background-position:-960px -20px} -.o2k7Skin span.mce_pagebreak {background-position:0 -40px} -.o2k7Skin span.mce_restoredraft {background-position:-20px -40px} -.o2k7Skin .mce_spellchecker span.mceAction {background-position:-540px -20px} +/* Reset */ +.o2k7Skin table, .o2k7Skin tbody, .o2k7Skin a, .o2k7Skin img, .o2k7Skin tr, .o2k7Skin div, .o2k7Skin td, .o2k7Skin iframe, .o2k7Skin span, .o2k7Skin *, .o2k7Skin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left} +.o2k7Skin a:hover, .o2k7Skin a:link, .o2k7Skin a:visited, .o2k7Skin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000} +.o2k7Skin table td {vertical-align:middle} + +/* Containers */ +.o2k7Skin table {background:transparent} +.o2k7Skin iframe {display:block;} +.o2k7Skin .mceToolbar {height:26px} + +/* External */ +.o2k7Skin .mceExternalToolbar {position:absolute; border:1px solid #ABC6DD; border-bottom:0; display:none} +.o2k7Skin .mceExternalToolbar td.mceToolbar {padding-right:13px;} +.o2k7Skin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0} + +/* Layout */ +.o2k7Skin table.mceLayout {border:0; border-left:1px solid #ABC6DD; border-right:1px solid #ABC6DD} +.o2k7Skin table.mceLayout tr.mceFirst td {border-top:1px solid #ABC6DD} +.o2k7Skin table.mceLayout tr.mceLast td {border-bottom:1px solid #ABC6DD} +.o2k7Skin table.mceToolbar, .o2k7Skin tr.mceFirst .mceToolbar tr td, .o2k7Skin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0} +.o2k7Skin .mceIframeContainer {border-top:1px solid #ABC6DD; border-bottom:1px solid #ABC6DD} +.o2k7Skin td.mceToolbar{background:#E5EFFD} +.o2k7Skin .mceStatusbar {background:#E5EFFD; display:block; font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; height:20px} +.o2k7Skin .mceStatusbar div {float:left; padding:2px} +.o2k7Skin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize; outline:0} +.o2k7Skin .mceStatusbar a:hover {text-decoration:underline} +.o2k7Skin table.mceToolbar {margin-left:3px} +.o2k7Skin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; margin-left:3px;} +.o2k7Skin .mceToolbar td.mceFirst span {margin:0} +.o2k7Skin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px} +.o2k7Skin .mceToolbar .mceToolbarEndListBox span, .o2k7Skin .mceToolbar .mceToolbarStartListBox span {display:none} +.o2k7Skin span.mceIcon, .o2k7Skin img.mceIcon {display:block; width:20px; height:20px} +.o2k7Skin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px} +.o2k7Skin td.mceCenter {text-align:center;} +.o2k7Skin td.mceCenter table {margin:0 auto; text-align:left;} +.o2k7Skin td.mceRight table {margin:0 0 0 auto;} + +/* Button */ +.o2k7Skin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px} +.o2k7Skin a.mceButton span, .o2k7Skin a.mceButton img {margin-left:1px} +.o2k7Skin .mceOldBoxModel a.mceButton span, .o2k7Skin .mceOldBoxModel a.mceButton img {margin:0 0 0 1px} +.o2k7Skin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px} +.o2k7Skin a.mceButtonActive, .o2k7Skin a.mceButtonSelected {background-position:0 -44px} +.o2k7Skin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.o2k7Skin .mceButtonLabeled {width:auto} +.o2k7Skin .mceButtonLabeled span.mceIcon {float:left} +.o2k7Skin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica} +.o2k7Skin .mceButtonDisabled .mceButtonLabel {color:#888} + +/* Separator */ +.o2k7Skin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px} + +/* ListBox */ +.o2k7Skin .mceListBox {padding-left: 3px} +.o2k7Skin .mceListBox, .o2k7Skin .mceListBox a {display:block} +.o2k7Skin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; border:1px solid #b3c7e1; border-right:0; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden} +.o2k7Skin .mceListBox .mceOpen {width:14px; height:22px; background:url(img/button_bg.png) -66px 0} +.o2k7Skin table.mceListBoxEnabled:hover .mceText, .o2k7Skin .mceListBoxHover .mceText, .o2k7Skin .mceListBoxSelected .mceText {background:#FFF} +.o2k7Skin table.mceListBoxEnabled:hover .mceOpen, .o2k7Skin .mceListBoxHover .mceOpen, .o2k7Skin .mceListBoxSelected .mceOpen {background-position:-66px -22px} +.o2k7Skin .mceListBoxDisabled .mceText {color:gray} +.o2k7Skin .mceListBoxMenu {overflow:auto; overflow-x:hidden; margin-left:3px} +.o2k7Skin .mceOldBoxModel .mceListBox .mceText {height:22px} +.o2k7Skin select.mceListBox {font-family:Tahoma,Verdana,Arial,Helvetica; font-size:12px; border:1px solid #b3c7e1; background:#FFF;} + +/* SplitButton */ +.o2k7Skin .mceSplitButton, .o2k7Skin .mceSplitButton a, .o2k7Skin .mceSplitButton span {display:block; height:22px; direction:ltr} +.o2k7Skin .mceSplitButton {background:url(img/button_bg.png)} +.o2k7Skin .mceSplitButton a.mceAction {width:22px} +.o2k7Skin .mceSplitButton span.mceAction {width:22px; background-image:url(../../img/icons.gif)} +.o2k7Skin .mceSplitButton a.mceOpen {width:10px; background:url(img/button_bg.png) -44px 0} +.o2k7Skin .mceSplitButton span.mceOpen {display:none} +.o2k7Skin table.mceSplitButtonEnabled:hover a.mceAction, .o2k7Skin .mceSplitButtonHover a.mceAction, .o2k7Skin .mceSplitButtonSelected {background:url(img/button_bg.png) 0 -22px} +.o2k7Skin table.mceSplitButtonEnabled:hover a.mceOpen, .o2k7Skin .mceSplitButtonHover a.mceOpen, .o2k7Skin .mceSplitButtonSelected a.mceOpen {background-position:-44px -44px} +.o2k7Skin .mceSplitButtonDisabled .mceAction {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)} +.o2k7Skin .mceSplitButtonActive {background-position:0 -44px} + +/* ColorSplitButton */ +.o2k7Skin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray} +.o2k7Skin .mceColorSplitMenu td {padding:2px} +.o2k7Skin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080} +.o2k7Skin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px} +.o2k7Skin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF} +.o2k7Skin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2} +.o2k7Skin a.mceMoreColors:hover {border:1px solid #0A246A} +.o2k7Skin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a;overflow:hidden} +.o2k7Skin .mce_forecolor span.mceAction, .o2k7Skin .mce_backcolor span.mceAction {height:15px;overflow:hidden} + +/* Menu */ +.o2k7Skin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #ABC6DD} +.o2k7Skin .mceNoIcons span.mceIcon {width:0;} +.o2k7Skin .mceNoIcons a .mceText {padding-left:10px} +.o2k7Skin .mceMenu table {background:#FFF} +.o2k7Skin .mceMenu a, .o2k7Skin .mceMenu span, .o2k7Skin .mceMenu {display:block} +.o2k7Skin .mceMenu td {height:20px} +.o2k7Skin .mceMenu a {position:relative;padding:3px 0 4px 0} +.o2k7Skin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block} +.o2k7Skin .mceMenu span.mceText, .o2k7Skin .mceMenu .mcePreview {font-size:11px} +.o2k7Skin .mceMenu pre.mceText {font-family:Monospace} +.o2k7Skin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;} +.o2k7Skin .mceMenu .mceMenuItemEnabled a:hover, .o2k7Skin .mceMenu .mceMenuItemActive {background-color:#dbecf3} +.o2k7Skin td.mceMenuItemSeparator {background:#DDD; height:1px} +.o2k7Skin .mceMenuItemTitle a {border:0; background:#E5EFFD; border-bottom:1px solid #ABC6DD} +.o2k7Skin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px} +.o2k7Skin .mceMenuItemDisabled .mceText {color:#888} +.o2k7Skin .mceMenuItemSelected .mceIcon {background:url(../default/img/menu_check.gif)} +.o2k7Skin .mceNoIcons .mceMenuItemSelected a {background:url(../default/img/menu_arrow.gif) no-repeat -6px center} +.o2k7Skin .mceMenu span.mceMenuLine {display:none} +.o2k7Skin .mceMenuItemSub a {background:url(../default/img/menu_arrow.gif) no-repeat top right;} +.o2k7Skin .mceMenuItem td, .o2k7Skin .mceMenuItem th {line-height: normal} + +/* Progress,Resize */ +.o2k7Skin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=50); background:#FFF} +.o2k7Skin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(../default/img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px} + +/* Formats */ +.o2k7Skin .mce_formatPreview a {font-size:10px} +.o2k7Skin .mce_p span.mceText {} +.o2k7Skin .mce_address span.mceText {font-style:italic} +.o2k7Skin .mce_pre span.mceText {font-family:monospace} +.o2k7Skin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em} +.o2k7Skin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em} +.o2k7Skin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em} +.o2k7Skin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em} +.o2k7Skin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em} +.o2k7Skin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em} + +/* Theme */ +.o2k7Skin span.mce_bold {background-position:0 0} +.o2k7Skin span.mce_italic {background-position:-60px 0} +.o2k7Skin span.mce_underline {background-position:-140px 0} +.o2k7Skin span.mce_strikethrough {background-position:-120px 0} +.o2k7Skin span.mce_undo {background-position:-160px 0} +.o2k7Skin span.mce_redo {background-position:-100px 0} +.o2k7Skin span.mce_cleanup {background-position:-40px 0} +.o2k7Skin span.mce_bullist {background-position:-20px 0} +.o2k7Skin span.mce_numlist {background-position:-80px 0} +.o2k7Skin span.mce_justifyleft {background-position:-460px 0} +.o2k7Skin span.mce_justifyright {background-position:-480px 0} +.o2k7Skin span.mce_justifycenter {background-position:-420px 0} +.o2k7Skin span.mce_justifyfull {background-position:-440px 0} +.o2k7Skin span.mce_anchor {background-position:-200px 0} +.o2k7Skin span.mce_indent {background-position:-400px 0} +.o2k7Skin span.mce_outdent {background-position:-540px 0} +.o2k7Skin span.mce_link {background-position:-500px 0} +.o2k7Skin span.mce_unlink {background-position:-640px 0} +.o2k7Skin span.mce_sub {background-position:-600px 0} +.o2k7Skin span.mce_sup {background-position:-620px 0} +.o2k7Skin span.mce_removeformat {background-position:-580px 0} +.o2k7Skin span.mce_newdocument {background-position:-520px 0} +.o2k7Skin span.mce_image {background-position:-380px 0} +.o2k7Skin span.mce_help {background-position:-340px 0} +.o2k7Skin span.mce_code {background-position:-260px 0} +.o2k7Skin span.mce_hr {background-position:-360px 0} +.o2k7Skin span.mce_visualaid {background-position:-660px 0} +.o2k7Skin span.mce_charmap {background-position:-240px 0} +.o2k7Skin span.mce_paste {background-position:-560px 0} +.o2k7Skin span.mce_copy {background-position:-700px 0} +.o2k7Skin span.mce_cut {background-position:-680px 0} +.o2k7Skin span.mce_blockquote {background-position:-220px 0} +.o2k7Skin .mce_forecolor span.mceAction {background-position:-720px 0} +.o2k7Skin .mce_backcolor span.mceAction {background-position:-760px 0} +.o2k7Skin span.mce_forecolorpicker {background-position:-720px 0} +.o2k7Skin span.mce_backcolorpicker {background-position:-760px 0} + +/* Plugins */ +.o2k7Skin span.mce_advhr {background-position:-0px -20px} +.o2k7Skin span.mce_ltr {background-position:-20px -20px} +.o2k7Skin span.mce_rtl {background-position:-40px -20px} +.o2k7Skin span.mce_emotions {background-position:-60px -20px} +.o2k7Skin span.mce_fullpage {background-position:-80px -20px} +.o2k7Skin span.mce_fullscreen {background-position:-100px -20px} +.o2k7Skin span.mce_iespell {background-position:-120px -20px} +.o2k7Skin span.mce_insertdate {background-position:-140px -20px} +.o2k7Skin span.mce_inserttime {background-position:-160px -20px} +.o2k7Skin span.mce_absolute {background-position:-180px -20px} +.o2k7Skin span.mce_backward {background-position:-200px -20px} +.o2k7Skin span.mce_forward {background-position:-220px -20px} +.o2k7Skin span.mce_insert_layer {background-position:-240px -20px} +.o2k7Skin span.mce_insertlayer {background-position:-260px -20px} +.o2k7Skin span.mce_movebackward {background-position:-280px -20px} +.o2k7Skin span.mce_moveforward {background-position:-300px -20px} +.o2k7Skin span.mce_media {background-position:-320px -20px} +.o2k7Skin span.mce_nonbreaking {background-position:-340px -20px} +.o2k7Skin span.mce_pastetext {background-position:-360px -20px} +.o2k7Skin span.mce_pasteword {background-position:-380px -20px} +.o2k7Skin span.mce_selectall {background-position:-400px -20px} +.o2k7Skin span.mce_preview {background-position:-420px -20px} +.o2k7Skin span.mce_print {background-position:-440px -20px} +.o2k7Skin span.mce_cancel {background-position:-460px -20px} +.o2k7Skin span.mce_save {background-position:-480px -20px} +.o2k7Skin span.mce_replace {background-position:-500px -20px} +.o2k7Skin span.mce_search {background-position:-520px -20px} +.o2k7Skin span.mce_styleprops {background-position:-560px -20px} +.o2k7Skin span.mce_table {background-position:-580px -20px} +.o2k7Skin span.mce_cell_props {background-position:-600px -20px} +.o2k7Skin span.mce_delete_table {background-position:-620px -20px} +.o2k7Skin span.mce_delete_col {background-position:-640px -20px} +.o2k7Skin span.mce_delete_row {background-position:-660px -20px} +.o2k7Skin span.mce_col_after {background-position:-680px -20px} +.o2k7Skin span.mce_col_before {background-position:-700px -20px} +.o2k7Skin span.mce_row_after {background-position:-720px -20px} +.o2k7Skin span.mce_row_before {background-position:-740px -20px} +.o2k7Skin span.mce_merge_cells {background-position:-760px -20px} +.o2k7Skin span.mce_table_props {background-position:-980px -20px} +.o2k7Skin span.mce_row_props {background-position:-780px -20px} +.o2k7Skin span.mce_split_cells {background-position:-800px -20px} +.o2k7Skin span.mce_template {background-position:-820px -20px} +.o2k7Skin span.mce_visualchars {background-position:-840px -20px} +.o2k7Skin span.mce_abbr {background-position:-860px -20px} +.o2k7Skin span.mce_acronym {background-position:-880px -20px} +.o2k7Skin span.mce_attribs {background-position:-900px -20px} +.o2k7Skin span.mce_cite {background-position:-920px -20px} +.o2k7Skin span.mce_del {background-position:-940px -20px} +.o2k7Skin span.mce_ins {background-position:-960px -20px} +.o2k7Skin span.mce_pagebreak {background-position:0 -40px} +.o2k7Skin span.mce_restoredraft {background-position:-20px -40px} +.o2k7Skin span.mce_spellchecker {background-position:-540px -20px} diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/ui_black.css b/js/tiny_mce/themes/advanced/skins/o2k7/ui_black.css index 153f0c38..85812cde 100644 --- a/js/tiny_mce/themes/advanced/skins/o2k7/ui_black.css +++ b/js/tiny_mce/themes/advanced/skins/o2k7/ui_black.css @@ -1,8 +1,8 @@ -/* Black */ -.o2k7SkinBlack .mceToolbar .mceToolbarStart span, .o2k7SkinBlack .mceToolbar .mceToolbarEnd span, .o2k7SkinBlack .mceButton, .o2k7SkinBlack .mceSplitButton, .o2k7SkinBlack .mceSeparator, .o2k7SkinBlack .mceSplitButton a.mceOpen, .o2k7SkinBlack .mceListBox a.mceOpen {background-image:url(img/button_bg_black.png)} -.o2k7SkinBlack table, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack .mceMenuItemTitle span.mceText, .o2k7SkinBlack .mceStatusbar div, .o2k7SkinBlack .mceStatusbar span, .o2k7SkinBlack .mceStatusbar a {background:#535353; color:#FFF} -.o2k7SkinBlack table.mceListBoxEnabled .mceText, o2k7SkinBlack .mceListBox .mceText {background:#FFF; border:1px solid #CBCFD4; border-bottom-color:#989FA9; border-right:0} -.o2k7SkinBlack table.mceListBoxEnabled:hover .mceText, .o2k7SkinBlack .mceListBoxHover .mceText, .o2k7SkinBlack .mceListBoxSelected .mceText {background:#FFF; border:1px solid #FFBD69; border-right:0} -.o2k7SkinBlack .mceExternalToolbar, .o2k7SkinBlack .mceListBox .mceText, .o2k7SkinBlack div.mceMenu, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceFirst td, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceLast td, .o2k7SkinBlack .mceIframeContainer {border-color: #535353;} -.o2k7SkinBlack table.mceSplitButtonEnabled:hover a.mceAction, .o2k7SkinBlack .mceSplitButtonHover a.mceAction, .o2k7SkinBlack .mceSplitButtonSelected {background-image:url(img/button_bg_black.png)} +/* Black */ +.o2k7SkinBlack .mceToolbar .mceToolbarStart span, .o2k7SkinBlack .mceToolbar .mceToolbarEnd span, .o2k7SkinBlack .mceButton, .o2k7SkinBlack .mceSplitButton, .o2k7SkinBlack .mceSeparator, .o2k7SkinBlack .mceSplitButton a.mceOpen, .o2k7SkinBlack .mceListBox a.mceOpen {background-image:url(img/button_bg_black.png)} +.o2k7SkinBlack td.mceToolbar, .o2k7SkinBlack td.mceStatusbar, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack .mceMenuItemTitle span.mceText, .o2k7SkinBlack .mceStatusbar div, .o2k7SkinBlack .mceStatusbar span, .o2k7SkinBlack .mceStatusbar a {background:#535353; color:#FFF} +.o2k7SkinBlack table.mceListBoxEnabled .mceText, o2k7SkinBlack .mceListBox .mceText {background:#FFF; border:1px solid #CBCFD4; border-bottom-color:#989FA9; border-right:0} +.o2k7SkinBlack table.mceListBoxEnabled:hover .mceText, .o2k7SkinBlack .mceListBoxHover .mceText, .o2k7SkinBlack .mceListBoxSelected .mceText {background:#FFF; border:1px solid #FFBD69; border-right:0} +.o2k7SkinBlack .mceExternalToolbar, .o2k7SkinBlack .mceListBox .mceText, .o2k7SkinBlack div.mceMenu, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceFirst td, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceLast td, .o2k7SkinBlack .mceIframeContainer {border-color: #535353;} +.o2k7SkinBlack table.mceSplitButtonEnabled:hover a.mceAction, .o2k7SkinBlack .mceSplitButtonHover a.mceAction, .o2k7SkinBlack .mceSplitButtonSelected {background-image:url(img/button_bg_black.png)} .o2k7SkinBlack .mceMenu .mceMenuItemEnabled a:hover, .o2k7SkinBlack .mceMenu .mceMenuItemActive {background-color:#FFE7A1} \ No newline at end of file diff --git a/js/tiny_mce/themes/advanced/skins/o2k7/ui_silver.css b/js/tiny_mce/themes/advanced/skins/o2k7/ui_silver.css index 7fe3b45e..d64c3616 100644 --- a/js/tiny_mce/themes/advanced/skins/o2k7/ui_silver.css +++ b/js/tiny_mce/themes/advanced/skins/o2k7/ui_silver.css @@ -1,5 +1,5 @@ -/* Silver */ -.o2k7SkinSilver .mceToolbar .mceToolbarStart span, .o2k7SkinSilver .mceButton, .o2k7SkinSilver .mceSplitButton, .o2k7SkinSilver .mceSeparator, .o2k7SkinSilver .mceSplitButton a.mceOpen, .o2k7SkinSilver .mceListBox a.mceOpen {background-image:url(img/button_bg_silver.png)} -.o2k7SkinSilver table, .o2k7SkinSilver .mceMenuItemTitle a {background:#eee} -.o2k7SkinSilver .mceListBox .mceText {background:#FFF} -.o2k7SkinSilver .mceExternalToolbar, .o2k7SkinSilver .mceListBox .mceText, .o2k7SkinSilver div.mceMenu, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceFirst td, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceLast td, .o2k7SkinSilver .mceIframeContainer {border-color: #bbb} +/* Silver */ +.o2k7SkinSilver .mceToolbar .mceToolbarStart span, .o2k7SkinSilver .mceButton, .o2k7SkinSilver .mceSplitButton, .o2k7SkinSilver .mceSeparator, .o2k7SkinSilver .mceSplitButton a.mceOpen, .o2k7SkinSilver .mceListBox a.mceOpen {background-image:url(img/button_bg_silver.png)} +.o2k7SkinSilver td.mceToolbar, .o2k7SkinSilver td.mceStatusbar, .o2k7SkinSilver .mceMenuItemTitle a {background:#eee} +.o2k7SkinSilver .mceListBox .mceText {background:#FFF} +.o2k7SkinSilver .mceExternalToolbar, .o2k7SkinSilver .mceListBox .mceText, .o2k7SkinSilver div.mceMenu, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceFirst td, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceLast td, .o2k7SkinSilver .mceIframeContainer {border-color: #bbb} diff --git a/js/tiny_mce/themes/advanced/source_editor.htm b/js/tiny_mce/themes/advanced/source_editor.htm index 1c81d23e..d3f75d66 100644 --- a/js/tiny_mce/themes/advanced/source_editor.htm +++ b/js/tiny_mce/themes/advanced/source_editor.htm @@ -1,26 +1,25 @@ - - - - {#advanced_dlg.code_title} - - - - -
    -
    {#advanced_dlg.code_title}
    - -
    - -
    - -
    - - - -
    - - -
    -
    - - + + + {#advanced_dlg.code_title} + + + + +
    +
    + +
    + +
    + +
    + + + +
    + + +
    +
    + + diff --git a/js/tiny_mce/themes/simple/editor_template.js b/js/tiny_mce/themes/simple/editor_template.js index ed89abc0..4b3209cc 100644 --- a/js/tiny_mce/themes/simple/editor_template.js +++ b/js/tiny_mce/themes/simple/editor_template.js @@ -1 +1 @@ -(function(){var a=tinymce.DOM;tinymce.ThemeManager.requireLangPack("simple");tinymce.create("tinymce.themes.SimpleTheme",{init:function(c,d){var e=this,b=["Bold","Italic","Underline","Strikethrough","InsertUnorderedList","InsertOrderedList"],f=c.settings;e.editor=c;c.onInit.add(function(){c.onNodeChange.add(function(h,g){tinymce.each(b,function(i){g.get(i.toLowerCase()).setActive(h.queryCommandState(i))})});c.dom.loadCSS(d+"/skins/"+f.skin+"/content.css")});a.loadCSS((f.editor_css?c.documentBaseURI.toAbsolute(f.editor_css):"")||d+"/skins/"+f.skin+"/ui.css")},renderUI:function(h){var e=this,i=h.targetNode,b,c,d=e.editor,f=d.controlManager,g;i=a.insertAfter(a.create("span",{id:d.id+"_container","class":"mceEditor "+d.settings.skin+"SimpleSkin"}),i);i=g=a.add(i,"table",{cellPadding:0,cellSpacing:0,"class":"mceLayout"});i=c=a.add(i,"tbody");i=a.add(c,"tr");i=b=a.add(a.add(i,"td"),"div",{"class":"mceIframeContainer"});i=a.add(a.add(c,"tr",{"class":"last"}),"td",{"class":"mceToolbar mceLast",align:"center"});c=e.toolbar=f.createToolbar("tools1");c.add(f.createButton("bold",{title:"simple.bold_desc",cmd:"Bold"}));c.add(f.createButton("italic",{title:"simple.italic_desc",cmd:"Italic"}));c.add(f.createButton("underline",{title:"simple.underline_desc",cmd:"Underline"}));c.add(f.createButton("strikethrough",{title:"simple.striketrough_desc",cmd:"Strikethrough"}));c.add(f.createSeparator());c.add(f.createButton("undo",{title:"simple.undo_desc",cmd:"Undo"}));c.add(f.createButton("redo",{title:"simple.redo_desc",cmd:"Redo"}));c.add(f.createSeparator());c.add(f.createButton("cleanup",{title:"simple.cleanup_desc",cmd:"mceCleanup"}));c.add(f.createSeparator());c.add(f.createButton("insertunorderedlist",{title:"simple.bullist_desc",cmd:"InsertUnorderedList"}));c.add(f.createButton("insertorderedlist",{title:"simple.numlist_desc",cmd:"InsertOrderedList"}));c.renderTo(i);return{iframeContainer:b,editorContainer:d.id+"_container",sizeContainer:g,deltaHeight:-20}},getInfo:function(){return{longname:"Simple theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.ThemeManager.add("simple",tinymce.themes.SimpleTheme)})(); \ No newline at end of file +(function(){var a=tinymce.DOM;tinymce.ThemeManager.requireLangPack("simple");tinymce.create("tinymce.themes.SimpleTheme",{init:function(c,d){var e=this,b=["Bold","Italic","Underline","Strikethrough","InsertUnorderedList","InsertOrderedList"],f=c.settings;e.editor=c;c.contentCSS.push(d+"/skins/"+f.skin+"/content.css");c.onInit.add(function(){c.onNodeChange.add(function(h,g){tinymce.each(b,function(i){g.get(i.toLowerCase()).setActive(h.queryCommandState(i))})})});a.loadCSS((f.editor_css?c.documentBaseURI.toAbsolute(f.editor_css):"")||d+"/skins/"+f.skin+"/ui.css")},renderUI:function(h){var e=this,i=h.targetNode,b,c,d=e.editor,f=d.controlManager,g;i=a.insertAfter(a.create("span",{id:d.id+"_container","class":"mceEditor "+d.settings.skin+"SimpleSkin"}),i);i=g=a.add(i,"table",{cellPadding:0,cellSpacing:0,"class":"mceLayout"});i=c=a.add(i,"tbody");i=a.add(c,"tr");i=b=a.add(a.add(i,"td"),"div",{"class":"mceIframeContainer"});i=a.add(a.add(c,"tr",{"class":"last"}),"td",{"class":"mceToolbar mceLast",align:"center"});c=e.toolbar=f.createToolbar("tools1");c.add(f.createButton("bold",{title:"simple.bold_desc",cmd:"Bold"}));c.add(f.createButton("italic",{title:"simple.italic_desc",cmd:"Italic"}));c.add(f.createButton("underline",{title:"simple.underline_desc",cmd:"Underline"}));c.add(f.createButton("strikethrough",{title:"simple.striketrough_desc",cmd:"Strikethrough"}));c.add(f.createSeparator());c.add(f.createButton("undo",{title:"simple.undo_desc",cmd:"Undo"}));c.add(f.createButton("redo",{title:"simple.redo_desc",cmd:"Redo"}));c.add(f.createSeparator());c.add(f.createButton("cleanup",{title:"simple.cleanup_desc",cmd:"mceCleanup"}));c.add(f.createSeparator());c.add(f.createButton("insertunorderedlist",{title:"simple.bullist_desc",cmd:"InsertUnorderedList"}));c.add(f.createButton("insertorderedlist",{title:"simple.numlist_desc",cmd:"InsertOrderedList"}));c.renderTo(i);return{iframeContainer:b,editorContainer:d.id+"_container",sizeContainer:g,deltaHeight:-20}},getInfo:function(){return{longname:"Simple theme",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.ThemeManager.add("simple",tinymce.themes.SimpleTheme)})(); \ No newline at end of file diff --git a/js/tiny_mce/themes/simple/editor_template_src.js b/js/tiny_mce/themes/simple/editor_template_src.js index 4b862d49..35c19a6b 100644 --- a/js/tiny_mce/themes/simple/editor_template_src.js +++ b/js/tiny_mce/themes/simple/editor_template_src.js @@ -1,85 +1,84 @@ -/** - * editor_template_src.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -(function() { - var DOM = tinymce.DOM; - - // Tell it to load theme specific language pack(s) - tinymce.ThemeManager.requireLangPack('simple'); - - tinymce.create('tinymce.themes.SimpleTheme', { - init : function(ed, url) { - var t = this, states = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'InsertUnorderedList', 'InsertOrderedList'], s = ed.settings; - - t.editor = ed; - - ed.onInit.add(function() { - ed.onNodeChange.add(function(ed, cm) { - tinymce.each(states, function(c) { - cm.get(c.toLowerCase()).setActive(ed.queryCommandState(c)); - }); - }); - - ed.dom.loadCSS(url + "/skins/" + s.skin + "/content.css"); - }); - - DOM.loadCSS((s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : '') || url + "/skins/" + s.skin + "/ui.css"); - }, - - renderUI : function(o) { - var t = this, n = o.targetNode, ic, tb, ed = t.editor, cf = ed.controlManager, sc; - - n = DOM.insertAfter(DOM.create('span', {id : ed.id + '_container', 'class' : 'mceEditor ' + ed.settings.skin + 'SimpleSkin'}), n); - n = sc = DOM.add(n, 'table', {cellPadding : 0, cellSpacing : 0, 'class' : 'mceLayout'}); - n = tb = DOM.add(n, 'tbody'); - - // Create iframe container - n = DOM.add(tb, 'tr'); - n = ic = DOM.add(DOM.add(n, 'td'), 'div', {'class' : 'mceIframeContainer'}); - - // Create toolbar container - n = DOM.add(DOM.add(tb, 'tr', {'class' : 'last'}), 'td', {'class' : 'mceToolbar mceLast', align : 'center'}); - - // Create toolbar - tb = t.toolbar = cf.createToolbar("tools1"); - tb.add(cf.createButton('bold', {title : 'simple.bold_desc', cmd : 'Bold'})); - tb.add(cf.createButton('italic', {title : 'simple.italic_desc', cmd : 'Italic'})); - tb.add(cf.createButton('underline', {title : 'simple.underline_desc', cmd : 'Underline'})); - tb.add(cf.createButton('strikethrough', {title : 'simple.striketrough_desc', cmd : 'Strikethrough'})); - tb.add(cf.createSeparator()); - tb.add(cf.createButton('undo', {title : 'simple.undo_desc', cmd : 'Undo'})); - tb.add(cf.createButton('redo', {title : 'simple.redo_desc', cmd : 'Redo'})); - tb.add(cf.createSeparator()); - tb.add(cf.createButton('cleanup', {title : 'simple.cleanup_desc', cmd : 'mceCleanup'})); - tb.add(cf.createSeparator()); - tb.add(cf.createButton('insertunorderedlist', {title : 'simple.bullist_desc', cmd : 'InsertUnorderedList'})); - tb.add(cf.createButton('insertorderedlist', {title : 'simple.numlist_desc', cmd : 'InsertOrderedList'})); - tb.renderTo(n); - - return { - iframeContainer : ic, - editorContainer : ed.id + '_container', - sizeContainer : sc, - deltaHeight : -20 - }; - }, - - getInfo : function() { - return { - longname : 'Simple theme', - author : 'Moxiecode Systems AB', - authorurl : 'http://tinymce.moxiecode.com', - version : tinymce.majorVersion + "." + tinymce.minorVersion - } - } - }); - - tinymce.ThemeManager.add('simple', tinymce.themes.SimpleTheme); +/** + * editor_template_src.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + */ + +(function() { + var DOM = tinymce.DOM; + + // Tell it to load theme specific language pack(s) + tinymce.ThemeManager.requireLangPack('simple'); + + tinymce.create('tinymce.themes.SimpleTheme', { + init : function(ed, url) { + var t = this, states = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'InsertUnorderedList', 'InsertOrderedList'], s = ed.settings; + + t.editor = ed; + ed.contentCSS.push(url + "/skins/" + s.skin + "/content.css"); + + ed.onInit.add(function() { + ed.onNodeChange.add(function(ed, cm) { + tinymce.each(states, function(c) { + cm.get(c.toLowerCase()).setActive(ed.queryCommandState(c)); + }); + }); + }); + + DOM.loadCSS((s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : '') || url + "/skins/" + s.skin + "/ui.css"); + }, + + renderUI : function(o) { + var t = this, n = o.targetNode, ic, tb, ed = t.editor, cf = ed.controlManager, sc; + + n = DOM.insertAfter(DOM.create('span', {id : ed.id + '_container', 'class' : 'mceEditor ' + ed.settings.skin + 'SimpleSkin'}), n); + n = sc = DOM.add(n, 'table', {cellPadding : 0, cellSpacing : 0, 'class' : 'mceLayout'}); + n = tb = DOM.add(n, 'tbody'); + + // Create iframe container + n = DOM.add(tb, 'tr'); + n = ic = DOM.add(DOM.add(n, 'td'), 'div', {'class' : 'mceIframeContainer'}); + + // Create toolbar container + n = DOM.add(DOM.add(tb, 'tr', {'class' : 'last'}), 'td', {'class' : 'mceToolbar mceLast', align : 'center'}); + + // Create toolbar + tb = t.toolbar = cf.createToolbar("tools1"); + tb.add(cf.createButton('bold', {title : 'simple.bold_desc', cmd : 'Bold'})); + tb.add(cf.createButton('italic', {title : 'simple.italic_desc', cmd : 'Italic'})); + tb.add(cf.createButton('underline', {title : 'simple.underline_desc', cmd : 'Underline'})); + tb.add(cf.createButton('strikethrough', {title : 'simple.striketrough_desc', cmd : 'Strikethrough'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('undo', {title : 'simple.undo_desc', cmd : 'Undo'})); + tb.add(cf.createButton('redo', {title : 'simple.redo_desc', cmd : 'Redo'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('cleanup', {title : 'simple.cleanup_desc', cmd : 'mceCleanup'})); + tb.add(cf.createSeparator()); + tb.add(cf.createButton('insertunorderedlist', {title : 'simple.bullist_desc', cmd : 'InsertUnorderedList'})); + tb.add(cf.createButton('insertorderedlist', {title : 'simple.numlist_desc', cmd : 'InsertOrderedList'})); + tb.renderTo(n); + + return { + iframeContainer : ic, + editorContainer : ed.id + '_container', + sizeContainer : sc, + deltaHeight : -20 + }; + }, + + getInfo : function() { + return { + longname : 'Simple theme', + author : 'Moxiecode Systems AB', + authorurl : 'http://tinymce.moxiecode.com', + version : tinymce.majorVersion + "." + tinymce.minorVersion + } + } + }); + + tinymce.ThemeManager.add('simple', tinymce.themes.SimpleTheme); })(); \ No newline at end of file diff --git a/js/tiny_mce/themes/simple/img/icons.gif b/js/tiny_mce/themes/simple/img/icons.gif index 16af141f..6fcbcb5d 100644 Binary files a/js/tiny_mce/themes/simple/img/icons.gif and b/js/tiny_mce/themes/simple/img/icons.gif differ diff --git a/js/tiny_mce/themes/simple/langs/en.js b/js/tiny_mce/themes/simple/langs/en.js index 9f08f102..088ed0fc 100644 --- a/js/tiny_mce/themes/simple/langs/en.js +++ b/js/tiny_mce/themes/simple/langs/en.js @@ -1,11 +1 @@ -tinyMCE.addI18n('en.simple',{ -bold_desc:"Bold (Ctrl+B)", -italic_desc:"Italic (Ctrl+I)", -underline_desc:"Underline (Ctrl+U)", -striketrough_desc:"Strikethrough", -bullist_desc:"Unordered list", -numlist_desc:"Ordered list", -undo_desc:"Undo (Ctrl+Z)", -redo_desc:"Redo (Ctrl+Y)", -cleanup_desc:"Cleanup messy code" -}); \ No newline at end of file +tinyMCE.addI18n('en.simple',{"cleanup_desc":"Cleanup Messy Code","redo_desc":"Redo (Ctrl+Y)","undo_desc":"Undo (Ctrl+Z)","numlist_desc":"Insert/Remove Numbered List","bullist_desc":"Insert/Remove Bulleted List","striketrough_desc":"Strikethrough","underline_desc":"Underline (Ctrl+U)","italic_desc":"Italic (Ctrl+I)","bold_desc":"Bold (Ctrl+B)"}); \ No newline at end of file diff --git a/js/tiny_mce/tiny_mce.js b/js/tiny_mce/tiny_mce.js index 1e10bb77..ad3745f8 100644 --- a/js/tiny_mce/tiny_mce.js +++ b/js/tiny_mce/tiny_mce.js @@ -1 +1 @@ -(function(c){var a=/^\s*|\s*$/g,d;var b={majorVersion:"3",minorVersion:"3.2",releaseDate:"2010-03-25",_init:function(){var r=this,o=document,m=navigator,f=m.userAgent,l,e,k,j,h,q;r.isOpera=c.opera&&opera.buildNumber;r.isWebKit=/WebKit/.test(f);r.isIE=!r.isWebKit&&!r.isOpera&&(/MSIE/gi).test(f)&&(/Explorer/gi).test(m.appName);r.isIE6=r.isIE&&/MSIE [56]/.test(f);r.isGecko=!r.isWebKit&&/Gecko/.test(f);r.isMac=f.indexOf("Mac")!=-1;r.isAir=/adobeair/i.test(f);if(c.tinyMCEPreInit){r.suffix=tinyMCEPreInit.suffix;r.baseURL=tinyMCEPreInit.base;r.query=tinyMCEPreInit.query;return}r.suffix="";e=o.getElementsByTagName("base");for(l=0;l=c.length){for(e=0,b=g.length;e=c.length||g[e]!=c[e]){f=e+1;break}}}if(g.length=g.length||g[e]!=c[e]){f=e+1;break}}}if(f==1){return h}for(e=0,b=g.length-(f-1);e=0;c--){if(f[c].length==0||f[c]=="."){continue}if(f[c]==".."){b++;continue}if(b>0){b--;continue}h.push(f[c])}c=e.length-b;if(c<=0){g=h.reverse().join("/")}else{g=e.slice(0,c).join("/")+"/"+h.reverse().join("/")}if(g.indexOf("/")!==0){g="/"+g}if(d&&g.lastIndexOf("/")!==g.length-1){g+=d}return g},getURI:function(d){var c,b=this;if(!b.source||d){c="";if(!d){if(b.protocol){c+=b.protocol+"://"}if(b.userInfo){c+=b.userInfo+"@"}if(b.host){c+=b.host}if(b.port){c+=":"+b.port}}if(b.path){c+=b.path}if(b.query){c+="?"+b.query}if(b.anchor){c+="#"+b.anchor}b.source=c}return b.source}})})();(function(){var a=tinymce.each;tinymce.create("static tinymce.util.Cookie",{getHash:function(d){var b=this.get(d),c;if(b){a(b.split("&"),function(e){e=e.split("=");c=c||{};c[unescape(e[0])]=unescape(e[1])})}return c},setHash:function(j,b,g,f,i,c){var h="";a(b,function(e,d){h+=(!h?"":"&")+escape(d)+"="+escape(e)});this.set(j,h,g,f,i,c)},get:function(i){var h=document.cookie,g,f=i+"=",d;if(!h){return}d=h.indexOf("; "+f);if(d==-1){d=h.indexOf(f);if(d!=0){return null}}else{d+=2}g=h.indexOf(";",d);if(g==-1){g=h.length}return unescape(h.substring(d+f.length,g))},set:function(i,b,g,f,h,c){document.cookie=i+"="+escape(b)+((g)?"; expires="+g.toGMTString():"")+((f)?"; path="+escape(f):"")+((h)?"; domain="+h:"")+((c)?"; secure":"")},remove:function(e,b){var c=new Date();c.setTime(c.getTime()-1000);this.set(e,"",c,b,c)}})})();tinymce.create("static tinymce.util.JSON",{serialize:function(e){var c,a,d=tinymce.util.JSON.serialize,b;if(e==null){return"null"}b=typeof e;if(b=="string"){a="\bb\tt\nn\ff\rr\"\"''\\\\";return'"'+e.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g,function(g,f){c=a.indexOf(f);if(c+1){return"\\"+a.charAt(c+1)}g=f.charCodeAt().toString(16);return"\\u"+"0000".substring(g.length)+g})+'"'}if(b=="object"){if(e.hasOwnProperty&&e instanceof Array){for(c=0,a="[";c0?",":"")+d(e[c])}return a+"]"}a="{";for(c in e){a+=typeof e[c]!="function"?(a.length>1?',"':'"')+c+'":'+d(e[c]):""}return a+"}"}return""+e},parse:function(s){try{return eval("("+s+")")}catch(ex){}}});tinymce.create("static tinymce.util.XHR",{send:function(g){var a,e,b=window,h=0;g.scope=g.scope||this;g.success_scope=g.success_scope||g.scope;g.error_scope=g.error_scope||g.scope;g.async=g.async===false?false:true;g.data=g.data||"";function d(i){a=0;try{a=new ActiveXObject(i)}catch(c){}return a}a=b.XMLHttpRequest?new XMLHttpRequest():d("Microsoft.XMLHTTP")||d("Msxml2.XMLHTTP");if(a){if(a.overrideMimeType){a.overrideMimeType(g.content_type)}a.open(g.type||(g.data?"POST":"GET"),g.url,g.async);if(g.content_type){a.setRequestHeader("Content-Type",g.content_type)}a.setRequestHeader("X-Requested-With","XMLHttpRequest");a.send(g.data);function f(){if(!g.async||a.readyState==4||h++>10000){if(g.success&&h<10000&&a.status==200){g.success.call(g.success_scope,""+a.responseText,a,g)}else{if(g.error){g.error.call(g.error_scope,h>10000?"TIMED_OUT":"GENERAL",a,g)}}a=null}else{b.setTimeout(f,10)}}if(!g.async){return f()}e=b.setTimeout(f,10)}}});(function(){var c=tinymce.extend,b=tinymce.util.JSON,a=tinymce.util.XHR;tinymce.create("tinymce.util.JSONRequest",{JSONRequest:function(d){this.settings=c({},d);this.count=0},send:function(f){var e=f.error,d=f.success;f=c(this.settings,f);f.success=function(h,g){h=b.parse(h);if(typeof(h)=="undefined"){h={error:"JSON Parse error."}}if(h.error){e.call(f.error_scope||f.scope,h.error,g)}else{d.call(f.success_scope||f.scope,h.result)}};f.error=function(h,g){e.call(f.error_scope||f.scope,h,g)};f.data=b.serialize({id:f.id||"c"+(this.count++),method:f.method,params:f.params});f.content_type="application/json";a.send(f)},"static":{sendRPC:function(d){return new tinymce.util.JSONRequest().send(d)}}})}());(function(m){var k=m.each,j=m.is,i=m.isWebKit,d=m.isIE,a=/^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,e=g("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),f=g("src,href,style,coords,shape"),c={"&":"&",'"':""","<":"<",">":">"},n=/[<>&\"]/g,b=/^([a-z0-9],?)+$/i,h=/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,l=/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;function g(q){var p={},o;q=q.split(",");for(o=q.length;o>=0;o--){p[q[o]]=1}return p}m.create("tinymce.dom.DOMUtils",{doc:null,root:null,files:null,pixelStyles:/^(top|left|bottom|right|width|height|borderWidth)$/,props:{"for":"htmlFor","class":"className",className:"className",checked:"checked",disabled:"disabled",maxlength:"maxLength",readonly:"readOnly",selected:"selected",value:"value",id:"id",name:"name",type:"type"},DOMUtils:function(u,q){var p=this,o;p.doc=u;p.win=window;p.files={};p.cssFlicker=false;p.counter=0;p.boxModel=!m.isIE||u.compatMode=="CSS1Compat";p.stdMode=u.documentMode===8;p.settings=q=m.extend({keep_values:false,hex_colors:1,process_html:1},q);if(m.isIE6){try{u.execCommand("BackgroundImageCache",false,true)}catch(r){p.cssFlicker=true}}if(q.valid_styles){p._styles={};k(q.valid_styles,function(t,s){p._styles[s]=m.explode(t)})}m.addUnload(p.destroy,p)},getRoot:function(){var o=this,p=o.settings;return(p&&o.get(p.root_element))||o.doc.body},getViewPort:function(p){var q,o;p=!p?this.win:p;q=p.document;o=this.boxModel?q.documentElement:q.body;return{x:p.pageXOffset||o.scrollLeft,y:p.pageYOffset||o.scrollTop,w:p.innerWidth||o.clientWidth,h:p.innerHeight||o.clientHeight}},getRect:function(s){var r,o=this,q;s=o.get(s);r=o.getPos(s);q=o.getSize(s);return{x:r.x,y:r.y,w:q.w,h:q.h}},getSize:function(r){var p=this,o,q;r=p.get(r);o=p.getStyle(r,"width");q=p.getStyle(r,"height");if(o.indexOf("px")===-1){o=0}if(q.indexOf("px")===-1){q=0}return{w:parseInt(o)||r.offsetWidth||r.clientWidth,h:parseInt(q)||r.offsetHeight||r.clientHeight}},getParent:function(q,p,o){return this.getParents(q,p,o,false)},getParents:function(z,v,s,y){var q=this,p,u=q.settings,x=[];z=q.get(z);y=y===undefined;if(u.strict_root){s=s||q.getRoot()}if(j(v,"string")){p=v;if(v==="*"){v=function(o){return o.nodeType==1}}else{v=function(o){return q.is(o,p)}}}while(z){if(z==s||!z.nodeType||z.nodeType===9){break}if(!v||v(z)){if(y){x.push(z)}else{return z}}z=z.parentNode}return y?x:null},get:function(o){var p;if(o&&this.doc&&typeof(o)=="string"){p=o;o=this.doc.getElementById(o);if(o&&o.id!==p){return this.doc.getElementsByName(p)[1]}}return o},getNext:function(p,o){return this._findSib(p,o,"nextSibling")},getPrev:function(p,o){return this._findSib(p,o,"previousSibling")},select:function(q,p){var o=this;return m.dom.Sizzle(q,o.get(p)||o.get(o.settings.root_element)||o.doc,[])},is:function(q,o){var p;if(q.length===undefined){if(o==="*"){return q.nodeType==1}if(b.test(o)){o=o.toLowerCase().split(/,/);q=q.nodeName.toLowerCase();for(p=o.length-1;p>=0;p--){if(o[p]==q){return true}}return false}}return m.dom.Sizzle.matches(o,q.nodeType?[q]:q).length>0},add:function(s,v,o,r,u){var q=this;return this.run(s,function(y){var x,t;x=j(v,"string")?q.doc.createElement(v):v;q.setAttribs(x,o);if(r){if(r.nodeType){x.appendChild(r)}else{q.setHTML(x,r)}}return !u?y.appendChild(x):x})},create:function(q,o,p){return this.add(this.doc.createElement(q),q,o,p,1)},createHTML:function(v,p,s){var u="",r=this,q;u+="<"+v;for(q in p){if(p.hasOwnProperty(q)){u+=" "+q+'="'+r.encode(p[q])+'"'}}if(m.is(s)){return u+">"+s+""}return u+" />"},remove:function(o,p){return this.run(o,function(r){var q,s;q=r.parentNode;if(!q){return null}if(p){while(s=r.firstChild){if(s.nodeType!==3||s.nodeValue){q.insertBefore(s,r)}else{r.removeChild(s)}}}return q.removeChild(r)})},setStyle:function(r,o,p){var q=this;return q.run(r,function(v){var u,t;u=v.style;o=o.replace(/-(\D)/g,function(x,s){return s.toUpperCase()});if(q.pixelStyles.test(o)&&(m.is(p,"number")||/^[\-0-9\.]+$/.test(p))){p+="px"}switch(o){case"opacity":if(d){u.filter=p===""?"":"alpha(opacity="+(p*100)+")";if(!r.currentStyle||!r.currentStyle.hasLayout){u.display="inline-block"}}u[o]=u["-moz-opacity"]=u["-khtml-opacity"]=p||"";break;case"float":d?u.styleFloat=p:u.cssFloat=p;break;default:u[o]=p||""}if(q.settings.update_styles){q.setAttrib(v,"_mce_style")}})},getStyle:function(r,o,q){r=this.get(r);if(!r){return false}if(this.doc.defaultView&&q){o=o.replace(/[A-Z]/g,function(s){return"-"+s});try{return this.doc.defaultView.getComputedStyle(r,null).getPropertyValue(o)}catch(p){return null}}o=o.replace(/-(\D)/g,function(t,s){return s.toUpperCase()});if(o=="float"){o=d?"styleFloat":"cssFloat"}if(r.currentStyle&&q){return r.currentStyle[o]}return r.style[o]},setStyles:function(u,v){var q=this,r=q.settings,p;p=r.update_styles;r.update_styles=0;k(v,function(o,s){q.setStyle(u,s,o)});r.update_styles=p;if(r.update_styles){q.setAttrib(u,r.cssText)}},setAttrib:function(q,r,o){var p=this;if(!q||!r){return}if(p.settings.strict){r=r.toLowerCase()}return this.run(q,function(u){var t=p.settings;switch(r){case"style":if(!j(o,"string")){k(o,function(s,x){p.setStyle(u,x,s)});return}if(t.keep_values){if(o&&!p._isRes(o)){u.setAttribute("_mce_style",o,2)}else{u.removeAttribute("_mce_style",2)}}u.style.cssText=o;break;case"class":u.className=o||"";break;case"src":case"href":if(t.keep_values){if(t.url_converter){o=t.url_converter.call(t.url_converter_scope||p,o,r,u)}p.setAttrib(u,"_mce_"+r,o,2)}break;case"shape":u.setAttribute("_mce_style",o);break}if(j(o)&&o!==null&&o.length!==0){u.setAttribute(r,""+o,2)}else{u.removeAttribute(r,2)}})},setAttribs:function(q,r){var p=this;return this.run(q,function(o){k(r,function(s,t){p.setAttrib(o,t,s)})})},getAttrib:function(r,s,q){var o,p=this;r=p.get(r);if(!r||r.nodeType!==1){return false}if(!j(q)){q=""}if(/^(src|href|style|coords|shape)$/.test(s)){o=r.getAttribute("_mce_"+s);if(o){return o}}if(d&&p.props[s]){o=r[p.props[s]];o=o&&o.nodeValue?o.nodeValue:o}if(!o){o=r.getAttribute(s,2)}if(/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(s)){if(r[p.props[s]]===true&&o===""){return s}return o?s:""}if(r.nodeName==="FORM"&&r.getAttributeNode(s)){return r.getAttributeNode(s).nodeValue}if(s==="style"){o=o||r.style.cssText;if(o){o=p.serializeStyle(p.parseStyle(o),r.nodeName);if(p.settings.keep_values&&!p._isRes(o)){r.setAttribute("_mce_style",o)}}}if(i&&s==="class"&&o){o=o.replace(/(apple|webkit)\-[a-z\-]+/gi,"")}if(d){switch(s){case"rowspan":case"colspan":if(o===1){o=""}break;case"size":if(o==="+0"||o===20||o===0){o=""}break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":if(o===0){o=""}break;case"hspace":if(o===-1){o=""}break;case"maxlength":case"tabindex":if(o===32768||o===2147483647||o==="32768"){o=""}break;case"multiple":case"compact":case"noshade":case"nowrap":if(o===65535){return s}return q;case"shape":o=o.toLowerCase();break;default:if(s.indexOf("on")===0&&o){o=(""+o).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1")}}}return(o!==undefined&&o!==null&&o!=="")?""+o:q},getPos:function(A,s){var p=this,o=0,z=0,u,v=p.doc,q;A=p.get(A);s=s||v.body;if(A){if(d&&!p.stdMode){A=A.getBoundingClientRect();u=p.boxModel?v.documentElement:v.body;o=p.getStyle(p.select("html")[0],"borderWidth");o=(o=="medium"||p.boxModel&&!p.isIE6)&&2||o;A.top+=p.win.self!=p.win.top?2:0;return{x:A.left+u.scrollLeft-o,y:A.top+u.scrollTop-o}}q=A;while(q&&q!=s&&q.nodeType){o+=q.offsetLeft||0;z+=q.offsetTop||0;q=q.offsetParent}q=A.parentNode;while(q&&q!=s&&q.nodeType){o-=q.scrollLeft||0;z-=q.scrollTop||0;q=q.parentNode}}return{x:o,y:z}},parseStyle:function(r){var u=this,v=u.settings,x={};if(!r){return x}function p(D,A,C){var z,B,o,y;z=x[D+"-top"+A];if(!z){return}B=x[D+"-right"+A];if(z!=B){return}o=x[D+"-bottom"+A];if(B!=o){return}y=x[D+"-left"+A];if(o!=y){return}x[C]=y;delete x[D+"-top"+A];delete x[D+"-right"+A];delete x[D+"-bottom"+A];delete x[D+"-left"+A]}function q(y,s,o,A){var z;z=x[s];if(!z){return}z=x[o];if(!z){return}z=x[A];if(!z){return}x[y]=x[s]+" "+x[o]+" "+x[A];delete x[s];delete x[o];delete x[A]}r=r.replace(/&(#?[a-z0-9]+);/g,"&$1_MCE_SEMI_");k(r.split(";"),function(s){var o,t=[];if(s){s=s.replace(/_MCE_SEMI_/g,";");s=s.replace(/url\([^\)]+\)/g,function(y){t.push(y);return"url("+t.length+")"});s=s.split(":");o=m.trim(s[1]);o=o.replace(/url\(([^\)]+)\)/g,function(z,y){return t[parseInt(y)-1]});o=o.replace(/rgb\([^\)]+\)/g,function(y){return u.toHex(y)});if(v.url_converter){o=o.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g,function(y,z){return"url("+v.url_converter.call(v.url_converter_scope||u,u.decode(z),"style",null)+")"})}x[m.trim(s[0]).toLowerCase()]=o}});p("border","","border");p("border","-width","border-width");p("border","-color","border-color");p("border","-style","border-style");p("padding","","padding");p("margin","","margin");q("border","border-width","border-style","border-color");if(d){if(x.border=="medium none"){x.border=""}}return x},serializeStyle:function(v,p){var q=this,r="";function u(s,o){if(o&&s){if(o.indexOf("-")===0){return}switch(o){case"font-weight":if(s==700){s="bold"}break;case"color":case"background-color":s=s.toLowerCase();break}r+=(r?" ":"")+o+": "+s+";"}}if(p&&q._styles){k(q._styles["*"],function(o){u(v[o],o)});k(q._styles[p.toLowerCase()],function(o){u(v[o],o)})}else{k(v,u)}return r},loadCSS:function(o){var q=this,r=q.doc,p;if(!o){o=""}p=q.select("head")[0];k(o.split(","),function(s){var t;if(q.files[s]){return}q.files[s]=true;t=q.create("link",{rel:"stylesheet",href:m._addVer(s)});if(d&&r.documentMode){t.onload=function(){r.recalc();t.onload=null}}p.appendChild(t)})},addClass:function(o,p){return this.run(o,function(q){var r;if(!p){return 0}if(this.hasClass(q,p)){return q.className}r=this.removeClass(q,p);return q.className=(r!=""?(r+" "):"")+p})},removeClass:function(q,r){var o=this,p;return o.run(q,function(t){var s;if(o.hasClass(t,r)){if(!p){p=new RegExp("(^|\\s+)"+r+"(\\s+|$)","g")}s=t.className.replace(p," ");s=m.trim(s!=" "?s:"");t.className=s;if(!s){t.removeAttribute("class");t.removeAttribute("className")}return s}return t.className})},hasClass:function(p,o){p=this.get(p);if(!p||!o){return false}return(" "+p.className+" ").indexOf(" "+o+" ")!==-1},show:function(o){return this.setStyle(o,"display","block")},hide:function(o){return this.setStyle(o,"display","none")},isHidden:function(o){o=this.get(o);return !o||o.style.display=="none"||this.getStyle(o,"display")=="none"},uniqueId:function(o){return(!o?"mce_":o)+(this.counter++)},setHTML:function(q,p){var o=this;return this.run(q,function(v){var r,t,s,z,u,r;p=o.processHTML(p);if(d){function y(){while(v.firstChild){v.firstChild.removeNode()}try{v.innerHTML="
    "+p;v.removeChild(v.firstChild)}catch(x){r=o.create("div");r.innerHTML="
    "+p;k(r.childNodes,function(B,A){if(A){v.appendChild(B)}})}}if(o.settings.fix_ie_paragraphs){p=p.replace(/

    <\/p>|]+)><\/p>|/gi,' 

    ')}y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("p");for(t=s.length-1,r=0;t>=0;t--){z=s[t];if(!z.hasChildNodes()){if(!z._mce_keep){r=1;break}z.removeAttribute("_mce_keep")}}}if(r){p=p.replace(/

    ]+)>|

    /ig,'

    ');p=p.replace(/<\/p>/g,"
    ");y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("DIV");for(t=s.length-1;t>=0;t--){z=s[t];if(z._mce_tmp){u=o.doc.createElement("p");z.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi,function(A,x){var B;if(x!=="_mce_tmp"){B=z.getAttribute(x);if(!B&&x==="class"){B=z.className}u.setAttribute(x,B)}});for(r=0;r]+)\/>|/gi,"");if(q.keep_values){if(/)/g,"\n");t=t.replace(/^[\r\n]*|[\r\n]*$/g,"");t=t.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g,"");return t}r=r.replace(/]+|)>([\s\S]*?)<\/script>/gi,function(s,x,t){if(!x){x=' type="text/javascript"'}x=x.replace(/src=\"([^\"]+)\"?/i,function(y,z){if(q.url_converter){z=p.encode(q.url_converter.call(q.url_converter_scope||p,p.decode(z),"src","script"))}return'_mce_src="'+z+'"'});if(m.trim(t)){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/style>/gi,function(s,x,t){if(t){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(s,x,t){return""})}r=r.replace(//g,"");function u(s){return s.replace(h,function(y,z,x,t){return"<"+z+x.replace(l,function(B,A,E,D,C){var F;A=A.toLowerCase();E=E||D||C||"";if(e[A]){if(E==="false"||E==="0"){return}return A+'="'+A+'"'}if(f[A]&&x.indexOf("_mce_"+A)==-1){F=p.decode(E);if(q.url_converter&&(A=="src"||A=="href")){F=q.url_converter.call(q.url_converter_scope||p,F,A,z)}if(A=="style"){F=p.serializeStyle(p.parseStyle(F),A)}return A+'="'+E+'" _mce_'+A+'="'+p.encode(F)+'"'}return B})+t+">"})}r=u(r);r=r.replace(/MCE_SCRIPT:([0-9]+)/g,function(t,s){return v[s]})}return r},getOuterHTML:function(o){var p;o=this.get(o);if(!o){return null}if(o.outerHTML!==undefined){return o.outerHTML}p=(o.ownerDocument||this.doc).createElement("body");p.appendChild(o.cloneNode(true));return p.innerHTML},setOuterHTML:function(r,p,s){var o=this;function q(u,t,x){var y,v;v=x.createElement("body");v.innerHTML=t;y=v.lastChild;while(y){o.insertAfter(y.cloneNode(true),u);y=y.previousSibling}o.remove(u)}return this.run(r,function(u){u=o.get(u);if(u.nodeType==1){s=s||u.ownerDocument||o.doc;if(d){try{if(d&&u.nodeType==1){u.outerHTML=p}else{q(u,p,s)}}catch(t){q(u,p,s)}}else{q(u,p,s)}}})},decode:function(p){var q,r,o;if(/&[\w#]+;/.test(p)){q=this.doc.createElement("div");q.innerHTML=p;r=q.firstChild;o="";if(r){do{o+=r.nodeValue}while(r=r.nextSibling)}return o||p}return p},encode:function(o){return(""+o).replace(n,function(p){return c[p]})},insertAfter:function(o,p){p=this.get(p);return this.run(o,function(r){var q,s;q=p.parentNode;s=p.nextSibling;if(s){q.insertBefore(r,s)}else{q.appendChild(r)}return r})},isBlock:function(o){if(o.nodeType&&o.nodeType!==1){return false}o=o.nodeName||o;return a.test(o)},replace:function(s,r,p){var q=this;if(j(r,"array")){s=s.cloneNode(true)}return q.run(r,function(t){if(p){k(m.grep(t.childNodes),function(o){s.appendChild(o)})}return t.parentNode.replaceChild(s,t)})},rename:function(r,o){var q=this,p;if(r.nodeName!=o.toUpperCase()){p=q.create(o);k(q.getAttribs(r),function(s){q.setAttrib(p,s.nodeName,q.getAttrib(r,s.nodeName))});q.replace(p,r,1)}return p||r},findCommonAncestor:function(q,o){var r=q,p;while(r){p=o;while(p&&r!=p){p=p.parentNode}if(r==p){break}r=r.parentNode}if(!r&&q.ownerDocument){return q.ownerDocument.documentElement}return r},toHex:function(o){var q=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(o);function p(r){r=parseInt(r).toString(16);return r.length>1?r:"0"+r}if(q){o="#"+p(q[1])+p(q[2])+p(q[3]);return o}return o},getClasses:function(){var s=this,o=[],r,u={},v=s.settings.class_filter,q;if(s.classes){return s.classes}function x(t){k(t.imports,function(y){x(y)});k(t.cssRules||t.rules,function(y){switch(y.type||1){case 1:if(y.selectorText){k(y.selectorText.split(","),function(z){z=z.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(z)||!/\.[\w\-]+$/.test(z)){return}q=z;z=z.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(v&&!(z=v(z,q))){return}if(!u[z]){o.push({"class":z});u[z]=1}})}break;case 3:x(y.styleSheet);break}})}try{k(s.doc.styleSheets,x)}catch(p){}if(o.length>0){s.classes=o}return o},run:function(u,r,q){var p=this,v;if(p.doc&&typeof(u)==="string"){u=p.get(u)}if(!u){return false}q=q||this;if(!u.nodeType&&(u.length||u.length===0)){v=[];k(u,function(s,o){if(s){if(typeof(s)=="string"){s=p.doc.getElementById(s)}v.push(r.call(q,s,o))}});return v}return r.call(q,u)},getAttribs:function(q){var p;q=this.get(q);if(!q){return[]}if(d){p=[];if(q.nodeName=="OBJECT"){return q.attributes}if(q.nodeName==="OPTION"&&this.getAttrib(q,"selected")){p.push({specified:1,nodeName:"selected"})}q.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi,"").replace(/[\w:\-]+/gi,function(o){p.push({specified:1,nodeName:o})});return p}return q.attributes},destroy:function(p){var o=this;if(o.events){o.events.destroy()}o.win=o.doc=o.root=o.events=null;if(!p){m.removeUnload(o.destroy)}},createRng:function(){var o=this.doc;return o.createRange?o.createRange():new m.dom.Range(this)},nodeIndex:function(s,t){var o=0,q,r,p;if(s){for(q=s.nodeType,s=s.previousSibling,r=s;s;s=s.previousSibling){p=s.nodeType;if(!t||p!=3||(q!=p&&s.nodeValue.length)){o++}q=p}}return o},split:function(u,s,y){var z=this,o=z.createRng(),v,q,x;function p(A){var t,r=A.childNodes;if(A.nodeType==1&&A.getAttribute("_mce_type")=="bookmark"){return}for(t=r.length-1;t>=0;t--){p(r[t])}if(A.nodeType!=9){if(A.nodeType==3&&A.nodeValue.length>0){return}if(A.nodeType==1){r=A.childNodes;if(r.length==1&&r[0]&&r[0].nodeType==1&&r[0].getAttribute("_mce_type")=="bookmark"){A.parentNode.insertBefore(r[0],A)}if(r.length||/^(br|hr|input|img)$/i.test(A.nodeName)){return}}z.remove(A)}return A}if(u&&s){o.setStart(u.parentNode,z.nodeIndex(u));o.setEnd(s.parentNode,z.nodeIndex(s));v=o.extractContents();o=z.createRng();o.setStart(s.parentNode,z.nodeIndex(s)+1);o.setEnd(u.parentNode,z.nodeIndex(u)+1);q=o.extractContents();x=u.parentNode;x.insertBefore(p(v),u);if(y){x.replaceChild(y,s)}else{x.insertBefore(s,u)}x.insertBefore(p(q),u);z.remove(u);return y||s}},bind:function(s,o,r,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.add(s,o,r,q||this)},unbind:function(r,o,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.remove(r,o,q)},_findSib:function(r,o,p){var q=this,s=o;if(r){if(j(s,"string")){s=function(t){return q.is(t,o)}}for(r=r[p];r;r=r[p]){if(s(r)){return r}}}return null},_isRes:function(o){return/^(top|left|bottom|right|width|height)/i.test(o)||/;\s*(top|left|bottom|right|width|height)/i.test(o)}});m.DOM=new m.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(a){function b(c){var N=this,e=c.doc,S=0,E=1,j=2,D=true,R=false,U="startOffset",h="startContainer",P="endContainer",z="endOffset",k=tinymce.extend,n=c.nodeIndex;k(N,{startContainer:e,startOffset:0,endContainer:e,endOffset:0,collapsed:D,commonAncestorContainer:e,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:q,setEnd:s,setStartBefore:g,setStartAfter:I,setEndBefore:J,setEndAfter:u,collapse:A,selectNode:x,selectNodeContents:F,compareBoundaryPoints:v,deleteContents:p,extractContents:H,cloneContents:d,insertNode:C,surroundContents:M,cloneRange:K});function q(V,t){B(D,V,t)}function s(V,t){B(R,V,t)}function g(t){q(t.parentNode,n(t))}function I(t){q(t.parentNode,n(t)+1)}function J(t){s(t.parentNode,n(t))}function u(t){s(t.parentNode,n(t)+1)}function A(t){if(t){N[P]=N[h];N[z]=N[U]}else{N[h]=N[P];N[U]=N[z]}N.collapsed=D}function x(t){g(t);u(t)}function F(t){q(t,0);s(t,t.nodeType===1?t.childNodes.length:t.nodeValue.length)}function v(W,X){var Z=N[h],Y=N[U],V=N[P],t=N[z];if(W===0){return G(Z,Y,Z,Y)}if(W===1){return G(Z,Y,V,t)}if(W===2){return G(V,t,V,t)}if(W===3){return G(V,t,Z,Y)}}function p(){m(j)}function H(){return m(S)}function d(){return m(E)}function C(Y){var V=this[h],t=this[U],X,W;if((V.nodeType===3||V.nodeType===4)&&V.nodeValue){if(!t){V.parentNode.insertBefore(Y,V)}else{if(t>=V.nodeValue.length){c.insertAfter(Y,V)}else{X=V.splitText(t);V.parentNode.insertBefore(Y,X)}}}else{if(V.childNodes.length>0){W=V.childNodes[t]}if(W){V.insertBefore(Y,W)}else{V.appendChild(Y)}}}function M(V){var t=N.extractContents();N.insertNode(V);V.appendChild(t);N.selectNode(V)}function K(){return k(new b(c),{startContainer:N[h],startOffset:N[U],endContainer:N[P],endOffset:N[z],collapsed:N.collapsed,commonAncestorContainer:N.commonAncestorContainer})}function O(t,V){var W;if(t.nodeType==3){return t}if(V<0){return t}W=t.firstChild;while(W&&V>0){--V;W=W.nextSibling}if(W){return W}return t}function l(){return(N[h]==N[P]&&N[U]==N[z])}function G(X,Z,V,Y){var aa,W,t,ab,ad,ac;if(X==V){if(Z==Y){return 0}if(Z0){N.collapse(V)}}else{N.collapse(V)}N.collapsed=l();N.commonAncestorContainer=c.findCommonAncestor(N[h],N[P])}function m(ab){var aa,X=0,ad=0,V,Z,W,Y,t,ac;if(N[h]==N[P]){return f(ab)}for(aa=N[P],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[h]){return r(aa,ab)}++X}for(aa=N[h],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[P]){return T(aa,ab)}++ad}Z=ad-X;W=N[h];while(Z>0){W=W.parentNode;Z--}Y=N[P];while(Z<0){Y=Y.parentNode;Z++}for(t=W.parentNode,ac=Y.parentNode;t!=ac;t=t.parentNode,ac=ac.parentNode){W=t;Y=ac}return o(W,Y,ab)}function f(Z){var ab,Y,X,aa,t,W,V;if(Z!=j){ab=e.createDocumentFragment()}if(N[U]==N[z]){return ab}if(N[h].nodeType==3){Y=N[h].nodeValue;X=Y.substring(N[U],N[z]);if(Z!=E){N[h].deleteData(N[U],N[z]-N[U]);N.collapse(D)}if(Z==j){return}ab.appendChild(e.createTextNode(X));return ab}aa=O(N[h],N[U]);t=N[z]-N[U];while(t>0){W=aa.nextSibling;V=y(aa,Z);if(ab){ab.appendChild(V)}--t;aa=W}if(Z!=E){N.collapse(D)}return ab}function r(ab,Y){var aa,Z,V,t,X,W;if(Y!=j){aa=e.createDocumentFragment()}Z=i(ab,Y);if(aa){aa.appendChild(Z)}V=n(ab);t=V-N[U];if(t<=0){if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}Z=ab.previousSibling;while(t>0){X=Z.previousSibling;W=y(Z,Y);if(aa){aa.insertBefore(W,aa.firstChild)}--t;Z=X}if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}function T(Z,Y){var ab,V,aa,t,X,W;if(Y!=j){ab=e.createDocumentFragment()}aa=Q(Z,Y);if(ab){ab.appendChild(aa)}V=n(Z);++V;t=N[z]-V;aa=Z.nextSibling;while(t>0){X=aa.nextSibling;W=y(aa,Y);if(ab){ab.appendChild(W)}--t;aa=X}if(Y!=E){N.setStartAfter(Z);N.collapse(D)}return ab}function o(Z,t,ac){var W,ae,Y,aa,ab,V,ad,X;if(ac!=j){ae=e.createDocumentFragment()}W=Q(Z,ac);if(ae){ae.appendChild(W)}Y=Z.parentNode;aa=n(Z);ab=n(t);++aa;V=ab-aa;ad=Z.nextSibling;while(V>0){X=ad.nextSibling;W=y(ad,ac);if(ae){ae.appendChild(W)}ad=X;--V}W=i(t,ac);if(ae){ae.appendChild(W)}if(ac!=E){N.setStartAfter(Z);N.collapse(D)}return ae}function i(aa,ab){var W=O(N[P],N[z]-1),ac,Z,Y,t,V,X=W!=N[P];if(W==aa){return L(W,X,R,ab)}ac=W.parentNode;Z=L(ac,R,R,ab);while(ac){while(W){Y=W.previousSibling;t=L(W,X,R,ab);if(ab!=j){Z.insertBefore(t,Z.firstChild)}X=D;W=Y}if(ac==aa){return Z}W=ac.previousSibling;ac=ac.parentNode;V=L(ac,R,R,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function Q(aa,ab){var X=O(N[h],N[U]),Y=X!=N[h],ac,Z,W,t,V;if(X==aa){return L(X,Y,D,ab)}ac=X.parentNode;Z=L(ac,R,D,ab);while(ac){while(X){W=X.nextSibling;t=L(X,Y,D,ab);if(ab!=j){Z.appendChild(t)}Y=D;X=W}if(ac==aa){return Z}X=ac.nextSibling;ac=ac.parentNode;V=L(ac,R,D,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function L(t,Y,ab,ac){var X,W,Z,V,aa;if(Y){return y(t,ac)}if(t.nodeType==3){X=t.nodeValue;if(ab){V=N[U];W=X.substring(V);Z=X.substring(0,V)}else{V=N[z];W=X.substring(0,V);Z=X.substring(V)}if(ac!=E){t.nodeValue=Z}if(ac==j){return}aa=t.cloneNode(R);aa.nodeValue=W;return aa}if(ac==j){return}return t.cloneNode(R)}function y(V,t){if(t!=j){return t==E?V.cloneNode(D):V}V.parentNode.removeChild(V)}}a.Range=b})(tinymce.dom);(function(){function a(h){var j=this,k="\uFEFF",f,i,e=h.dom,d=true,g=false;function c(m,l){if(m&&l){if(m.item&&l.item&&m.item(0)===l.item(0)){return d}if(m.isEqual&&l.isEqual&&l.isEqual(m)){try{f.startContainer.nextSibling;return d}catch(n){}}}return g}function b(){var p=h.getRng(),l=e.createRng(),m,n,r,q;n=p.item?p.item(0):p.parentElement();if(n.ownerDocument!=e.doc){return l}if(p.item||!n.hasChildNodes()){l.setStart(n.parentNode,e.nodeIndex(n));l.setEnd(l.startContainer,l.startOffset+1);return l}m=p.duplicate();r=h.isCollapsed();p.collapse();p.pasteHTML('");if(!r){m.collapse(g);m.pasteHTML('")}function o(x){var t,v,s,u;s=e.get("_mce_"+(x?"start":"end"));u=s.previousSibling;if(u&&u.nodeType==3){t=u;v=t.nodeValue.length;e.remove(s);u=t.nextSibling;if(u&&u.nodeType==3){q=d;t.appendData(u.nodeValue);e.remove(u)}}else{u=s.nextSibling;if(u&&u.nodeType==3){t=u;v=0}else{if(u){v=e.nodeIndex(u)-1}else{v=e.nodeIndex(s)}t=s.parentNode}e.remove(s)}if(x){l.setStart(t,v)}if(!x||r){l.setEnd(t,v)}}o(d);if(!r){o(g)}if(q){j.addRange(l)}return l}this.addRange=function(m){var u,A,z=h.dom.doc,s=z.body,v,o,y,p,t,l,q,r,x,n;this.destroy();y=m.startContainer;p=m.startOffset;t=m.endContainer;l=m.endOffset;u=s.createTextRange();if(y==z||t==z){u=s.createTextRange();u.collapse();u.select();return}if(y.nodeType==1&&y.hasChildNodes()){r=y.childNodes.length-1;if(p>r){x=1;y=y.childNodes[r]}else{y=y.childNodes[p]}if(y.nodeType==3){p=0}}if(t.nodeType==1&&t.hasChildNodes()){r=t.childNodes.length-1;if(l==0){n=1;t=t.childNodes[0]}else{t=t.childNodes[Math.min(r,l-1)];if(t.nodeType==3){l=t.nodeValue.length}}}if(y==t&&y.nodeType==1){if(/^(IMG|TABLE)$/.test(y.nodeName)&&p!=l){u=s.createControlRange();u.addElement(y)}else{u=s.createTextRange();if(!y.hasChildNodes()&&y.canHaveHTML){y.innerHTML=k}u.moveToElementText(y);if(y.innerHTML==k){u.collapse(d);y.removeChild(y.firstChild)}}if(p==l){u.collapse(l<=m.endContainer.childNodes.length-1)}u.select();u.scrollIntoView();return}u=s.createTextRange();q=z.createElement("span");q.innerHTML=" ";if(y.nodeType==3){if(x){e.insertAfter(q,y)}else{y.parentNode.insertBefore(q,y)}u.moveToElementText(q);q.parentNode.removeChild(q);u.move("character",p)}else{u.moveToElementText(y);if(x){u.collapse(g)}}if(y==t&&y.nodeType==3){u.moveEnd("character",l-p);u.select();u.scrollIntoView();return}A=s.createTextRange();if(t.nodeType==3){t.parentNode.insertBefore(q,t);A.moveToElementText(q);q.parentNode.removeChild(q);A.move("character",l);u.setEndPoint("EndToStart",A)}else{A.moveToElementText(t);A.collapse(!!n);u.setEndPoint("EndToEnd",A)}u.select();u.scrollIntoView()};this.getRangeAt=function(){if(!f||!c(i,h.getRng())){f=b();i=h.getRng()}return f};this.destroy=function(){i=f=null};if(h.dom.boxModel){(function(){var r=e.doc,m=r.body,o,p;r.documentElement.unselectable=d;function q(s,v){var t=m.createTextRange();try{t.moveToPoint(s,v)}catch(u){t=null}return t}function n(t){var s;if(t.button){s=q(t.x,t.y);if(s){if(s.compareEndPoints("StartToStart",p)>0){s.setEndPoint("StartToStart",p)}else{s.setEndPoint("EndToEnd",p)}s.select()}}else{l()}}function l(){e.unbind(r,"mouseup",l);e.unbind(r,"mousemove",n);o=0}e.bind(r,"mousedown",function(s){if(s.target.nodeName==="HTML"){if(o){l()}o=1;p=q(s.x,s.y);if(p){e.bind(r,"mouseup",l);e.bind(r,"mousemove",n);p.select()}}})})()}}tinymce.dom.TridentSelection=a})();(function(){var p=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,i=0,d=Object.prototype.toString,n=false;var b=function(E,t,B,v){B=B||[];var e=t=t||document;if(t.nodeType!==1&&t.nodeType!==9){return[]}if(!E||typeof E!=="string"){return B}var C=[],D,z,H,G,A,s,r=true,x=o(t);p.lastIndex=0;while((D=p.exec(E))!==null){C.push(D[1]);if(D[2]){s=RegExp.rightContext;break}}if(C.length>1&&j.exec(E)){if(C.length===2&&f.relative[C[0]]){z=g(C[0]+C[1],t)}else{z=f.relative[C[0]]?[t]:b(C.shift(),t);while(C.length){E=C.shift();if(f.relative[E]){E+=C.shift()}z=g(E,z)}}}else{if(!v&&C.length>1&&t.nodeType===9&&!x&&f.match.ID.test(C[0])&&!f.match.ID.test(C[C.length-1])){var I=b.find(C.shift(),t,x);t=I.expr?b.filter(I.expr,I.set)[0]:I.set[0]}if(t){var I=v?{expr:C.pop(),set:a(v)}:b.find(C.pop(),C.length===1&&(C[0]==="~"||C[0]==="+")&&t.parentNode?t.parentNode:t,x);z=I.expr?b.filter(I.expr,I.set):I.set;if(C.length>0){H=a(z)}else{r=false}while(C.length){var u=C.pop(),y=u;if(!f.relative[u]){u=""}else{y=C.pop()}if(y==null){y=t}f.relative[u](H,y,x)}}else{H=C=[]}}if(!H){H=z}if(!H){throw"Syntax error, unrecognized expression: "+(u||E)}if(d.call(H)==="[object Array]"){if(!r){B.push.apply(B,H)}else{if(t&&t.nodeType===1){for(var F=0;H[F]!=null;F++){if(H[F]&&(H[F]===true||H[F].nodeType===1&&h(t,H[F]))){B.push(z[F])}}}else{for(var F=0;H[F]!=null;F++){if(H[F]&&H[F].nodeType===1){B.push(z[F])}}}}}else{a(H,B)}if(s){b(s,e,B,v);b.uniqueSort(B)}return B};b.uniqueSort=function(r){if(c){n=false;r.sort(c);if(n){for(var e=1;e":function(x,r,y){var u=typeof r==="string";if(u&&!/\W/.test(r)){r=y?r:r.toUpperCase();for(var s=0,e=x.length;s=0)){if(!s){e.push(v)}}else{if(s){r[u]=false}}}}return false},ID:function(e){return e[1].replace(/\\/g,"")},TAG:function(r,e){for(var s=0;e[s]===false;s++){}return e[s]&&o(e[s])?r[1]:r[1].toUpperCase()},CHILD:function(e){if(e[1]=="nth"){var r=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(e[2]=="even"&&"2n"||e[2]=="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(r[1]+(r[2]||1))-0;e[3]=r[3]-0}e[0]=i++;return e},ATTR:function(u,r,s,e,v,x){var t=u[1].replace(/\\/g,"");if(!x&&f.attrMap[t]){u[1]=f.attrMap[t]}if(u[2]==="~="){u[4]=" "+u[4]+" "}return u},PSEUDO:function(u,r,s,e,v){if(u[1]==="not"){if(u[3].match(p).length>1||/^\w/.test(u[3])){u[3]=b(u[3],null,null,r)}else{var t=b.filter(u[3],r,s,true^v);if(!s){e.push.apply(e,t)}return false}}else{if(f.match.POS.test(u[0])||f.match.CHILD.test(u[0])){return true}}return u},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){e.parentNode.selectedIndex;return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(s,r,e){return !!b(e[3],s).length},header:function(e){return/h\d/i.test(e.nodeName)},text:function(e){return"text"===e.type},radio:function(e){return"radio"===e.type},checkbox:function(e){return"checkbox"===e.type},file:function(e){return"file"===e.type},password:function(e){return"password"===e.type},submit:function(e){return"submit"===e.type},image:function(e){return"image"===e.type},reset:function(e){return"reset"===e.type},button:function(e){return"button"===e.type||e.nodeName.toUpperCase()==="BUTTON"},input:function(e){return/input|select|textarea|button/i.test(e.nodeName)}},setFilters:{first:function(r,e){return e===0},last:function(s,r,e,t){return r===t.length-1},even:function(r,e){return e%2===0},odd:function(r,e){return e%2===1},lt:function(s,r,e){return re[3]-0},nth:function(s,r,e){return e[3]-0==r},eq:function(s,r,e){return e[3]-0==r}},filter:{PSEUDO:function(x,s,t,y){var r=s[1],u=f.filters[r];if(u){return u(x,t,s,y)}else{if(r==="contains"){return(x.textContent||x.innerText||"").indexOf(s[3])>=0}else{if(r==="not"){var v=s[3];for(var t=0,e=v.length;t=0)}}},ID:function(r,e){return r.nodeType===1&&r.getAttribute("id")===e},TAG:function(r,e){return(e==="*"&&r.nodeType===1)||r.nodeName===e},CLASS:function(r,e){return(" "+(r.className||r.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(v,t){var s=t[1],e=f.attrHandle[s]?f.attrHandle[s](v):v[s]!=null?v[s]:v.getAttribute(s),x=e+"",u=t[2],r=t[4];return e==null?u==="!=":u==="="?x===r:u==="*="?x.indexOf(r)>=0:u==="~="?(" "+x+" ").indexOf(r)>=0:!r?x&&e!==false:u==="!="?x!=r:u==="^="?x.indexOf(r)===0:u==="$="?x.substr(x.length-r.length)===r:u==="|="?x===r||x.substr(0,r.length+1)===r+"-":false},POS:function(u,r,s,v){var e=r[2],t=f.setFilters[e];if(t){return t(u,s,r,v)}}}};var j=f.match.POS;for(var l in f.match){f.match[l]=new RegExp(f.match[l].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var a=function(r,e){r=Array.prototype.slice.call(r);if(e){e.push.apply(e,r);return e}return r};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(k){a=function(u,t){var r=t||[];if(d.call(u)==="[object Array]"){Array.prototype.push.apply(r,u)}else{if(typeof u.length==="number"){for(var s=0,e=u.length;s";var e=document.documentElement;e.insertBefore(r,e.firstChild);if(!!document.getElementById(s)){f.find.ID=function(u,v,x){if(typeof v.getElementById!=="undefined"&&!x){var t=v.getElementById(u[1]);return t?t.id===u[1]||typeof t.getAttributeNode!=="undefined"&&t.getAttributeNode("id").nodeValue===u[1]?[t]:undefined:[]}};f.filter.ID=function(v,t){var u=typeof v.getAttributeNode!=="undefined"&&v.getAttributeNode("id");return v.nodeType===1&&u&&u.nodeValue===t}}e.removeChild(r)})();(function(){var e=document.createElement("div");e.appendChild(document.createComment(""));if(e.getElementsByTagName("*").length>0){f.find.TAG=function(r,v){var u=v.getElementsByTagName(r[1]);if(r[1]==="*"){var t=[];for(var s=0;u[s];s++){if(u[s].nodeType===1){t.push(u[s])}}u=t}return u}}e.innerHTML="";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){f.attrHandle.href=function(r){return r.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var e=b,s=document.createElement("div");s.innerHTML="

    ";if(s.querySelectorAll&&s.querySelectorAll(".TEST").length===0){return}b=function(x,v,t,u){v=v||document;if(!u&&v.nodeType===9&&!o(v)){try{return a(v.querySelectorAll(x),t)}catch(y){}}return e(x,v,t,u)};for(var r in e){b[r]=e[r]}})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var e=document.createElement("div");e.innerHTML="
    ";if(e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}f.order.splice(1,0,"CLASS");f.find.CLASS=function(r,s,t){if(typeof s.getElementsByClassName!=="undefined"&&!t){return s.getElementsByClassName(r[1])}}})()}function m(r,x,v,B,y,A){var z=r=="previousSibling"&&!A;for(var t=0,s=B.length;t0){u=e;break}}}e=e[r]}B[t]=u}}}var h=document.compareDocumentPosition?function(r,e){return r.compareDocumentPosition(e)&16}:function(r,e){return r!==e&&(r.contains?r.contains(e):true)};var o=function(e){return e.nodeType===9&&e.documentElement.nodeName!=="HTML"||!!e.ownerDocument&&e.ownerDocument.documentElement.nodeName!=="HTML"};var g=function(e,y){var t=[],u="",v,s=y.nodeType?[y]:y;while((v=f.match.PSEUDO.exec(e))){u+=v[0];e=e.replace(f.match.PSEUDO,"")}e=f.relative[e]?e+"*":e;for(var x=0,r=s.length;x=0;h--){k=g[h];if(k.obj===l){j._remove(k.obj,k.name,k.cfunc);k.obj=k.cfunc=null;g.splice(h,1)}}}},cancel:function(g){if(!g){return false}this.stop(g);return this.prevent(g)},stop:function(g){if(g.stopPropagation){g.stopPropagation()}else{g.cancelBubble=true}return false},prevent:function(g){if(g.preventDefault){g.preventDefault()}else{g.returnValue=false}return false},destroy:function(){var g=this;f(g.events,function(j,h){g._remove(j.obj,j.name,j.cfunc);j.obj=j.cfunc=null});g.events=[];g=null},_add:function(h,i,g){if(h.attachEvent){h.attachEvent("on"+i,g)}else{if(h.addEventListener){h.addEventListener(i,g,false)}else{h["on"+i]=g}}},_remove:function(i,j,h){if(i){try{if(i.detachEvent){i.detachEvent("on"+j,h)}else{if(i.removeEventListener){i.removeEventListener(j,h,false)}else{i["on"+j]=null}}}catch(g){}}},_pageInit:function(h){var g=this;if(g.domLoaded){return}g.domLoaded=true;f(g.inits,function(i){i()});g.inits=[]},_wait:function(i){var g=this,h=i.document;if(i.tinyMCE_GZ&&tinyMCE_GZ.loaded){g.domLoaded=1;return}if(h.attachEvent){h.attachEvent("onreadystatechange",function(){if(h.readyState==="complete"){h.detachEvent("onreadystatechange",arguments.callee);g._pageInit(i)}});if(h.documentElement.doScroll&&i==i.top){(function(){if(g.domLoaded){return}try{h.documentElement.doScroll("left")}catch(j){setTimeout(arguments.callee,0);return}g._pageInit(i)})()}}else{if(h.addEventListener){g._add(i,"DOMContentLoaded",function(){g._pageInit(i)})}}g._add(i,"load",function(){g._pageInit(i)})},_stoppers:{preventDefault:function(){this.returnValue=false},stopPropagation:function(){this.cancelBubble=true}}});a=d.dom.Event=new d.dom.EventUtils();a._wait(window);d.addUnload(function(){a.destroy()})})(tinymce);(function(a){a.dom.Element=function(f,d){var b=this,e,c;b.settings=d=d||{};b.id=f;b.dom=e=d.dom||a.DOM;if(!a.isIE){c=e.get(b.id)}a.each(("getPos,getRect,getParent,add,setStyle,getStyle,setStyles,setAttrib,setAttribs,getAttrib,addClass,removeClass,hasClass,getOuterHTML,setOuterHTML,remove,show,hide,isHidden,setHTML,get").split(/,/),function(g){b[g]=function(){var h=[f],j;for(j=0;j_';if(j.startContainer==k&&j.endContainer==k){k.body.innerHTML=i}else{j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i))}l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndBefore(l);f.setRng(j);f.dom.remove("__caret")}else{if(j.item){k.execCommand("Delete",false,null);j=f.getRng()}j.pasteHTML(i)}f.onSetContent.dispatch(f,g)},getStart:function(){var f=this,g=f.getRng(),h;if(g.duplicate||g.item){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild||h}return h}else{h=g.startContainer;if(h.nodeType==1&&h.hasChildNodes()){h=h.childNodes[Math.min(h.childNodes.length-1,g.startOffset)]}if(h&&h.nodeType==3){return h.parentNode}return h}},getEnd:function(){var g=this,h=g.getRng(),i,f;if(h.duplicate||h.item){if(h.item){return h.item(0)}h=h.duplicate();h.collapse(0);i=h.parentElement();if(i&&i.nodeName=="BODY"){return i.lastChild||i}return i}else{i=h.endContainer;f=h.endOffset;if(i.nodeType==1&&i.hasChildNodes()){i=i.childNodes[f>0?f-1:f]}if(i&&i.nodeType==3){return i.parentNode}return i}},getBookmark:function(q,r){var u=this,m=u.dom,g,j,i,n,h,o,p,l="\uFEFF",s;function f(v,x){var t=0;d(m.select(v),function(z,y){if(z==x){t=y}});return t}if(q==2){function k(){var v=u.getRng(true),t=m.getRoot(),x={};function y(B,G){var A=B[G?"startContainer":"endContainer"],F=B[G?"startOffset":"endOffset"],z=[],C,E,D=0;if(A.nodeType==3){if(r){for(C=A.previousSibling;C&&C.nodeType==3;C=C.previousSibling){F+=C.nodeValue.length}}z.push(F)}else{E=A.childNodes;if(F>=E.length){D=1;F=E.length-1}z.push(u.dom.nodeIndex(E[F],r)+D)}for(;A&&A!=t;A=A.parentNode){z.push(u.dom.nodeIndex(A,r))}return z}x.start=y(v,true);if(!u.isCollapsed()){x.end=y(v)}return x}return k()}if(q){return{rng:u.getRng()}}g=u.getRng();i=m.uniqueId();n=tinyMCE.activeEditor.selection.isCollapsed();s="overflow:hidden;line-height:0px";if(g.duplicate||g.item){if(!g.item){j=g.duplicate();g.collapse();g.pasteHTML(''+l+"");if(!n){j.collapse(false);j.pasteHTML(''+l+"")}}else{o=g.item(0);h=o.nodeName;return{name:h,index:f(h,o)}}}else{o=u.getNode();h=o.nodeName;if(h=="IMG"){return{name:h,index:f(h,o)}}j=g.cloneRange();if(!n){j.collapse(false);j.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_end",style:s},l))}g.collapse(true);g.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_start",style:s},l))}u.moveToBookmark({id:i,keep:1});return{id:i}},moveToBookmark:function(l){var n=this,k=n.dom,i,h,f,m;if(n.tridentSel){n.tridentSel.destroy()}if(l){if(l.start){f=k.createRng();m=k.getRoot();function g(s){var o=l[s?"start":"end"],p,q,r;if(o){for(q=m,p=o.length-1;p>=1;p--){q=q.childNodes[o[p]]}if(s){f.setStart(q,o[0])}else{f.setEnd(q,o[0])}}}g(true);g();n.setRng(f)}else{if(l.id){f=k.createRng();function j(u){var p=k.get(l.id+"_"+u),t,o,r,s,q=l.keep;if(p){t=p.parentNode;if(u=="start"){if(!q){o=k.nodeIndex(p)}else{t=p;o=1}f.setStart(t,o);f.setEnd(t,o)}else{if(!q){o=k.nodeIndex(p)}else{t=p;o=1}f.setEnd(t,o)}if(!q){s=p.previousSibling;r=p.nextSibling;d(c.grep(p.childNodes),function(v){if(v.nodeType==3){v.nodeValue=v.nodeValue.replace(/\uFEFF/g,"")}});while(p=k.get(l.id+"_"+u)){k.remove(p,1)}if(s&&r&&s.nodeType==r.nodeType&&s.nodeType==3){o=s.nodeValue.length;s.appendData(r.nodeValue);k.remove(r);if(u=="start"){f.setStart(s,o);f.setEnd(s,o)}else{f.setEnd(s,o)}}}}}j("start");j("end");n.setRng(f)}else{if(l.name){n.select(k.select(l.name)[l.index])}else{if(l.rng){n.setRng(l.rng)}}}}}},select:function(k,j){var i=this,l=i.dom,g=l.createRng(),f;f=l.nodeIndex(k);g.setStart(k.parentNode,f);g.setEnd(k.parentNode,f+1);if(j){function h(m,o){var n=new c.dom.TreeWalker(m,m);do{if(m.nodeType==3&&c.trim(m.nodeValue).length!=0){if(o){g.setStart(m,0)}else{g.setEnd(m,m.nodeValue.length)}return}if(m.nodeName=="BR"){if(o){g.setStartBefore(m)}else{g.setEndBefore(m)}return}}while(m=(o?n.next():n.prev()))}h(k,1);h(k)}i.setRng(g);return k},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}if(h.compareEndPoints){return h.compareEndPoints("StartToEnd",h)===0}return !g||h.collapsed},collapse:function(f){var g=this,h=g.getRng(),i;if(h.item){i=h.item(0);h=this.win.document.body.createTextRange();h.moveToElementText(i)}h.collapse(!!f);g.setRng(h)},getSel:function(){var g=this,f=this.win;return f.getSelection?f.getSelection():f.document.selection},getRng:function(j){var g=this,h,i;if(j&&g.tridentSel){return g.tridentSel.getRangeAt(0)}try{if(h=g.getSel()){i=h.rangeCount>0?h.getRangeAt(0):(h.createRange?h.createRange():g.win.document.createRange())}}catch(f){}if(!i){i=g.win.document.createRange?g.win.document.createRange():g.win.document.body.createTextRange()}return i},setRng:function(i){var h,g=this;if(!g.tridentSel){h=g.getSel();if(h){h.removeAllRanges();h.addRange(i)}}else{if(i.cloneRange){g.tridentSel.addRange(i);return}try{i.select()}catch(f){}}},setNode:function(g){var f=this;f.setContent(f.dom.getOuterHTML(g));return g},getNode:function(){var g=this,f=g.getRng(),h=g.getSel(),i;if(f.setStart){if(!f){return g.dom.getRoot()}i=f.commonAncestorContainer;if(!f.collapsed){if(f.startContainer==f.endContainer){if(f.startOffset-f.endOffset<2){if(f.startContainer.hasChildNodes()){i=f.startContainer.childNodes[f.startOffset]}}}if(c.isWebKit&&h.anchorNode&&h.anchorNode.nodeType==1){return h.anchorNode.childNodes[h.anchorOffset]}}if(i&&i.nodeType==3){return i.parentNode}return i}return f.item?f.item(0):f.parentElement()},getSelectedBlocks:function(g,f){var i=this,j=i.dom,m,h,l,k=[];m=j.getParent(g||i.getStart(),j.isBlock);h=j.getParent(f||i.getEnd(),j.isBlock);if(m){k.push(m)}if(m&&h&&m!=h){l=m;while((l=l.nextSibling)&&l!=h){if(j.isBlock(l)){k.push(l)}}}if(h&&m!=h){k.push(h)}return k},destroy:function(g){var f=this;f.win=null;if(f.tridentSel){f.tridentSel.destroy()}if(!g){c.removeUnload(f.destroy)}}})})(tinymce);(function(a){a.create("tinymce.dom.XMLWriter",{node:null,XMLWriter:function(c){function b(){var e=document.implementation;if(!e||!e.createDocument){try{return new ActiveXObject("MSXML2.DOMDocument")}catch(d){}try{return new ActiveXObject("Microsoft.XmlDom")}catch(d){}}else{return e.createDocument("","",null)}}this.doc=b();this.valid=a.isOpera||a.isWebKit;this.reset()},reset:function(){var b=this,c=b.doc;if(c.firstChild){c.removeChild(c.firstChild)}b.node=c.appendChild(c.createElement("html"))},writeStartElement:function(c){var b=this;b.node=b.node.appendChild(b.doc.createElement(c))},writeAttribute:function(c,b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.setAttribute(c,b)},writeEndElement:function(){this.node=this.node.parentNode},writeFullEndElement:function(){var b=this,c=b.node;c.appendChild(b.doc.createTextNode(""));b.node=c.parentNode},writeText:function(b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.appendChild(this.doc.createTextNode(b))},writeCDATA:function(b){this.node.appendChild(this.doc.createCDATASection(b))},writeComment:function(b){if(a.isIE){b=b.replace(/^\-|\-$/g," ")}this.node.appendChild(this.doc.createComment(b.replace(/\-\-/g," ")))},getContent:function(){var b;b=this.doc.xml||new XMLSerializer().serializeToString(this.doc);b=b.replace(/<\?[^?]+\?>||<\/html>||]+>/g,"");b=b.replace(/ ?\/>/g," />");if(this.valid){b=b.replace(/\%MCGT%/g,">")}return b}})})(tinymce);(function(a){a.create("tinymce.dom.StringWriter",{str:null,tags:null,count:0,settings:null,indent:null,StringWriter:function(b){this.settings=a.extend({indent_char:" ",indentation:0},b);this.reset()},reset:function(){this.indent="";this.str="";this.tags=[];this.count=0},writeStartElement:function(b){this._writeAttributesEnd();this.writeRaw("<"+b);this.tags.push(b);this.inAttr=true;this.count++;this.elementCount=this.count},writeAttribute:function(d,b){var c=this;c.writeRaw(" "+c.encode(d)+'="'+c.encode(b)+'"')},writeEndElement:function(){var b;if(this.tags.length>0){b=this.tags.pop();if(this._writeAttributesEnd(1)){this.writeRaw("")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw("");if(this.settings.indentation>0){this.writeRaw("\n")}}},writeText:function(b){this._writeAttributesEnd();this.writeRaw(this.encode(b));this.count++},writeCDATA:function(b){this._writeAttributesEnd();this.writeRaw("");this.count++},writeComment:function(b){this._writeAttributesEnd();this.writeRaw("");this.count++},writeRaw:function(b){this.str+=b},encode:function(b){return b.replace(/[<>&"]/g,function(c){switch(c){case"<":return"<";case">":return">";case"&":return"&";case'"':return"""}return c})},getContent:function(){return this.str},_writeAttributesEnd:function(b){if(!this.inAttr){return}this.inAttr=false;if(b&&this.elementCount==this.count){this.writeRaw(" />");return false}this.writeRaw(">");return true}})})(tinymce);(function(e){var g=e.extend,f=e.each,b=e.util.Dispatcher,d=e.isIE,a=e.isGecko;function c(h){return h.replace(/([?+*])/g,".$1")}e.create("tinymce.dom.Serializer",{Serializer:function(j){var i=this;i.key=0;i.onPreProcess=new b(i);i.onPostProcess=new b(i);try{i.writer=new e.dom.XMLWriter()}catch(h){i.writer=new e.dom.StringWriter()}i.settings=j=g({dom:e.DOM,valid_nodes:0,node_filter:0,attr_filter:0,invalid_attrs:/^(_mce_|_moz_|sizset|sizcache)/,closed:/^(br|hr|input|meta|img|link|param|area)$/,entity_encoding:"named",entities:"160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro",valid_elements:"*[*]",extended_valid_elements:0,invalid_elements:0,fix_table_elements:1,fix_list_elements:true,fix_content_duplication:true,convert_fonts_to_spans:false,font_size_classes:0,apply_source_formatting:0,indent_mode:"simple",indent_char:"\t",indent_levels:1,remove_linebreaks:1,remove_redundant_brs:1,element_format:"xhtml"},j);i.dom=j.dom;i.schema=j.schema;if(j.entity_encoding=="named"&&!j.entities){j.entity_encoding="raw"}if(j.remove_redundant_brs){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/(
    \s*)+<\/(p|h[1-6]|div|li)>/gi,function(n,m,o){if(/^
    \s*<\//.test(n)){return""}return n})})}if(j.element_format=="html"){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/<([^>]+) \/>/g,"<$1>")})}if(j.fix_list_elements){i.onPreProcess.add(function(v,s){var l,z,y=["ol","ul"],u,t,q,k=/^(OL|UL)$/,A;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p=1767){f(i.dom.select("p table",l.node).reverse(),function(p){var o=i.dom.getParent(p.parentNode,"table,p");if(o.nodeName!="TABLE"){try{i.dom.split(o,p)}catch(m){}}})}})}},setEntities:function(o){var n=this,j,m,h={},k;if(n.entityLookup){return}j=o.split(",");for(m=0;m1){f(q[1].split("|"),function(u){var p={},t;k=k||[];u=u.replace(/::/g,"~");u=/^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(u);u[2]=u[2].replace(/~/g,":");if(u[1]=="!"){r=r||[];r.push(u[2])}if(u[1]=="-"){for(t=0;t=1767)){p=j.createHTMLDocument("");f(r.nodeName=="BODY"?r.childNodes:[r],function(h){p.body.appendChild(p.importNode(h,true))});if(r.nodeName!="BODY"){r=p.body.firstChild}else{r=p.body}i=k.dom.doc;k.dom.doc=p}k.key=""+(parseInt(k.key)+1);if(!q.no_events){q.node=r;k.onPreProcess.dispatch(k,q)}k.writer.reset();k._info=q;k._serializeNode(r,q.getInner);q.content=k.writer.getContent();if(i){k.dom.doc=i}if(!q.no_events){k.onPostProcess.dispatch(k,q)}k._postProcess(q);q.node=null;return e.trim(q.content)},_postProcess:function(n){var i=this,k=i.settings,j=n.content,m=[],l;if(n.format=="html"){l=i._protect({content:j,patterns:[{pattern:/(]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/(]*>)(.*?)(<\/style>)/g},{pattern:/(]*>)(.*?)(<\/pre>)/g,encode:1},{pattern:/()/g}]});j=l.content;if(k.entity_encoding!=="raw"){j=i._encode(j)}if(!n.set){j=j.replace(/

    \s+<\/p>|]+)>\s+<\/p>/g,k.entity_encoding=="numeric"?" 

    ":" 

    ");if(k.remove_linebreaks){j=j.replace(/\r?\n|\r/g," ");j=j.replace(/(<[^>]+>)\s+/g,"$1 ");j=j.replace(/\s+(<\/[^>]+>)/g," $1");j=j.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g,"<$1 $2>");j=j.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g,"<$1>");j=j.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g,"")}if(k.apply_source_formatting&&k.indent_mode=="simple"){j=j.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g,"\n<$1$2$3>\n");j=j.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g,"\n<$1$2>");j=j.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g,"\n");j=j.replace(/\n\n/g,"\n")}}j=i._unprotect(j,l);j=j.replace(//g,"");if(k.entity_encoding=="raw"){j=j.replace(/

     <\/p>|]+)> <\/p>/g,"\u00a0

    ")}j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,p,o){return""+i.dom.decode(o.replace(//g,""))+""})}n.content=j},_serializeNode:function(D,I){var z=this,A=z.settings,x=z.writer,q,j,u,F,E,H,B,h,y,k,r,C,p,m,G,o;if(!A.node_filter||A.node_filter(D)){switch(D.nodeType){case 1:if(D.hasAttribute?D.hasAttribute("_mce_bogus"):D.getAttribute("_mce_bogus")){return}p=G=false;q=D.hasChildNodes();k=D.getAttribute("_mce_name")||D.nodeName.toLowerCase();o=D.getAttribute("_mce_type");if(o){if(!z._info.cleanup){p=true;return}else{G=1}}if(d){if(D.scopeName!=="HTML"&&D.scopeName!=="html"){k=D.scopeName+":"+k}}if(k.indexOf("mce:")===0){k=k.substring(4)}if(!G){if(!z.validElementsRE||!z.validElementsRE.test(k)||(z.invalidElementsRE&&z.invalidElementsRE.test(k))||I){p=true;break}}if(d){if(A.fix_content_duplication){if(D._mce_serialized==z.key){return}D._mce_serialized=z.key}if(k.charAt(0)=="/"){k=k.substring(1)}}else{if(a){if(D.nodeName==="BR"&&D.getAttribute("type")=="_moz"){return}}}if(A.validate_children){if(z.elementName&&!z.schema.isValid(z.elementName,k)){p=true;break}z.elementName=k}r=z.findRule(k);if(!r){p=true;break}k=r.name||k;m=A.closed.test(k);if((!q&&r.noEmpty)||(d&&!k)){p=true;break}if(r.requiredAttribs){H=r.requiredAttribs;for(F=H.length-1;F>=0;F--){if(this.dom.getAttrib(D,H[F])!==""){break}}if(F==-1){p=true;break}}x.writeStartElement(k);if(r.attribs){for(F=0,B=r.attribs,E=B.length;F-1;F--){h=B[F];if(h.specified){H=h.nodeName.toLowerCase();if(A.invalid_attrs.test(H)||!r.validAttribsRE.test(H)){continue}C=z.findAttribRule(r,H);y=z._getAttrib(D,C,H);if(y!==null){x.writeAttribute(H,y)}}}}if(o&&G){x.writeAttribute("_mce_type",o)}if(k==="script"&&e.trim(D.innerHTML)){x.writeText("// ");x.writeCDATA(D.innerHTML.replace(/|<\[CDATA\[|\]\]>/g,""));q=false;break}if(r.padd){if(q&&(u=D.firstChild)&&u.nodeType===1&&D.childNodes.length===1){if(u.hasAttribute?u.hasAttribute("_mce_bogus"):u.getAttribute("_mce_bogus")){x.writeText("\u00a0")}}else{if(!q){x.writeText("\u00a0")}}}break;case 3:if(A.validate_children&&z.elementName&&!z.schema.isValid(z.elementName,"#text")){return}return x.writeText(D.nodeValue);case 4:return x.writeCDATA(D.nodeValue);case 8:return x.writeComment(D.nodeValue)}}else{if(D.nodeType==1){q=D.hasChildNodes()}}if(q&&!m){u=D.firstChild;while(u){z._serializeNode(u);z.elementName=k;u=u.nextSibling}}if(!p){if(!m){x.writeFullEndElement()}else{x.writeEndElement()}}},_protect:function(j){var i=this;j.items=j.items||[];function h(l){return l.replace(/[\r\n\\]/g,function(m){if(m==="\n"){return"\\n"}else{if(m==="\\"){return"\\\\"}}return"\\r"})}function k(l){return l.replace(/\\[\\rn]/g,function(m){if(m==="\\n"){return"\n"}else{if(m==="\\\\"){return"\\"}}return"\r"})}f(j.patterns,function(l){j.content=k(h(j.content).replace(l.pattern,function(n,o,m,p){m=k(m);if(l.encode){m=i._encode(m)}j.items.push(m);return o+""+p}))});return j},_unprotect:function(i,j){i=i.replace(/\"))}if(a&&j.ListBox){if(a.Button||a.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarEnd"},b.createHTML("span",null,""))}}if(b.stdMode){e+=''+j.renderHTML()+""}else{e+=""+j.renderHTML()+""}if(f&&j.ListBox){if(f.Button||f.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarStart"},b.createHTML("span",null,""))}}}g="mceToolbarEnd";if(j.Button){g+=" mceToolbarEndButton"}else{if(j.SplitButton){g+=" mceToolbarEndSplitButton"}else{if(j.ListBox){g+=" mceToolbarEndListBox"}}}e+=b.createHTML("td",{"class":g},b.createHTML("span",null,""));return b.createHTML("table",{id:l.id,"class":"mceToolbar"+(m["class"]?" "+m["class"]:""),cellpadding:"0",cellspacing:"0",align:l.settings.align||""},""+e+"")}});(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{items:[],urls:{},lookup:{},onAdd:new a(this),get:function(d){return this.lookup[d]},requireLangPack:function(e){var d=b.settings;if(d&&d.language){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(e,d){this.items.push(d);this.lookup[e]=d;this.onAdd.dispatch(this,e,d);return d},load:function(h,e,d,g){var f=this;if(f.urls[h]){return}if(e.indexOf("/")!=0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}f.urls[h]=e.substring(0,e.lastIndexOf("/"));b.ScriptLoader.add(e,d,g)}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(q){var n=this,p,l=j.ScriptLoader,u,o=[],m;function r(x,y,t){var v=x[y];if(!v){return}if(j.is(v,"string")){t=v.replace(/\.\w+$/,"");t=t?j.resolve(t):0;v=j.resolve(v)}return v.apply(t||this,Array.prototype.slice.call(arguments,2))}q=d({theme:"simple",language:"en"},q);n.settings=q;i.add(document,"init",function(){var s,v;r(q,"onpageload");switch(q.mode){case"exact":s=q.elements||"";if(s.length>0){g(e(s),function(x){if(k.get(x)){m=new j.Editor(x,q);o.push(m);m.render(1)}else{g(document.forms,function(y){g(y.elements,function(z){if(z.name===x){x="mce_editor_"+c++;k.setAttrib(z,"id",x);m=new j.Editor(x,q);o.push(m);m.render(1)}})})}})}break;case"textareas":case"specific_textareas":function t(y,x){return x.constructor===RegExp?x.test(y.className):k.hasClass(y,x)}g(k.select("textarea"),function(x){if(q.editor_deselector&&t(x,q.editor_deselector)){return}if(!q.editor_selector||t(x,q.editor_selector)){u=k.get(x.name);if(!x.id&&!u){x.id=x.name}if(!x.id||n.get(x.id)){x.id=k.uniqueId()}m=new j.Editor(x.id,q);o.push(m);m.render(1)}});break}if(q.oninit){s=v=0;g(o,function(x){v++;if(!x.initialized){x.onInit.add(function(){s++;if(s==v){r(q,"oninit")}})}else{s++}if(s==v){r(q,"oninit")}})}})},get:function(l){if(l===a){return this.editors}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual_table_class:"mceItemTable",visual:1,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",apply_source_formatting:1,directionality:"ltr",forced_root_block:"p",valid_elements:"@[id|class|style|title|dir';if(F.document_base_url!=m.documentBaseURL){E.iframeHTML+=''}E.iframeHTML+='';if(m.relaxedDomain){E.iframeHTML+='\n'; - - document.write(html); - }; - - // Firebug - if (query.debug) - include('firebug/firebug-lite.js'); - - // Core ns - include('tinymce.js'); - - // Load framework adapter - if (query.api) - include('adapter/' + query.api + '/adapter.js'); - - // Core API - include('util/Dispatcher.js'); - include('util/URI.js'); - include('util/Cookie.js'); - include('util/JSON.js'); - include('util/JSONP.js'); - include('util/XHR.js'); - include('util/JSONRequest.js'); - include('dom/DOMUtils.js'); - include('dom/Range.js'); - include('dom/TridentSelection.js'); - include('dom/Sizzle.js'); - include('dom/EventUtils.js'); - include('dom/Element.js'); - include('dom/Selection.js'); - include('dom/XMLWriter.js'); - include('dom/Schema.js'); - include('dom/StringWriter.js'); - include('dom/Serializer.js'); - include('dom/ScriptLoader.js'); - include('dom/TreeWalker.js'); - include('dom/RangeUtils.js'); - include('ui/Control.js'); - include('ui/Container.js'); - include('ui/Separator.js'); - include('ui/MenuItem.js'); - include('ui/Menu.js'); - include('ui/DropMenu.js'); - include('ui/Button.js'); - include('ui/ListBox.js'); - include('ui/NativeListBox.js'); - include('ui/MenuButton.js'); - include('ui/SplitButton.js'); - include('ui/ColorSplitButton.js'); - include('ui/Toolbar.js'); - include('AddOnManager.js'); - include('EditorManager.js'); - include('Editor.js'); - include('EditorCommands.js'); - include('UndoManager.js'); - include('ForceBlocks.js'); - include('ControlManager.js'); - include('WindowManager.js'); - include('Formatter.js'); - include('CommandManager.js'); - include('LegacyInput.js'); - - // Developer API - include('xml/Parser.js'); - include('Developer.js'); - - load(); -}()); \ No newline at end of file +/** + * tiny_mce_dev.js + * + * Copyright 2009, Moxiecode Systems AB + * Released under LGPL License. + * + * License: http://tinymce.moxiecode.com/license + * Contributing: http://tinymce.moxiecode.com/contributing + * + * This file should only be used while developing TinyMCE + * tiny_mce.js or tiny_mce_src.js should be used in a production environment. + * This file loads the js files from classes instead of a merged copy. + */ + +(function() { + var i, nl = document.getElementsByTagName('script'), base, src, p, li, query = '', it, scripts = []; + + if (window.tinyMCEPreInit) { + base = tinyMCEPreInit.base; + query = tinyMCEPreInit.query || ''; + } else { + for (i=0; i\n'; + + document.write(html); + }; + + // Firebug + if (query.debug && !("console" in window)) { + include('firebug/firebug-lite.js'); + } + + // Core ns + include('tinymce.js'); + + // Load framework adapter + if (query.api) + include('adapter/' + query.api + '/adapter.js'); + + // tinymce.util.* + include('util/Dispatcher.js'); + include('util/URI.js'); + include('util/Cookie.js'); + include('util/JSON.js'); + include('util/JSONP.js'); + include('util/XHR.js'); + include('util/JSONRequest.js'); + include('util/VK.js'); + include('util/Quirks.js'); + + // tinymce.html.* + include('html/Entities.js'); + include('html/Styles.js'); + include('html/Schema.js'); + include('html/SaxParser.js'); + include('html/Node.js'); + include('html/DomParser.js'); + include('html/Serializer.js'); + include('html/Writer.js'); + + // tinymce.dom.* + include('dom/DOMUtils.js'); + include('dom/Range.js'); + include('dom/TridentSelection.js'); + include('dom/Sizzle.js'); + include('dom/EventUtils.js'); + include('dom/Element.js'); + include('dom/Selection.js'); + include('dom/Serializer.js'); + include('dom/ScriptLoader.js'); + include('dom/TreeWalker.js'); + include('dom/RangeUtils.js'); + + // tinymce.ui.* + include('ui/KeyboardNavigation.js'); + include('ui/Control.js'); + include('ui/Container.js'); + include('ui/Separator.js'); + include('ui/MenuItem.js'); + include('ui/Menu.js'); + include('ui/DropMenu.js'); + include('ui/Button.js'); + include('ui/ListBox.js'); + include('ui/NativeListBox.js'); + include('ui/MenuButton.js'); + include('ui/SplitButton.js'); + include('ui/ColorSplitButton.js'); + include('ui/ToolbarGroup.js'); + include('ui/Toolbar.js'); + + // tinymce.* + include('AddOnManager.js'); + include('EditorManager.js'); + include('Editor.js'); + include('EditorCommands.js'); + include('UndoManager.js'); + include('ForceBlocks.js'); + include('ControlManager.js'); + include('WindowManager.js'); + include('Formatter.js'); + include('LegacyInput.js'); + + load(); +}()); diff --git a/js/tiny_mce/tiny_mce_jquery.js b/js/tiny_mce/tiny_mce_jquery.js index b4f7ddb4..e7139c08 100644 --- a/js/tiny_mce/tiny_mce_jquery.js +++ b/js/tiny_mce/tiny_mce_jquery.js @@ -1 +1 @@ -(function(c){var a=/^\s*|\s*$/g,d;var b={majorVersion:"3",minorVersion:"3.2",releaseDate:"2010-03-25",_init:function(){var r=this,o=document,m=navigator,f=m.userAgent,l,e,k,j,h,q;r.isOpera=c.opera&&opera.buildNumber;r.isWebKit=/WebKit/.test(f);r.isIE=!r.isWebKit&&!r.isOpera&&(/MSIE/gi).test(f)&&(/Explorer/gi).test(m.appName);r.isIE6=r.isIE&&/MSIE [56]/.test(f);r.isGecko=!r.isWebKit&&/Gecko/.test(f);r.isMac=f.indexOf("Mac")!=-1;r.isAir=/adobeair/i.test(f);if(c.tinyMCEPreInit){r.suffix=tinyMCEPreInit.suffix;r.baseURL=tinyMCEPreInit.base;r.query=tinyMCEPreInit.query;return}r.suffix="";e=o.getElementsByTagName("base");for(l=0;l=c.length){for(e=0,b=g.length;e=c.length||g[e]!=c[e]){f=e+1;break}}}if(g.length=g.length||g[e]!=c[e]){f=e+1;break}}}if(f==1){return h}for(e=0,b=g.length-(f-1);e=0;c--){if(f[c].length==0||f[c]=="."){continue}if(f[c]==".."){b++;continue}if(b>0){b--;continue}h.push(f[c])}c=e.length-b;if(c<=0){g=h.reverse().join("/")}else{g=e.slice(0,c).join("/")+"/"+h.reverse().join("/")}if(g.indexOf("/")!==0){g="/"+g}if(d&&g.lastIndexOf("/")!==g.length-1){g+=d}return g},getURI:function(d){var c,b=this;if(!b.source||d){c="";if(!d){if(b.protocol){c+=b.protocol+"://"}if(b.userInfo){c+=b.userInfo+"@"}if(b.host){c+=b.host}if(b.port){c+=":"+b.port}}if(b.path){c+=b.path}if(b.query){c+="?"+b.query}if(b.anchor){c+="#"+b.anchor}b.source=c}return b.source}})})();(function(){var a=tinymce.each;tinymce.create("static tinymce.util.Cookie",{getHash:function(d){var b=this.get(d),c;if(b){a(b.split("&"),function(e){e=e.split("=");c=c||{};c[unescape(e[0])]=unescape(e[1])})}return c},setHash:function(j,b,g,f,i,c){var h="";a(b,function(e,d){h+=(!h?"":"&")+escape(d)+"="+escape(e)});this.set(j,h,g,f,i,c)},get:function(i){var h=document.cookie,g,f=i+"=",d;if(!h){return}d=h.indexOf("; "+f);if(d==-1){d=h.indexOf(f);if(d!=0){return null}}else{d+=2}g=h.indexOf(";",d);if(g==-1){g=h.length}return unescape(h.substring(d+f.length,g))},set:function(i,b,g,f,h,c){document.cookie=i+"="+escape(b)+((g)?"; expires="+g.toGMTString():"")+((f)?"; path="+escape(f):"")+((h)?"; domain="+h:"")+((c)?"; secure":"")},remove:function(e,b){var c=new Date();c.setTime(c.getTime()-1000);this.set(e,"",c,b,c)}})})();tinymce.create("static tinymce.util.JSON",{serialize:function(e){var c,a,d=tinymce.util.JSON.serialize,b;if(e==null){return"null"}b=typeof e;if(b=="string"){a="\bb\tt\nn\ff\rr\"\"''\\\\";return'"'+e.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g,function(g,f){c=a.indexOf(f);if(c+1){return"\\"+a.charAt(c+1)}g=f.charCodeAt().toString(16);return"\\u"+"0000".substring(g.length)+g})+'"'}if(b=="object"){if(e.hasOwnProperty&&e instanceof Array){for(c=0,a="[";c0?",":"")+d(e[c])}return a+"]"}a="{";for(c in e){a+=typeof e[c]!="function"?(a.length>1?',"':'"')+c+'":'+d(e[c]):""}return a+"}"}return""+e},parse:function(s){try{return eval("("+s+")")}catch(ex){}}});tinymce.create("static tinymce.util.XHR",{send:function(g){var a,e,b=window,h=0;g.scope=g.scope||this;g.success_scope=g.success_scope||g.scope;g.error_scope=g.error_scope||g.scope;g.async=g.async===false?false:true;g.data=g.data||"";function d(i){a=0;try{a=new ActiveXObject(i)}catch(c){}return a}a=b.XMLHttpRequest?new XMLHttpRequest():d("Microsoft.XMLHTTP")||d("Msxml2.XMLHTTP");if(a){if(a.overrideMimeType){a.overrideMimeType(g.content_type)}a.open(g.type||(g.data?"POST":"GET"),g.url,g.async);if(g.content_type){a.setRequestHeader("Content-Type",g.content_type)}a.setRequestHeader("X-Requested-With","XMLHttpRequest");a.send(g.data);function f(){if(!g.async||a.readyState==4||h++>10000){if(g.success&&h<10000&&a.status==200){g.success.call(g.success_scope,""+a.responseText,a,g)}else{if(g.error){g.error.call(g.error_scope,h>10000?"TIMED_OUT":"GENERAL",a,g)}}a=null}else{b.setTimeout(f,10)}}if(!g.async){return f()}e=b.setTimeout(f,10)}}});(function(){var c=tinymce.extend,b=tinymce.util.JSON,a=tinymce.util.XHR;tinymce.create("tinymce.util.JSONRequest",{JSONRequest:function(d){this.settings=c({},d);this.count=0},send:function(f){var e=f.error,d=f.success;f=c(this.settings,f);f.success=function(h,g){h=b.parse(h);if(typeof(h)=="undefined"){h={error:"JSON Parse error."}}if(h.error){e.call(f.error_scope||f.scope,h.error,g)}else{d.call(f.success_scope||f.scope,h.result)}};f.error=function(h,g){e.call(f.error_scope||f.scope,h,g)};f.data=b.serialize({id:f.id||"c"+(this.count++),method:f.method,params:f.params});f.content_type="application/json";a.send(f)},"static":{sendRPC:function(d){return new tinymce.util.JSONRequest().send(d)}}})}());(function(m){var k=m.each,j=m.is,i=m.isWebKit,d=m.isIE,a=/^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,e=g("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"),f=g("src,href,style,coords,shape"),c={"&":"&",'"':""","<":"<",">":">"},n=/[<>&\"]/g,b=/^([a-z0-9],?)+$/i,h=/<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,l=/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;function g(q){var p={},o;q=q.split(",");for(o=q.length;o>=0;o--){p[q[o]]=1}return p}m.create("tinymce.dom.DOMUtils",{doc:null,root:null,files:null,pixelStyles:/^(top|left|bottom|right|width|height|borderWidth)$/,props:{"for":"htmlFor","class":"className",className:"className",checked:"checked",disabled:"disabled",maxlength:"maxLength",readonly:"readOnly",selected:"selected",value:"value",id:"id",name:"name",type:"type"},DOMUtils:function(u,q){var p=this,o;p.doc=u;p.win=window;p.files={};p.cssFlicker=false;p.counter=0;p.boxModel=!m.isIE||u.compatMode=="CSS1Compat";p.stdMode=u.documentMode===8;p.settings=q=m.extend({keep_values:false,hex_colors:1,process_html:1},q);if(m.isIE6){try{u.execCommand("BackgroundImageCache",false,true)}catch(r){p.cssFlicker=true}}if(q.valid_styles){p._styles={};k(q.valid_styles,function(t,s){p._styles[s]=m.explode(t)})}m.addUnload(p.destroy,p)},getRoot:function(){var o=this,p=o.settings;return(p&&o.get(p.root_element))||o.doc.body},getViewPort:function(p){var q,o;p=!p?this.win:p;q=p.document;o=this.boxModel?q.documentElement:q.body;return{x:p.pageXOffset||o.scrollLeft,y:p.pageYOffset||o.scrollTop,w:p.innerWidth||o.clientWidth,h:p.innerHeight||o.clientHeight}},getRect:function(s){var r,o=this,q;s=o.get(s);r=o.getPos(s);q=o.getSize(s);return{x:r.x,y:r.y,w:q.w,h:q.h}},getSize:function(r){var p=this,o,q;r=p.get(r);o=p.getStyle(r,"width");q=p.getStyle(r,"height");if(o.indexOf("px")===-1){o=0}if(q.indexOf("px")===-1){q=0}return{w:parseInt(o)||r.offsetWidth||r.clientWidth,h:parseInt(q)||r.offsetHeight||r.clientHeight}},getParent:function(q,p,o){return this.getParents(q,p,o,false)},getParents:function(z,v,s,y){var q=this,p,u=q.settings,x=[];z=q.get(z);y=y===undefined;if(u.strict_root){s=s||q.getRoot()}if(j(v,"string")){p=v;if(v==="*"){v=function(o){return o.nodeType==1}}else{v=function(o){return q.is(o,p)}}}while(z){if(z==s||!z.nodeType||z.nodeType===9){break}if(!v||v(z)){if(y){x.push(z)}else{return z}}z=z.parentNode}return y?x:null},get:function(o){var p;if(o&&this.doc&&typeof(o)=="string"){p=o;o=this.doc.getElementById(o);if(o&&o.id!==p){return this.doc.getElementsByName(p)[1]}}return o},getNext:function(p,o){return this._findSib(p,o,"nextSibling")},getPrev:function(p,o){return this._findSib(p,o,"previousSibling")},add:function(s,v,o,r,u){var q=this;return this.run(s,function(y){var x,t;x=j(v,"string")?q.doc.createElement(v):v;q.setAttribs(x,o);if(r){if(r.nodeType){x.appendChild(r)}else{q.setHTML(x,r)}}return !u?y.appendChild(x):x})},create:function(q,o,p){return this.add(this.doc.createElement(q),q,o,p,1)},createHTML:function(v,p,s){var u="",r=this,q;u+="<"+v;for(q in p){if(p.hasOwnProperty(q)){u+=" "+q+'="'+r.encode(p[q])+'"'}}if(m.is(s)){return u+">"+s+""}return u+" />"},remove:function(o,p){return this.run(o,function(r){var q,s;q=r.parentNode;if(!q){return null}if(p){while(s=r.firstChild){if(s.nodeType!==3||s.nodeValue){q.insertBefore(s,r)}else{r.removeChild(s)}}}return q.removeChild(r)})},setStyle:function(r,o,p){var q=this;return q.run(r,function(v){var u,t;u=v.style;o=o.replace(/-(\D)/g,function(x,s){return s.toUpperCase()});if(q.pixelStyles.test(o)&&(m.is(p,"number")||/^[\-0-9\.]+$/.test(p))){p+="px"}switch(o){case"opacity":if(d){u.filter=p===""?"":"alpha(opacity="+(p*100)+")";if(!r.currentStyle||!r.currentStyle.hasLayout){u.display="inline-block"}}u[o]=u["-moz-opacity"]=u["-khtml-opacity"]=p||"";break;case"float":d?u.styleFloat=p:u.cssFloat=p;break;default:u[o]=p||""}if(q.settings.update_styles){q.setAttrib(v,"_mce_style")}})},getStyle:function(r,o,q){r=this.get(r);if(!r){return false}if(this.doc.defaultView&&q){o=o.replace(/[A-Z]/g,function(s){return"-"+s});try{return this.doc.defaultView.getComputedStyle(r,null).getPropertyValue(o)}catch(p){return null}}o=o.replace(/-(\D)/g,function(t,s){return s.toUpperCase()});if(o=="float"){o=d?"styleFloat":"cssFloat"}if(r.currentStyle&&q){return r.currentStyle[o]}return r.style[o]},setStyles:function(u,v){var q=this,r=q.settings,p;p=r.update_styles;r.update_styles=0;k(v,function(o,s){q.setStyle(u,s,o)});r.update_styles=p;if(r.update_styles){q.setAttrib(u,r.cssText)}},setAttrib:function(q,r,o){var p=this;if(!q||!r){return}if(p.settings.strict){r=r.toLowerCase()}return this.run(q,function(u){var t=p.settings;switch(r){case"style":if(!j(o,"string")){k(o,function(s,x){p.setStyle(u,x,s)});return}if(t.keep_values){if(o&&!p._isRes(o)){u.setAttribute("_mce_style",o,2)}else{u.removeAttribute("_mce_style",2)}}u.style.cssText=o;break;case"class":u.className=o||"";break;case"src":case"href":if(t.keep_values){if(t.url_converter){o=t.url_converter.call(t.url_converter_scope||p,o,r,u)}p.setAttrib(u,"_mce_"+r,o,2)}break;case"shape":u.setAttribute("_mce_style",o);break}if(j(o)&&o!==null&&o.length!==0){u.setAttribute(r,""+o,2)}else{u.removeAttribute(r,2)}})},setAttribs:function(q,r){var p=this;return this.run(q,function(o){k(r,function(s,t){p.setAttrib(o,t,s)})})},getAttrib:function(r,s,q){var o,p=this;r=p.get(r);if(!r||r.nodeType!==1){return false}if(!j(q)){q=""}if(/^(src|href|style|coords|shape)$/.test(s)){o=r.getAttribute("_mce_"+s);if(o){return o}}if(d&&p.props[s]){o=r[p.props[s]];o=o&&o.nodeValue?o.nodeValue:o}if(!o){o=r.getAttribute(s,2)}if(/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(s)){if(r[p.props[s]]===true&&o===""){return s}return o?s:""}if(r.nodeName==="FORM"&&r.getAttributeNode(s)){return r.getAttributeNode(s).nodeValue}if(s==="style"){o=o||r.style.cssText;if(o){o=p.serializeStyle(p.parseStyle(o),r.nodeName);if(p.settings.keep_values&&!p._isRes(o)){r.setAttribute("_mce_style",o)}}}if(i&&s==="class"&&o){o=o.replace(/(apple|webkit)\-[a-z\-]+/gi,"")}if(d){switch(s){case"rowspan":case"colspan":if(o===1){o=""}break;case"size":if(o==="+0"||o===20||o===0){o=""}break;case"width":case"height":case"vspace":case"checked":case"disabled":case"readonly":if(o===0){o=""}break;case"hspace":if(o===-1){o=""}break;case"maxlength":case"tabindex":if(o===32768||o===2147483647||o==="32768"){o=""}break;case"multiple":case"compact":case"noshade":case"nowrap":if(o===65535){return s}return q;case"shape":o=o.toLowerCase();break;default:if(s.indexOf("on")===0&&o){o=(""+o).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/,"$1")}}}return(o!==undefined&&o!==null&&o!=="")?""+o:q},getPos:function(A,s){var p=this,o=0,z=0,u,v=p.doc,q;A=p.get(A);s=s||v.body;if(A){if(d&&!p.stdMode){A=A.getBoundingClientRect();u=p.boxModel?v.documentElement:v.body;o=p.getStyle(p.select("html")[0],"borderWidth");o=(o=="medium"||p.boxModel&&!p.isIE6)&&2||o;A.top+=p.win.self!=p.win.top?2:0;return{x:A.left+u.scrollLeft-o,y:A.top+u.scrollTop-o}}q=A;while(q&&q!=s&&q.nodeType){o+=q.offsetLeft||0;z+=q.offsetTop||0;q=q.offsetParent}q=A.parentNode;while(q&&q!=s&&q.nodeType){o-=q.scrollLeft||0;z-=q.scrollTop||0;q=q.parentNode}}return{x:o,y:z}},parseStyle:function(r){var u=this,v=u.settings,x={};if(!r){return x}function p(D,A,C){var z,B,o,y;z=x[D+"-top"+A];if(!z){return}B=x[D+"-right"+A];if(z!=B){return}o=x[D+"-bottom"+A];if(B!=o){return}y=x[D+"-left"+A];if(o!=y){return}x[C]=y;delete x[D+"-top"+A];delete x[D+"-right"+A];delete x[D+"-bottom"+A];delete x[D+"-left"+A]}function q(y,s,o,A){var z;z=x[s];if(!z){return}z=x[o];if(!z){return}z=x[A];if(!z){return}x[y]=x[s]+" "+x[o]+" "+x[A];delete x[s];delete x[o];delete x[A]}r=r.replace(/&(#?[a-z0-9]+);/g,"&$1_MCE_SEMI_");k(r.split(";"),function(s){var o,t=[];if(s){s=s.replace(/_MCE_SEMI_/g,";");s=s.replace(/url\([^\)]+\)/g,function(y){t.push(y);return"url("+t.length+")"});s=s.split(":");o=m.trim(s[1]);o=o.replace(/url\(([^\)]+)\)/g,function(z,y){return t[parseInt(y)-1]});o=o.replace(/rgb\([^\)]+\)/g,function(y){return u.toHex(y)});if(v.url_converter){o=o.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g,function(y,z){return"url("+v.url_converter.call(v.url_converter_scope||u,u.decode(z),"style",null)+")"})}x[m.trim(s[0]).toLowerCase()]=o}});p("border","","border");p("border","-width","border-width");p("border","-color","border-color");p("border","-style","border-style");p("padding","","padding");p("margin","","margin");q("border","border-width","border-style","border-color");if(d){if(x.border=="medium none"){x.border=""}}return x},serializeStyle:function(v,p){var q=this,r="";function u(s,o){if(o&&s){if(o.indexOf("-")===0){return}switch(o){case"font-weight":if(s==700){s="bold"}break;case"color":case"background-color":s=s.toLowerCase();break}r+=(r?" ":"")+o+": "+s+";"}}if(p&&q._styles){k(q._styles["*"],function(o){u(v[o],o)});k(q._styles[p.toLowerCase()],function(o){u(v[o],o)})}else{k(v,u)}return r},loadCSS:function(o){var q=this,r=q.doc,p;if(!o){o=""}p=q.select("head")[0];k(o.split(","),function(s){var t;if(q.files[s]){return}q.files[s]=true;t=q.create("link",{rel:"stylesheet",href:m._addVer(s)});if(d&&r.documentMode){t.onload=function(){r.recalc();t.onload=null}}p.appendChild(t)})},addClass:function(o,p){return this.run(o,function(q){var r;if(!p){return 0}if(this.hasClass(q,p)){return q.className}r=this.removeClass(q,p);return q.className=(r!=""?(r+" "):"")+p})},removeClass:function(q,r){var o=this,p;return o.run(q,function(t){var s;if(o.hasClass(t,r)){if(!p){p=new RegExp("(^|\\s+)"+r+"(\\s+|$)","g")}s=t.className.replace(p," ");s=m.trim(s!=" "?s:"");t.className=s;if(!s){t.removeAttribute("class");t.removeAttribute("className")}return s}return t.className})},hasClass:function(p,o){p=this.get(p);if(!p||!o){return false}return(" "+p.className+" ").indexOf(" "+o+" ")!==-1},show:function(o){return this.setStyle(o,"display","block")},hide:function(o){return this.setStyle(o,"display","none")},isHidden:function(o){o=this.get(o);return !o||o.style.display=="none"||this.getStyle(o,"display")=="none"},uniqueId:function(o){return(!o?"mce_":o)+(this.counter++)},setHTML:function(q,p){var o=this;return this.run(q,function(v){var r,t,s,z,u,r;p=o.processHTML(p);if(d){function y(){while(v.firstChild){v.firstChild.removeNode()}try{v.innerHTML="
    "+p;v.removeChild(v.firstChild)}catch(x){r=o.create("div");r.innerHTML="
    "+p;k(r.childNodes,function(B,A){if(A){v.appendChild(B)}})}}if(o.settings.fix_ie_paragraphs){p=p.replace(/

    <\/p>|]+)><\/p>|/gi,' 

    ')}y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("p");for(t=s.length-1,r=0;t>=0;t--){z=s[t];if(!z.hasChildNodes()){if(!z._mce_keep){r=1;break}z.removeAttribute("_mce_keep")}}}if(r){p=p.replace(/

    ]+)>|

    /ig,'

    ');p=p.replace(/<\/p>/g,"
    ");y();if(o.settings.fix_ie_paragraphs){s=v.getElementsByTagName("DIV");for(t=s.length-1;t>=0;t--){z=s[t];if(z._mce_tmp){u=o.doc.createElement("p");z.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi,function(A,x){var B;if(x!=="_mce_tmp"){B=z.getAttribute(x);if(!B&&x==="class"){B=z.className}u.setAttribute(x,B)}});for(r=0;r]+)\/>|/gi,"");if(q.keep_values){if(/)/g,"\n");t=t.replace(/^[\r\n]*|[\r\n]*$/g,"");t=t.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g,"");return t}r=r.replace(/]+|)>([\s\S]*?)<\/script>/gi,function(s,x,t){if(!x){x=' type="text/javascript"'}x=x.replace(/src=\"([^\"]+)\"?/i,function(y,z){if(q.url_converter){z=p.encode(q.url_converter.call(q.url_converter_scope||p,p.decode(z),"src","script"))}return'_mce_src="'+z+'"'});if(m.trim(t)){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/style>/gi,function(s,x,t){if(t){v.push(o(t));t=""}return""+t+""});r=r.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(s,x,t){return""})}r=r.replace(//g,"");function u(s){return s.replace(h,function(y,z,x,t){return"<"+z+x.replace(l,function(B,A,E,D,C){var F;A=A.toLowerCase();E=E||D||C||"";if(e[A]){if(E==="false"||E==="0"){return}return A+'="'+A+'"'}if(f[A]&&x.indexOf("_mce_"+A)==-1){F=p.decode(E);if(q.url_converter&&(A=="src"||A=="href")){F=q.url_converter.call(q.url_converter_scope||p,F,A,z)}if(A=="style"){F=p.serializeStyle(p.parseStyle(F),A)}return A+'="'+E+'" _mce_'+A+'="'+p.encode(F)+'"'}return B})+t+">"})}r=u(r);r=r.replace(/MCE_SCRIPT:([0-9]+)/g,function(t,s){return v[s]})}return r},getOuterHTML:function(o){var p;o=this.get(o);if(!o){return null}if(o.outerHTML!==undefined){return o.outerHTML}p=(o.ownerDocument||this.doc).createElement("body");p.appendChild(o.cloneNode(true));return p.innerHTML},setOuterHTML:function(r,p,s){var o=this;function q(u,t,x){var y,v;v=x.createElement("body");v.innerHTML=t;y=v.lastChild;while(y){o.insertAfter(y.cloneNode(true),u);y=y.previousSibling}o.remove(u)}return this.run(r,function(u){u=o.get(u);if(u.nodeType==1){s=s||u.ownerDocument||o.doc;if(d){try{if(d&&u.nodeType==1){u.outerHTML=p}else{q(u,p,s)}}catch(t){q(u,p,s)}}else{q(u,p,s)}}})},decode:function(p){var q,r,o;if(/&[\w#]+;/.test(p)){q=this.doc.createElement("div");q.innerHTML=p;r=q.firstChild;o="";if(r){do{o+=r.nodeValue}while(r=r.nextSibling)}return o||p}return p},encode:function(o){return(""+o).replace(n,function(p){return c[p]})},insertAfter:function(o,p){p=this.get(p);return this.run(o,function(r){var q,s;q=p.parentNode;s=p.nextSibling;if(s){q.insertBefore(r,s)}else{q.appendChild(r)}return r})},isBlock:function(o){if(o.nodeType&&o.nodeType!==1){return false}o=o.nodeName||o;return a.test(o)},replace:function(s,r,p){var q=this;if(j(r,"array")){s=s.cloneNode(true)}return q.run(r,function(t){if(p){k(m.grep(t.childNodes),function(o){s.appendChild(o)})}return t.parentNode.replaceChild(s,t)})},rename:function(r,o){var q=this,p;if(r.nodeName!=o.toUpperCase()){p=q.create(o);k(q.getAttribs(r),function(s){q.setAttrib(p,s.nodeName,q.getAttrib(r,s.nodeName))});q.replace(p,r,1)}return p||r},findCommonAncestor:function(q,o){var r=q,p;while(r){p=o;while(p&&r!=p){p=p.parentNode}if(r==p){break}r=r.parentNode}if(!r&&q.ownerDocument){return q.ownerDocument.documentElement}return r},toHex:function(o){var q=/^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(o);function p(r){r=parseInt(r).toString(16);return r.length>1?r:"0"+r}if(q){o="#"+p(q[1])+p(q[2])+p(q[3]);return o}return o},getClasses:function(){var s=this,o=[],r,u={},v=s.settings.class_filter,q;if(s.classes){return s.classes}function x(t){k(t.imports,function(y){x(y)});k(t.cssRules||t.rules,function(y){switch(y.type||1){case 1:if(y.selectorText){k(y.selectorText.split(","),function(z){z=z.replace(/^\s*|\s*$|^\s\./g,"");if(/\.mce/.test(z)||!/\.[\w\-]+$/.test(z)){return}q=z;z=z.replace(/.*\.([a-z0-9_\-]+).*/i,"$1");if(v&&!(z=v(z,q))){return}if(!u[z]){o.push({"class":z});u[z]=1}})}break;case 3:x(y.styleSheet);break}})}try{k(s.doc.styleSheets,x)}catch(p){}if(o.length>0){s.classes=o}return o},run:function(u,r,q){var p=this,v;if(p.doc&&typeof(u)==="string"){u=p.get(u)}if(!u){return false}q=q||this;if(!u.nodeType&&(u.length||u.length===0)){v=[];k(u,function(s,o){if(s){if(typeof(s)=="string"){s=p.doc.getElementById(s)}v.push(r.call(q,s,o))}});return v}return r.call(q,u)},getAttribs:function(q){var p;q=this.get(q);if(!q){return[]}if(d){p=[];if(q.nodeName=="OBJECT"){return q.attributes}if(q.nodeName==="OPTION"&&this.getAttrib(q,"selected")){p.push({specified:1,nodeName:"selected"})}q.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi,"").replace(/[\w:\-]+/gi,function(o){p.push({specified:1,nodeName:o})});return p}return q.attributes},destroy:function(p){var o=this;if(o.events){o.events.destroy()}o.win=o.doc=o.root=o.events=null;if(!p){m.removeUnload(o.destroy)}},createRng:function(){var o=this.doc;return o.createRange?o.createRange():new m.dom.Range(this)},nodeIndex:function(s,t){var o=0,q,r,p;if(s){for(q=s.nodeType,s=s.previousSibling,r=s;s;s=s.previousSibling){p=s.nodeType;if(!t||p!=3||(q!=p&&s.nodeValue.length)){o++}q=p}}return o},split:function(u,s,y){var z=this,o=z.createRng(),v,q,x;function p(A){var t,r=A.childNodes;if(A.nodeType==1&&A.getAttribute("_mce_type")=="bookmark"){return}for(t=r.length-1;t>=0;t--){p(r[t])}if(A.nodeType!=9){if(A.nodeType==3&&A.nodeValue.length>0){return}if(A.nodeType==1){r=A.childNodes;if(r.length==1&&r[0]&&r[0].nodeType==1&&r[0].getAttribute("_mce_type")=="bookmark"){A.parentNode.insertBefore(r[0],A)}if(r.length||/^(br|hr|input|img)$/i.test(A.nodeName)){return}}z.remove(A)}return A}if(u&&s){o.setStart(u.parentNode,z.nodeIndex(u));o.setEnd(s.parentNode,z.nodeIndex(s));v=o.extractContents();o=z.createRng();o.setStart(s.parentNode,z.nodeIndex(s)+1);o.setEnd(u.parentNode,z.nodeIndex(u)+1);q=o.extractContents();x=u.parentNode;x.insertBefore(p(v),u);if(y){x.replaceChild(y,s)}else{x.insertBefore(s,u)}x.insertBefore(p(q),u);z.remove(u);return y||s}},bind:function(s,o,r,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.add(s,o,r,q||this)},unbind:function(r,o,q){var p=this;if(!p.events){p.events=new m.dom.EventUtils()}return p.events.remove(r,o,q)},_findSib:function(r,o,p){var q=this,s=o;if(r){if(j(s,"string")){s=function(t){return q.is(t,o)}}for(r=r[p];r;r=r[p]){if(s(r)){return r}}}return null},_isRes:function(o){return/^(top|left|bottom|right|width|height)/i.test(o)||/;\s*(top|left|bottom|right|width|height)/i.test(o)}});m.DOM=new m.dom.DOMUtils(document,{process_html:0})})(tinymce);(function(a){function b(c){var N=this,e=c.doc,S=0,E=1,j=2,D=true,R=false,U="startOffset",h="startContainer",P="endContainer",z="endOffset",k=tinymce.extend,n=c.nodeIndex;k(N,{startContainer:e,startOffset:0,endContainer:e,endOffset:0,collapsed:D,commonAncestorContainer:e,START_TO_START:0,START_TO_END:1,END_TO_END:2,END_TO_START:3,setStart:q,setEnd:s,setStartBefore:g,setStartAfter:I,setEndBefore:J,setEndAfter:u,collapse:A,selectNode:x,selectNodeContents:F,compareBoundaryPoints:v,deleteContents:p,extractContents:H,cloneContents:d,insertNode:C,surroundContents:M,cloneRange:K});function q(V,t){B(D,V,t)}function s(V,t){B(R,V,t)}function g(t){q(t.parentNode,n(t))}function I(t){q(t.parentNode,n(t)+1)}function J(t){s(t.parentNode,n(t))}function u(t){s(t.parentNode,n(t)+1)}function A(t){if(t){N[P]=N[h];N[z]=N[U]}else{N[h]=N[P];N[U]=N[z]}N.collapsed=D}function x(t){g(t);u(t)}function F(t){q(t,0);s(t,t.nodeType===1?t.childNodes.length:t.nodeValue.length)}function v(W,X){var Z=N[h],Y=N[U],V=N[P],t=N[z];if(W===0){return G(Z,Y,Z,Y)}if(W===1){return G(Z,Y,V,t)}if(W===2){return G(V,t,V,t)}if(W===3){return G(V,t,Z,Y)}}function p(){m(j)}function H(){return m(S)}function d(){return m(E)}function C(Y){var V=this[h],t=this[U],X,W;if((V.nodeType===3||V.nodeType===4)&&V.nodeValue){if(!t){V.parentNode.insertBefore(Y,V)}else{if(t>=V.nodeValue.length){c.insertAfter(Y,V)}else{X=V.splitText(t);V.parentNode.insertBefore(Y,X)}}}else{if(V.childNodes.length>0){W=V.childNodes[t]}if(W){V.insertBefore(Y,W)}else{V.appendChild(Y)}}}function M(V){var t=N.extractContents();N.insertNode(V);V.appendChild(t);N.selectNode(V)}function K(){return k(new b(c),{startContainer:N[h],startOffset:N[U],endContainer:N[P],endOffset:N[z],collapsed:N.collapsed,commonAncestorContainer:N.commonAncestorContainer})}function O(t,V){var W;if(t.nodeType==3){return t}if(V<0){return t}W=t.firstChild;while(W&&V>0){--V;W=W.nextSibling}if(W){return W}return t}function l(){return(N[h]==N[P]&&N[U]==N[z])}function G(X,Z,V,Y){var aa,W,t,ab,ad,ac;if(X==V){if(Z==Y){return 0}if(Z0){N.collapse(V)}}else{N.collapse(V)}N.collapsed=l();N.commonAncestorContainer=c.findCommonAncestor(N[h],N[P])}function m(ab){var aa,X=0,ad=0,V,Z,W,Y,t,ac;if(N[h]==N[P]){return f(ab)}for(aa=N[P],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[h]){return r(aa,ab)}++X}for(aa=N[h],V=aa.parentNode;V;aa=V,V=V.parentNode){if(V==N[P]){return T(aa,ab)}++ad}Z=ad-X;W=N[h];while(Z>0){W=W.parentNode;Z--}Y=N[P];while(Z<0){Y=Y.parentNode;Z++}for(t=W.parentNode,ac=Y.parentNode;t!=ac;t=t.parentNode,ac=ac.parentNode){W=t;Y=ac}return o(W,Y,ab)}function f(Z){var ab,Y,X,aa,t,W,V;if(Z!=j){ab=e.createDocumentFragment()}if(N[U]==N[z]){return ab}if(N[h].nodeType==3){Y=N[h].nodeValue;X=Y.substring(N[U],N[z]);if(Z!=E){N[h].deleteData(N[U],N[z]-N[U]);N.collapse(D)}if(Z==j){return}ab.appendChild(e.createTextNode(X));return ab}aa=O(N[h],N[U]);t=N[z]-N[U];while(t>0){W=aa.nextSibling;V=y(aa,Z);if(ab){ab.appendChild(V)}--t;aa=W}if(Z!=E){N.collapse(D)}return ab}function r(ab,Y){var aa,Z,V,t,X,W;if(Y!=j){aa=e.createDocumentFragment()}Z=i(ab,Y);if(aa){aa.appendChild(Z)}V=n(ab);t=V-N[U];if(t<=0){if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}Z=ab.previousSibling;while(t>0){X=Z.previousSibling;W=y(Z,Y);if(aa){aa.insertBefore(W,aa.firstChild)}--t;Z=X}if(Y!=E){N.setEndBefore(ab);N.collapse(R)}return aa}function T(Z,Y){var ab,V,aa,t,X,W;if(Y!=j){ab=e.createDocumentFragment()}aa=Q(Z,Y);if(ab){ab.appendChild(aa)}V=n(Z);++V;t=N[z]-V;aa=Z.nextSibling;while(t>0){X=aa.nextSibling;W=y(aa,Y);if(ab){ab.appendChild(W)}--t;aa=X}if(Y!=E){N.setStartAfter(Z);N.collapse(D)}return ab}function o(Z,t,ac){var W,ae,Y,aa,ab,V,ad,X;if(ac!=j){ae=e.createDocumentFragment()}W=Q(Z,ac);if(ae){ae.appendChild(W)}Y=Z.parentNode;aa=n(Z);ab=n(t);++aa;V=ab-aa;ad=Z.nextSibling;while(V>0){X=ad.nextSibling;W=y(ad,ac);if(ae){ae.appendChild(W)}ad=X;--V}W=i(t,ac);if(ae){ae.appendChild(W)}if(ac!=E){N.setStartAfter(Z);N.collapse(D)}return ae}function i(aa,ab){var W=O(N[P],N[z]-1),ac,Z,Y,t,V,X=W!=N[P];if(W==aa){return L(W,X,R,ab)}ac=W.parentNode;Z=L(ac,R,R,ab);while(ac){while(W){Y=W.previousSibling;t=L(W,X,R,ab);if(ab!=j){Z.insertBefore(t,Z.firstChild)}X=D;W=Y}if(ac==aa){return Z}W=ac.previousSibling;ac=ac.parentNode;V=L(ac,R,R,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function Q(aa,ab){var X=O(N[h],N[U]),Y=X!=N[h],ac,Z,W,t,V;if(X==aa){return L(X,Y,D,ab)}ac=X.parentNode;Z=L(ac,R,D,ab);while(ac){while(X){W=X.nextSibling;t=L(X,Y,D,ab);if(ab!=j){Z.appendChild(t)}Y=D;X=W}if(ac==aa){return Z}X=ac.nextSibling;ac=ac.parentNode;V=L(ac,R,D,ab);if(ab!=j){V.appendChild(Z)}Z=V}}function L(t,Y,ab,ac){var X,W,Z,V,aa;if(Y){return y(t,ac)}if(t.nodeType==3){X=t.nodeValue;if(ab){V=N[U];W=X.substring(V);Z=X.substring(0,V)}else{V=N[z];W=X.substring(0,V);Z=X.substring(V)}if(ac!=E){t.nodeValue=Z}if(ac==j){return}aa=t.cloneNode(R);aa.nodeValue=W;return aa}if(ac==j){return}return t.cloneNode(R)}function y(V,t){if(t!=j){return t==E?V.cloneNode(D):V}V.parentNode.removeChild(V)}}a.Range=b})(tinymce.dom);(function(){function a(h){var j=this,k="\uFEFF",f,i,e=h.dom,d=true,g=false;function c(m,l){if(m&&l){if(m.item&&l.item&&m.item(0)===l.item(0)){return d}if(m.isEqual&&l.isEqual&&l.isEqual(m)){try{f.startContainer.nextSibling;return d}catch(n){}}}return g}function b(){var p=h.getRng(),l=e.createRng(),m,n,r,q;n=p.item?p.item(0):p.parentElement();if(n.ownerDocument!=e.doc){return l}if(p.item||!n.hasChildNodes()){l.setStart(n.parentNode,e.nodeIndex(n));l.setEnd(l.startContainer,l.startOffset+1);return l}m=p.duplicate();r=h.isCollapsed();p.collapse();p.pasteHTML('");if(!r){m.collapse(g);m.pasteHTML('")}function o(x){var t,v,s,u;s=e.get("_mce_"+(x?"start":"end"));u=s.previousSibling;if(u&&u.nodeType==3){t=u;v=t.nodeValue.length;e.remove(s);u=t.nextSibling;if(u&&u.nodeType==3){q=d;t.appendData(u.nodeValue);e.remove(u)}}else{u=s.nextSibling;if(u&&u.nodeType==3){t=u;v=0}else{if(u){v=e.nodeIndex(u)-1}else{v=e.nodeIndex(s)}t=s.parentNode}e.remove(s)}if(x){l.setStart(t,v)}if(!x||r){l.setEnd(t,v)}}o(d);if(!r){o(g)}if(q){j.addRange(l)}return l}this.addRange=function(m){var u,A,z=h.dom.doc,s=z.body,v,o,y,p,t,l,q,r,x,n;this.destroy();y=m.startContainer;p=m.startOffset;t=m.endContainer;l=m.endOffset;u=s.createTextRange();if(y==z||t==z){u=s.createTextRange();u.collapse();u.select();return}if(y.nodeType==1&&y.hasChildNodes()){r=y.childNodes.length-1;if(p>r){x=1;y=y.childNodes[r]}else{y=y.childNodes[p]}if(y.nodeType==3){p=0}}if(t.nodeType==1&&t.hasChildNodes()){r=t.childNodes.length-1;if(l==0){n=1;t=t.childNodes[0]}else{t=t.childNodes[Math.min(r,l-1)];if(t.nodeType==3){l=t.nodeValue.length}}}if(y==t&&y.nodeType==1){if(/^(IMG|TABLE)$/.test(y.nodeName)&&p!=l){u=s.createControlRange();u.addElement(y)}else{u=s.createTextRange();if(!y.hasChildNodes()&&y.canHaveHTML){y.innerHTML=k}u.moveToElementText(y);if(y.innerHTML==k){u.collapse(d);y.removeChild(y.firstChild)}}if(p==l){u.collapse(l<=m.endContainer.childNodes.length-1)}u.select();u.scrollIntoView();return}u=s.createTextRange();q=z.createElement("span");q.innerHTML=" ";if(y.nodeType==3){if(x){e.insertAfter(q,y)}else{y.parentNode.insertBefore(q,y)}u.moveToElementText(q);q.parentNode.removeChild(q);u.move("character",p)}else{u.moveToElementText(y);if(x){u.collapse(g)}}if(y==t&&y.nodeType==3){u.moveEnd("character",l-p);u.select();u.scrollIntoView();return}A=s.createTextRange();if(t.nodeType==3){t.parentNode.insertBefore(q,t);A.moveToElementText(q);q.parentNode.removeChild(q);A.move("character",l);u.setEndPoint("EndToStart",A)}else{A.moveToElementText(t);A.collapse(!!n);u.setEndPoint("EndToEnd",A)}u.select();u.scrollIntoView()};this.getRangeAt=function(){if(!f||!c(i,h.getRng())){f=b();i=h.getRng()}return f};this.destroy=function(){i=f=null};if(h.dom.boxModel){(function(){var r=e.doc,m=r.body,o,p;r.documentElement.unselectable=d;function q(s,v){var t=m.createTextRange();try{t.moveToPoint(s,v)}catch(u){t=null}return t}function n(t){var s;if(t.button){s=q(t.x,t.y);if(s){if(s.compareEndPoints("StartToStart",p)>0){s.setEndPoint("StartToStart",p)}else{s.setEndPoint("EndToEnd",p)}s.select()}}else{l()}}function l(){e.unbind(r,"mouseup",l);e.unbind(r,"mousemove",n);o=0}e.bind(r,"mousedown",function(s){if(s.target.nodeName==="HTML"){if(o){l()}o=1;p=q(s.x,s.y);if(p){e.bind(r,"mouseup",l);e.bind(r,"mousemove",n);p.select()}}})})()}}tinymce.dom.TridentSelection=a})();(function(d){var f=d.each,c=d.DOM,b=d.isIE,e=d.isWebKit,a;d.create("tinymce.dom.EventUtils",{EventUtils:function(){this.inits=[];this.events=[]},add:function(m,p,l,j){var g,h=this,i=h.events,k;if(p instanceof Array){k=[];f(p,function(o){k.push(h.add(m,o,l,j))});return k}if(m&&m.hasOwnProperty&&m instanceof Array){k=[];f(m,function(n){n=c.get(n);k.push(h.add(n,p,l,j))});return k}m=c.get(m);if(!m){return}g=function(n){if(h.disabled){return}n=n||window.event;if(n&&b){if(!n.target){n.target=n.srcElement}d.extend(n,h._stoppers)}if(!j){return l(n)}return l.call(j,n)};if(p=="unload"){d.unloads.unshift({func:g});return g}if(p=="init"){if(h.domLoaded){g()}else{h.inits.push(g)}return g}i.push({obj:m,name:p,func:l,cfunc:g,scope:j});h._add(m,p,g);return l},remove:function(l,m,k){var h=this,g=h.events,i=false,j;if(l&&l.hasOwnProperty&&l instanceof Array){j=[];f(l,function(n){n=c.get(n);j.push(h.remove(n,m,k))});return j}l=c.get(l);f(g,function(o,n){if(o.obj==l&&o.name==m&&(!k||(o.func==k||o.cfunc==k))){g.splice(n,1);h._remove(l,m,o.cfunc);i=true;return false}});return i},clear:function(l){var j=this,g=j.events,h,k;if(l){l=c.get(l);for(h=g.length-1;h>=0;h--){k=g[h];if(k.obj===l){j._remove(k.obj,k.name,k.cfunc);k.obj=k.cfunc=null;g.splice(h,1)}}}},cancel:function(g){if(!g){return false}this.stop(g);return this.prevent(g)},stop:function(g){if(g.stopPropagation){g.stopPropagation()}else{g.cancelBubble=true}return false},prevent:function(g){if(g.preventDefault){g.preventDefault()}else{g.returnValue=false}return false},destroy:function(){var g=this;f(g.events,function(j,h){g._remove(j.obj,j.name,j.cfunc);j.obj=j.cfunc=null});g.events=[];g=null},_add:function(h,i,g){if(h.attachEvent){h.attachEvent("on"+i,g)}else{if(h.addEventListener){h.addEventListener(i,g,false)}else{h["on"+i]=g}}},_remove:function(i,j,h){if(i){try{if(i.detachEvent){i.detachEvent("on"+j,h)}else{if(i.removeEventListener){i.removeEventListener(j,h,false)}else{i["on"+j]=null}}}catch(g){}}},_pageInit:function(h){var g=this;if(g.domLoaded){return}g.domLoaded=true;f(g.inits,function(i){i()});g.inits=[]},_wait:function(i){var g=this,h=i.document;if(i.tinyMCE_GZ&&tinyMCE_GZ.loaded){g.domLoaded=1;return}if(h.attachEvent){h.attachEvent("onreadystatechange",function(){if(h.readyState==="complete"){h.detachEvent("onreadystatechange",arguments.callee);g._pageInit(i)}});if(h.documentElement.doScroll&&i==i.top){(function(){if(g.domLoaded){return}try{h.documentElement.doScroll("left")}catch(j){setTimeout(arguments.callee,0);return}g._pageInit(i)})()}}else{if(h.addEventListener){g._add(i,"DOMContentLoaded",function(){g._pageInit(i)})}}g._add(i,"load",function(){g._pageInit(i)})},_stoppers:{preventDefault:function(){this.returnValue=false},stopPropagation:function(){this.cancelBubble=true}}});a=d.dom.Event=new d.dom.EventUtils();a._wait(window);d.addUnload(function(){a.destroy()})})(tinymce);(function(a){a.dom.Element=function(f,d){var b=this,e,c;b.settings=d=d||{};b.id=f;b.dom=e=d.dom||a.DOM;if(!a.isIE){c=e.get(b.id)}a.each(("getPos,getRect,getParent,add,setStyle,getStyle,setStyles,setAttrib,setAttribs,getAttrib,addClass,removeClass,hasClass,getOuterHTML,setOuterHTML,remove,show,hide,isHidden,setHTML,get").split(/,/),function(g){b[g]=function(){var h=[f],j;for(j=0;j_';if(j.startContainer==k&&j.endContainer==k){k.body.innerHTML=i}else{j.deleteContents();j.insertNode(f.getRng().createContextualFragment(i))}l=f.dom.get("__caret");j=k.createRange();j.setStartBefore(l);j.setEndBefore(l);f.setRng(j);f.dom.remove("__caret")}else{if(j.item){k.execCommand("Delete",false,null);j=f.getRng()}j.pasteHTML(i)}f.onSetContent.dispatch(f,g)},getStart:function(){var f=this,g=f.getRng(),h;if(g.duplicate||g.item){if(g.item){return g.item(0)}g=g.duplicate();g.collapse(1);h=g.parentElement();if(h&&h.nodeName=="BODY"){return h.firstChild||h}return h}else{h=g.startContainer;if(h.nodeType==1&&h.hasChildNodes()){h=h.childNodes[Math.min(h.childNodes.length-1,g.startOffset)]}if(h&&h.nodeType==3){return h.parentNode}return h}},getEnd:function(){var g=this,h=g.getRng(),i,f;if(h.duplicate||h.item){if(h.item){return h.item(0)}h=h.duplicate();h.collapse(0);i=h.parentElement();if(i&&i.nodeName=="BODY"){return i.lastChild||i}return i}else{i=h.endContainer;f=h.endOffset;if(i.nodeType==1&&i.hasChildNodes()){i=i.childNodes[f>0?f-1:f]}if(i&&i.nodeType==3){return i.parentNode}return i}},getBookmark:function(q,r){var u=this,m=u.dom,g,j,i,n,h,o,p,l="\uFEFF",s;function f(v,x){var t=0;d(m.select(v),function(z,y){if(z==x){t=y}});return t}if(q==2){function k(){var v=u.getRng(true),t=m.getRoot(),x={};function y(B,G){var A=B[G?"startContainer":"endContainer"],F=B[G?"startOffset":"endOffset"],z=[],C,E,D=0;if(A.nodeType==3){if(r){for(C=A.previousSibling;C&&C.nodeType==3;C=C.previousSibling){F+=C.nodeValue.length}}z.push(F)}else{E=A.childNodes;if(F>=E.length){D=1;F=E.length-1}z.push(u.dom.nodeIndex(E[F],r)+D)}for(;A&&A!=t;A=A.parentNode){z.push(u.dom.nodeIndex(A,r))}return z}x.start=y(v,true);if(!u.isCollapsed()){x.end=y(v)}return x}return k()}if(q){return{rng:u.getRng()}}g=u.getRng();i=m.uniqueId();n=tinyMCE.activeEditor.selection.isCollapsed();s="overflow:hidden;line-height:0px";if(g.duplicate||g.item){if(!g.item){j=g.duplicate();g.collapse();g.pasteHTML(''+l+"");if(!n){j.collapse(false);j.pasteHTML(''+l+"")}}else{o=g.item(0);h=o.nodeName;return{name:h,index:f(h,o)}}}else{o=u.getNode();h=o.nodeName;if(h=="IMG"){return{name:h,index:f(h,o)}}j=g.cloneRange();if(!n){j.collapse(false);j.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_end",style:s},l))}g.collapse(true);g.insertNode(m.create("span",{_mce_type:"bookmark",id:i+"_start",style:s},l))}u.moveToBookmark({id:i,keep:1});return{id:i}},moveToBookmark:function(l){var n=this,k=n.dom,i,h,f,m;if(n.tridentSel){n.tridentSel.destroy()}if(l){if(l.start){f=k.createRng();m=k.getRoot();function g(s){var o=l[s?"start":"end"],p,q,r;if(o){for(q=m,p=o.length-1;p>=1;p--){q=q.childNodes[o[p]]}if(s){f.setStart(q,o[0])}else{f.setEnd(q,o[0])}}}g(true);g();n.setRng(f)}else{if(l.id){f=k.createRng();function j(u){var p=k.get(l.id+"_"+u),t,o,r,s,q=l.keep;if(p){t=p.parentNode;if(u=="start"){if(!q){o=k.nodeIndex(p)}else{t=p;o=1}f.setStart(t,o);f.setEnd(t,o)}else{if(!q){o=k.nodeIndex(p)}else{t=p;o=1}f.setEnd(t,o)}if(!q){s=p.previousSibling;r=p.nextSibling;d(c.grep(p.childNodes),function(v){if(v.nodeType==3){v.nodeValue=v.nodeValue.replace(/\uFEFF/g,"")}});while(p=k.get(l.id+"_"+u)){k.remove(p,1)}if(s&&r&&s.nodeType==r.nodeType&&s.nodeType==3){o=s.nodeValue.length;s.appendData(r.nodeValue);k.remove(r);if(u=="start"){f.setStart(s,o);f.setEnd(s,o)}else{f.setEnd(s,o)}}}}}j("start");j("end");n.setRng(f)}else{if(l.name){n.select(k.select(l.name)[l.index])}else{if(l.rng){n.setRng(l.rng)}}}}}},select:function(k,j){var i=this,l=i.dom,g=l.createRng(),f;f=l.nodeIndex(k);g.setStart(k.parentNode,f);g.setEnd(k.parentNode,f+1);if(j){function h(m,o){var n=new c.dom.TreeWalker(m,m);do{if(m.nodeType==3&&c.trim(m.nodeValue).length!=0){if(o){g.setStart(m,0)}else{g.setEnd(m,m.nodeValue.length)}return}if(m.nodeName=="BR"){if(o){g.setStartBefore(m)}else{g.setEndBefore(m)}return}}while(m=(o?n.next():n.prev()))}h(k,1);h(k)}i.setRng(g);return k},isCollapsed:function(){var f=this,h=f.getRng(),g=f.getSel();if(!h||h.item){return false}if(h.compareEndPoints){return h.compareEndPoints("StartToEnd",h)===0}return !g||h.collapsed},collapse:function(f){var g=this,h=g.getRng(),i;if(h.item){i=h.item(0);h=this.win.document.body.createTextRange();h.moveToElementText(i)}h.collapse(!!f);g.setRng(h)},getSel:function(){var g=this,f=this.win;return f.getSelection?f.getSelection():f.document.selection},getRng:function(j){var g=this,h,i;if(j&&g.tridentSel){return g.tridentSel.getRangeAt(0)}try{if(h=g.getSel()){i=h.rangeCount>0?h.getRangeAt(0):(h.createRange?h.createRange():g.win.document.createRange())}}catch(f){}if(!i){i=g.win.document.createRange?g.win.document.createRange():g.win.document.body.createTextRange()}return i},setRng:function(i){var h,g=this;if(!g.tridentSel){h=g.getSel();if(h){h.removeAllRanges();h.addRange(i)}}else{if(i.cloneRange){g.tridentSel.addRange(i);return}try{i.select()}catch(f){}}},setNode:function(g){var f=this;f.setContent(f.dom.getOuterHTML(g));return g},getNode:function(){var g=this,f=g.getRng(),h=g.getSel(),i;if(f.setStart){if(!f){return g.dom.getRoot()}i=f.commonAncestorContainer;if(!f.collapsed){if(f.startContainer==f.endContainer){if(f.startOffset-f.endOffset<2){if(f.startContainer.hasChildNodes()){i=f.startContainer.childNodes[f.startOffset]}}}if(c.isWebKit&&h.anchorNode&&h.anchorNode.nodeType==1){return h.anchorNode.childNodes[h.anchorOffset]}}if(i&&i.nodeType==3){return i.parentNode}return i}return f.item?f.item(0):f.parentElement()},getSelectedBlocks:function(g,f){var i=this,j=i.dom,m,h,l,k=[];m=j.getParent(g||i.getStart(),j.isBlock);h=j.getParent(f||i.getEnd(),j.isBlock);if(m){k.push(m)}if(m&&h&&m!=h){l=m;while((l=l.nextSibling)&&l!=h){if(j.isBlock(l)){k.push(l)}}}if(h&&m!=h){k.push(h)}return k},destroy:function(g){var f=this;f.win=null;if(f.tridentSel){f.tridentSel.destroy()}if(!g){c.removeUnload(f.destroy)}}})})(tinymce);(function(a){a.create("tinymce.dom.XMLWriter",{node:null,XMLWriter:function(c){function b(){var e=document.implementation;if(!e||!e.createDocument){try{return new ActiveXObject("MSXML2.DOMDocument")}catch(d){}try{return new ActiveXObject("Microsoft.XmlDom")}catch(d){}}else{return e.createDocument("","",null)}}this.doc=b();this.valid=a.isOpera||a.isWebKit;this.reset()},reset:function(){var b=this,c=b.doc;if(c.firstChild){c.removeChild(c.firstChild)}b.node=c.appendChild(c.createElement("html"))},writeStartElement:function(c){var b=this;b.node=b.node.appendChild(b.doc.createElement(c))},writeAttribute:function(c,b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.setAttribute(c,b)},writeEndElement:function(){this.node=this.node.parentNode},writeFullEndElement:function(){var b=this,c=b.node;c.appendChild(b.doc.createTextNode(""));b.node=c.parentNode},writeText:function(b){if(this.valid){b=b.replace(/>/g,"%MCGT%")}this.node.appendChild(this.doc.createTextNode(b))},writeCDATA:function(b){this.node.appendChild(this.doc.createCDATASection(b))},writeComment:function(b){if(a.isIE){b=b.replace(/^\-|\-$/g," ")}this.node.appendChild(this.doc.createComment(b.replace(/\-\-/g," ")))},getContent:function(){var b;b=this.doc.xml||new XMLSerializer().serializeToString(this.doc);b=b.replace(/<\?[^?]+\?>||<\/html>||]+>/g,"");b=b.replace(/ ?\/>/g," />");if(this.valid){b=b.replace(/\%MCGT%/g,">")}return b}})})(tinymce);(function(a){a.create("tinymce.dom.StringWriter",{str:null,tags:null,count:0,settings:null,indent:null,StringWriter:function(b){this.settings=a.extend({indent_char:" ",indentation:0},b);this.reset()},reset:function(){this.indent="";this.str="";this.tags=[];this.count=0},writeStartElement:function(b){this._writeAttributesEnd();this.writeRaw("<"+b);this.tags.push(b);this.inAttr=true;this.count++;this.elementCount=this.count},writeAttribute:function(d,b){var c=this;c.writeRaw(" "+c.encode(d)+'="'+c.encode(b)+'"')},writeEndElement:function(){var b;if(this.tags.length>0){b=this.tags.pop();if(this._writeAttributesEnd(1)){this.writeRaw("")}if(this.settings.indentation>0){this.writeRaw("\n")}}},writeFullEndElement:function(){if(this.tags.length>0){this._writeAttributesEnd();this.writeRaw("");if(this.settings.indentation>0){this.writeRaw("\n")}}},writeText:function(b){this._writeAttributesEnd();this.writeRaw(this.encode(b));this.count++},writeCDATA:function(b){this._writeAttributesEnd();this.writeRaw("");this.count++},writeComment:function(b){this._writeAttributesEnd();this.writeRaw("");this.count++},writeRaw:function(b){this.str+=b},encode:function(b){return b.replace(/[<>&"]/g,function(c){switch(c){case"<":return"<";case">":return">";case"&":return"&";case'"':return"""}return c})},getContent:function(){return this.str},_writeAttributesEnd:function(b){if(!this.inAttr){return}this.inAttr=false;if(b&&this.elementCount==this.count){this.writeRaw(" />");return false}this.writeRaw(">");return true}})})(tinymce);(function(e){var g=e.extend,f=e.each,b=e.util.Dispatcher,d=e.isIE,a=e.isGecko;function c(h){return h.replace(/([?+*])/g,".$1")}e.create("tinymce.dom.Serializer",{Serializer:function(j){var i=this;i.key=0;i.onPreProcess=new b(i);i.onPostProcess=new b(i);try{i.writer=new e.dom.XMLWriter()}catch(h){i.writer=new e.dom.StringWriter()}i.settings=j=g({dom:e.DOM,valid_nodes:0,node_filter:0,attr_filter:0,invalid_attrs:/^(_mce_|_moz_|sizset|sizcache)/,closed:/^(br|hr|input|meta|img|link|param|area)$/,entity_encoding:"named",entities:"160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro",valid_elements:"*[*]",extended_valid_elements:0,invalid_elements:0,fix_table_elements:1,fix_list_elements:true,fix_content_duplication:true,convert_fonts_to_spans:false,font_size_classes:0,apply_source_formatting:0,indent_mode:"simple",indent_char:"\t",indent_levels:1,remove_linebreaks:1,remove_redundant_brs:1,element_format:"xhtml"},j);i.dom=j.dom;i.schema=j.schema;if(j.entity_encoding=="named"&&!j.entities){j.entity_encoding="raw"}if(j.remove_redundant_brs){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/(
    \s*)+<\/(p|h[1-6]|div|li)>/gi,function(n,m,o){if(/^
    \s*<\//.test(n)){return""}return n})})}if(j.element_format=="html"){i.onPostProcess.add(function(k,l){l.content=l.content.replace(/<([^>]+) \/>/g,"<$1>")})}if(j.fix_list_elements){i.onPreProcess.add(function(v,s){var l,z,y=["ol","ul"],u,t,q,k=/^(OL|UL)$/,A;function m(r,x){var o=x.split(","),p;while((r=r.previousSibling)!=null){for(p=0;p=1767){f(i.dom.select("p table",l.node).reverse(),function(p){var o=i.dom.getParent(p.parentNode,"table,p");if(o.nodeName!="TABLE"){try{i.dom.split(o,p)}catch(m){}}})}})}},setEntities:function(o){var n=this,j,m,h={},k;if(n.entityLookup){return}j=o.split(",");for(m=0;m1){f(q[1].split("|"),function(u){var p={},t;k=k||[];u=u.replace(/::/g,"~");u=/^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(u);u[2]=u[2].replace(/~/g,":");if(u[1]=="!"){r=r||[];r.push(u[2])}if(u[1]=="-"){for(t=0;t=1767)){p=j.createHTMLDocument("");f(r.nodeName=="BODY"?r.childNodes:[r],function(h){p.body.appendChild(p.importNode(h,true))});if(r.nodeName!="BODY"){r=p.body.firstChild}else{r=p.body}i=k.dom.doc;k.dom.doc=p}k.key=""+(parseInt(k.key)+1);if(!q.no_events){q.node=r;k.onPreProcess.dispatch(k,q)}k.writer.reset();k._info=q;k._serializeNode(r,q.getInner);q.content=k.writer.getContent();if(i){k.dom.doc=i}if(!q.no_events){k.onPostProcess.dispatch(k,q)}k._postProcess(q);q.node=null;return e.trim(q.content)},_postProcess:function(n){var i=this,k=i.settings,j=n.content,m=[],l;if(n.format=="html"){l=i._protect({content:j,patterns:[{pattern:/(]*>)(.*?)(<\/script>)/g},{pattern:/(]*>)(.*?)(<\/noscript>)/g},{pattern:/(]*>)(.*?)(<\/style>)/g},{pattern:/(]*>)(.*?)(<\/pre>)/g,encode:1},{pattern:/()/g}]});j=l.content;if(k.entity_encoding!=="raw"){j=i._encode(j)}if(!n.set){j=j.replace(/

    \s+<\/p>|]+)>\s+<\/p>/g,k.entity_encoding=="numeric"?" 

    ":" 

    ");if(k.remove_linebreaks){j=j.replace(/\r?\n|\r/g," ");j=j.replace(/(<[^>]+>)\s+/g,"$1 ");j=j.replace(/\s+(<\/[^>]+>)/g," $1");j=j.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g,"<$1 $2>");j=j.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g,"<$1>");j=j.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g,"")}if(k.apply_source_formatting&&k.indent_mode=="simple"){j=j.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g,"\n<$1$2$3>\n");j=j.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g,"\n<$1$2>");j=j.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g,"\n");j=j.replace(/\n\n/g,"\n")}}j=i._unprotect(j,l);j=j.replace(//g,"");if(k.entity_encoding=="raw"){j=j.replace(/

     <\/p>|]+)> <\/p>/g,"\u00a0

    ")}j=j.replace(/]+|)>([\s\S]*?)<\/noscript>/g,function(h,p,o){return""+i.dom.decode(o.replace(//g,""))+""})}n.content=j},_serializeNode:function(D,I){var z=this,A=z.settings,x=z.writer,q,j,u,F,E,H,B,h,y,k,r,C,p,m,G,o;if(!A.node_filter||A.node_filter(D)){switch(D.nodeType){case 1:if(D.hasAttribute?D.hasAttribute("_mce_bogus"):D.getAttribute("_mce_bogus")){return}p=G=false;q=D.hasChildNodes();k=D.getAttribute("_mce_name")||D.nodeName.toLowerCase();o=D.getAttribute("_mce_type");if(o){if(!z._info.cleanup){p=true;return}else{G=1}}if(d){if(D.scopeName!=="HTML"&&D.scopeName!=="html"){k=D.scopeName+":"+k}}if(k.indexOf("mce:")===0){k=k.substring(4)}if(!G){if(!z.validElementsRE||!z.validElementsRE.test(k)||(z.invalidElementsRE&&z.invalidElementsRE.test(k))||I){p=true;break}}if(d){if(A.fix_content_duplication){if(D._mce_serialized==z.key){return}D._mce_serialized=z.key}if(k.charAt(0)=="/"){k=k.substring(1)}}else{if(a){if(D.nodeName==="BR"&&D.getAttribute("type")=="_moz"){return}}}if(A.validate_children){if(z.elementName&&!z.schema.isValid(z.elementName,k)){p=true;break}z.elementName=k}r=z.findRule(k);if(!r){p=true;break}k=r.name||k;m=A.closed.test(k);if((!q&&r.noEmpty)||(d&&!k)){p=true;break}if(r.requiredAttribs){H=r.requiredAttribs;for(F=H.length-1;F>=0;F--){if(this.dom.getAttrib(D,H[F])!==""){break}}if(F==-1){p=true;break}}x.writeStartElement(k);if(r.attribs){for(F=0,B=r.attribs,E=B.length;F-1;F--){h=B[F];if(h.specified){H=h.nodeName.toLowerCase();if(A.invalid_attrs.test(H)||!r.validAttribsRE.test(H)){continue}C=z.findAttribRule(r,H);y=z._getAttrib(D,C,H);if(y!==null){x.writeAttribute(H,y)}}}}if(o&&G){x.writeAttribute("_mce_type",o)}if(k==="script"&&e.trim(D.innerHTML)){x.writeText("// ");x.writeCDATA(D.innerHTML.replace(/|<\[CDATA\[|\]\]>/g,""));q=false;break}if(r.padd){if(q&&(u=D.firstChild)&&u.nodeType===1&&D.childNodes.length===1){if(u.hasAttribute?u.hasAttribute("_mce_bogus"):u.getAttribute("_mce_bogus")){x.writeText("\u00a0")}}else{if(!q){x.writeText("\u00a0")}}}break;case 3:if(A.validate_children&&z.elementName&&!z.schema.isValid(z.elementName,"#text")){return}return x.writeText(D.nodeValue);case 4:return x.writeCDATA(D.nodeValue);case 8:return x.writeComment(D.nodeValue)}}else{if(D.nodeType==1){q=D.hasChildNodes()}}if(q&&!m){u=D.firstChild;while(u){z._serializeNode(u);z.elementName=k;u=u.nextSibling}}if(!p){if(!m){x.writeFullEndElement()}else{x.writeEndElement()}}},_protect:function(j){var i=this;j.items=j.items||[];function h(l){return l.replace(/[\r\n\\]/g,function(m){if(m==="\n"){return"\\n"}else{if(m==="\\"){return"\\\\"}}return"\\r"})}function k(l){return l.replace(/\\[\\rn]/g,function(m){if(m==="\\n"){return"\n"}else{if(m==="\\\\"){return"\\"}}return"\r"})}f(j.patterns,function(l){j.content=k(h(j.content).replace(l.pattern,function(n,o,m,p){m=k(m);if(l.encode){m=i._encode(m)}j.items.push(m);return o+""+p}))});return j},_unprotect:function(i,j){i=i.replace(/\"))}if(a&&j.ListBox){if(a.Button||a.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarEnd"},b.createHTML("span",null,""))}}if(b.stdMode){e+=''+j.renderHTML()+""}else{e+=""+j.renderHTML()+""}if(f&&j.ListBox){if(f.Button||f.SplitButton){e+=b.createHTML("td",{"class":"mceToolbarStart"},b.createHTML("span",null,""))}}}g="mceToolbarEnd";if(j.Button){g+=" mceToolbarEndButton"}else{if(j.SplitButton){g+=" mceToolbarEndSplitButton"}else{if(j.ListBox){g+=" mceToolbarEndListBox"}}}e+=b.createHTML("td",{"class":g},b.createHTML("span",null,""));return b.createHTML("table",{id:l.id,"class":"mceToolbar"+(m["class"]?" "+m["class"]:""),cellpadding:"0",cellspacing:"0",align:l.settings.align||""},""+e+"")}});(function(b){var a=b.util.Dispatcher,c=b.each;b.create("tinymce.AddOnManager",{items:[],urls:{},lookup:{},onAdd:new a(this),get:function(d){return this.lookup[d]},requireLangPack:function(e){var d=b.settings;if(d&&d.language){b.ScriptLoader.add(this.urls[e]+"/langs/"+d.language+".js")}},add:function(e,d){this.items.push(d);this.lookup[e]=d;this.onAdd.dispatch(this,e,d);return d},load:function(h,e,d,g){var f=this;if(f.urls[h]){return}if(e.indexOf("/")!=0&&e.indexOf("://")==-1){e=b.baseURL+"/"+e}f.urls[h]=e.substring(0,e.lastIndexOf("/"));b.ScriptLoader.add(e,d,g)}});b.PluginManager=new b.AddOnManager();b.ThemeManager=new b.AddOnManager()}(tinymce));(function(j){var g=j.each,d=j.extend,k=j.DOM,i=j.dom.Event,f=j.ThemeManager,b=j.PluginManager,e=j.explode,h=j.util.Dispatcher,a,c=0;j.documentBaseURL=window.location.href.replace(/[\?#].*$/,"").replace(/[\/\\][^\/]+$/,"");if(!/[\/\\]$/.test(j.documentBaseURL)){j.documentBaseURL+="/"}j.baseURL=new j.util.URI(j.documentBaseURL).toAbsolute(j.baseURL);j.baseURI=new j.util.URI(j.baseURL);j.onBeforeUnload=new h(j);i.add(window,"beforeunload",function(l){j.onBeforeUnload.dispatch(j,l)});j.onAddEditor=new h(j);j.onRemoveEditor=new h(j);j.EditorManager=d(j,{editors:[],i18n:{},activeEditor:null,init:function(q){var n=this,p,l=j.ScriptLoader,u,o=[],m;function r(x,y,t){var v=x[y];if(!v){return}if(j.is(v,"string")){t=v.replace(/\.\w+$/,"");t=t?j.resolve(t):0;v=j.resolve(v)}return v.apply(t||this,Array.prototype.slice.call(arguments,2))}q=d({theme:"simple",language:"en"},q);n.settings=q;i.add(document,"init",function(){var s,v;r(q,"onpageload");switch(q.mode){case"exact":s=q.elements||"";if(s.length>0){g(e(s),function(x){if(k.get(x)){m=new j.Editor(x,q);o.push(m);m.render(1)}else{g(document.forms,function(y){g(y.elements,function(z){if(z.name===x){x="mce_editor_"+c++;k.setAttrib(z,"id",x);m=new j.Editor(x,q);o.push(m);m.render(1)}})})}})}break;case"textareas":case"specific_textareas":function t(y,x){return x.constructor===RegExp?x.test(y.className):k.hasClass(y,x)}g(k.select("textarea"),function(x){if(q.editor_deselector&&t(x,q.editor_deselector)){return}if(!q.editor_selector||t(x,q.editor_selector)){u=k.get(x.name);if(!x.id&&!u){x.id=x.name}if(!x.id||n.get(x.id)){x.id=k.uniqueId()}m=new j.Editor(x.id,q);o.push(m);m.render(1)}});break}if(q.oninit){s=v=0;g(o,function(x){v++;if(!x.initialized){x.onInit.add(function(){s++;if(s==v){r(q,"oninit")}})}else{s++}if(s==v){r(q,"oninit")}})}})},get:function(l){if(l===a){return this.editors}return this.editors[l]},getInstanceById:function(l){return this.get(l)},add:function(m){var l=this,n=l.editors;n[m.id]=m;n.push(m);l._setActive(m);l.onAddEditor.dispatch(l,m);if(j.adapter){j.adapter.patchEditor(m)}return m},remove:function(n){var m=this,l,o=m.editors;if(!o[n.id]){return null}delete o[n.id];for(l=0;l':"",visual_table_class:"mceItemTable",visual:1,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",apply_source_formatting:1,directionality:"ltr",forced_root_block:"p",valid_elements:"@[id|class|style|title|dir';if(F.document_base_url!=m.documentBaseURL){E.iframeHTML+=''}E.iframeHTML+='';if(m.relaxedDomain){E.iframeHTML+=''; - - bi = s.body_id || 'tinymce'; - if (bi.indexOf('=') != -1) { - bi = t.getParam('body_id', '', 'hash'); - bi = bi[t.id] || bi; - } - - bc = s.body_class || ''; - if (bc.indexOf('=') != -1) { - bc = t.getParam('body_class', '', 'hash'); - bc = bc[t.id] || ''; - } - - t.iframeHTML += ''; - - // Domain relaxing enabled, then set document domain - if (tinymce.relaxedDomain) { - // We need to write the contents here in IE since multiple writes messes up refresh button and back button - if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; - else if (tinymce.isOpera) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; - } - - // Create iframe - n = DOM.add(o.iframeContainer, 'iframe', { - id : t.id + "_ifr", - src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 - frameBorder : '0', - style : { - width : '100%', - height : h - } - }); - - t.contentAreaContainer = o.iframeContainer; - DOM.get(o.editorContainer).style.display = t.orgDisplay; - DOM.get(t.id).style.display = 'none'; - - if (!isIE || !tinymce.relaxedDomain) - t.setupIframe(); - - e = n = o = null; // Cleanup - }, - - setupIframe : function() { - var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; - - // Setup iframe body - if (!isIE || !tinymce.relaxedDomain) { - d.open(); - d.write(t.iframeHTML); - d.close(); - } - - // Design mode needs to be added here Ctrl+A will fail otherwise - if (!isIE) { - try { - if (!s.readonly) - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - - // IE needs to use contentEditable or it will display non secure items for HTTPS - if (isIE) { - // It will not steal focus if we hide it while setting contentEditable - b = t.getBody(); - DOM.hide(b); - - if (!s.readonly) - b.contentEditable = true; - - DOM.show(b); - } - - t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { - keep_values : true, - url_converter : t.convertURL, - url_converter_scope : t, - hex_colors : s.force_hex_style_colors, - class_filter : s.class_filter, - update_styles : 1, - fix_ie_paragraphs : 1, - valid_styles : s.valid_styles - }); - - t.schema = new tinymce.dom.Schema(); - - t.serializer = new tinymce.dom.Serializer(extend(s, { - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - dom : t.dom, - schema : t.schema - })); - - t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); - - t.formatter = new tinymce.Formatter(this); - - // Register default formats - t.formatter.register({ - alignleft : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, - {selector : 'img,table', styles : {'float' : 'left'}} - ], - - aligncenter : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, - {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, - {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} - ], - - alignright : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, - {selector : 'img,table', styles : {'float' : 'right'}} - ], - - alignfull : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} - ], - - bold : [ - {inline : 'strong'}, - {inline : 'span', styles : {fontWeight : 'bold'}}, - {inline : 'b'} - ], - - italic : [ - {inline : 'em'}, - {inline : 'span', styles : {fontStyle : 'italic'}}, - {inline : 'i'} - ], - - underline : [ - {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, - {inline : 'u'} - ], - - strikethrough : [ - {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, - {inline : 'u'} - ], - - forecolor : {inline : 'span', styles : {color : '%value'}}, - hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, - fontname : {inline : 'span', styles : {fontFamily : '%value'}}, - fontsize : {inline : 'span', styles : {fontSize : '%value'}}, - blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, - - removeformat : [ - {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, - {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, - {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} - ] - }); - - // Register default block formats - each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { - t.formatter.register(name, {block : name, remove : 'all'}); - }); - - // Register user defined formats - t.formatter.register(t.settings.formats); - - t.undoManager = new tinymce.UndoManager(t); - - // Pass through - t.undoManager.onAdd.add(function(um, l) { - if (!l.initial) - return t.onChange.dispatch(t, l, um); - }); - - t.undoManager.onUndo.add(function(um, l) { - return t.onUndo.dispatch(t, l, um); - }); - - t.undoManager.onRedo.add(function(um, l) { - return t.onRedo.dispatch(t, l, um); - }); - - t.forceBlocks = new tinymce.ForceBlocks(t, { - forced_root_block : s.forced_root_block - }); - - t.editorCommands = new tinymce.EditorCommands(t); - - // Pass through - t.serializer.onPreProcess.add(function(se, o) { - return t.onPreProcess.dispatch(t, o, se); - }); - - t.serializer.onPostProcess.add(function(se, o) { - return t.onPostProcess.dispatch(t, o, se); - }); - - t.onPreInit.dispatch(t); - - if (!s.gecko_spellcheck) - t.getBody().spellcheck = 0; - - if (!s.readonly) - t._addEvents(); - - t.controlManager.onPostRender.dispatch(t, t.controlManager); - t.onPostRender.dispatch(t); - - if (s.directionality) - t.getBody().dir = s.directionality; - - if (s.nowrap) - t.getBody().style.whiteSpace = "nowrap"; - - if (s.custom_elements) { - function handleCustom(ed, o) { - each(explode(s.custom_elements), function(v) { - var n; - - if (v.indexOf('~') === 0) { - v = v.substring(1); - n = 'span'; - } else - n = 'div'; - - o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); - o.content = o.content.replace(new RegExp('', 'g'), ''); - }); - }; - - t.onBeforeSetContent.add(handleCustom); - t.onPostProcess.add(function(ed, o) { - if (o.set) - handleCustom(ed, o); - }); - } - - if (s.handle_node_change_callback) { - t.onNodeChange.add(function(ed, cm, n) { - t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); - }); - } - - if (s.save_callback) { - t.onSaveContent.add(function(ed, o) { - var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); - - if (h) - o.content = h; - }); - } - - if (s.onchange_callback) { - t.onChange.add(function(ed, l) { - t.execCallback('onchange_callback', t, l); - }); - } - - if (s.convert_newlines_to_brs) { - t.onBeforeSetContent.add(function(ed, o) { - if (o.initial) - o.content = o.content.replace(/\r?\n/g, '
    '); - }); - } - - if (s.fix_nesting && isIE) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t._fixNesting(o.content); - }); - } - - if (s.preformatted) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^\s*/, ''); - o.content = o.content.replace(/<\/pre>\s*$/, ''); - - if (o.set) - o.content = '
    ' + o.content + '
    '; - }); - } - - if (s.verify_css_classes) { - t.serializer.attribValueFilter = function(n, v) { - var s, cl; - - if (n == 'class') { - // Build regexp for classes - if (!t.classesRE) { - cl = t.dom.getClasses(); - - if (cl.length > 0) { - s = ''; - - each (cl, function(o) { - s += (s ? '|' : '') + o['class']; - }); - - t.classesRE = new RegExp('(' + s + ')', 'gi'); - } - } - - return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; - } - - return v; - }; - } - - if (s.cleanup_callback) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - }); - - t.onPreProcess.add(function(ed, o) { - if (o.set) - t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); - - if (o.get) - t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); - }); - - t.onPostProcess.add(function(ed, o) { - if (o.set) - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - - if (o.get) - o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); - }); - } - - if (s.save_callback) { - t.onGetContent.add(function(ed, o) { - if (o.save) - o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); - }); - } - - if (s.handle_event_callback) { - t.onEvent.add(function(ed, e, o) { - if (t.execCallback('handle_event_callback', e, ed, o) === false) - Event.cancel(e); - }); - } - - // Add visual aids when new contents is added - t.onSetContent.add(function() { - t.addVisual(t.getBody()); - }); - - // Remove empty contents - if (s.padd_empty_editor) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); - }); - } - - if (isGecko) { - // Fix gecko link bug, when a link is placed at the end of block elements there is - // no way to move the caret behind the link. This fix adds a bogus br element after the link - function fixLinks(ed, o) { - each(ed.dom.select('a'), function(n) { - var pn = n.parentNode; - - if (ed.dom.isBlock(pn) && pn.lastChild === n) - ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); - }); - }; - - t.onExecCommand.add(function(ed, cmd) { - if (cmd === 'CreateLink') - fixLinks(ed); - }); - - t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); - - if (!s.readonly) { - try { - // Design mode must be set here once again to fix a bug where - // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again - d.designMode = 'Off'; - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - } - - // A small timeout was needed since firefox will remove. Bug: #1838304 - setTimeout(function () { - if (t.removed) - return; - - t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); - t.startContent = t.getContent({format : 'raw'}); - t.initialized = true; - - t.onInit.dispatch(t); - t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); - t.execCallback('init_instance_callback', t); - t.focus(true); - t.nodeChanged({initial : 1}); - - // Load specified content CSS last - if (s.content_css) { - tinymce.each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } - - // Handle auto focus - if (s.auto_focus) { - setTimeout(function () { - var ed = tinymce.get(s.auto_focus); - - ed.selection.select(ed.getBody(), 1); - ed.selection.collapse(1); - ed.getWin().focus(); - }, 100); - } - }, 1); - - e = null; - }, - - - focus : function(sf) { - var oed, t = this, ce = t.settings.content_editable; - - if (!sf) { - // Is not content editable - if (!ce) - t.getWin().focus(); - - } - - if (tinymce.activeEditor != t) { - if ((oed = tinymce.activeEditor) != null) - oed.onDeactivate.dispatch(oed, t); - - t.onActivate.dispatch(t, oed); - } - - tinymce._setActive(t); - }, - - execCallback : function(n) { - var t = this, f = t.settings[n], s; - - if (!f) - return; - - // Look through lookup - if (t.callbackLookup && (s = t.callbackLookup[n])) { - f = s.func; - s = s.scope; - } - - if (is(f, 'string')) { - s = f.replace(/\.\w+$/, ''); - s = s ? tinymce.resolve(s) : 0; - f = tinymce.resolve(f); - t.callbackLookup = t.callbackLookup || {}; - t.callbackLookup[n] = {func : f, scope : s}; - } - - return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); - }, - - translate : function(s) { - var c = this.settings.language || 'en', i18n = tinymce.i18n; - - if (!s) - return ''; - - return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { - return i18n[c + '.' + b] || '{#' + b + '}'; - }); - }, - - getLang : function(n, dv) { - return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); - }, - - getParam : function(n, dv, ty) { - var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; - - if (ty === 'hash') { - o = {}; - - if (is(v, 'string')) { - each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { - v = v.split('='); - - if (v.length > 1) - o[tr(v[0])] = tr(v[1]); - else - o[tr(v[0])] = tr(v); - }); - } else - o = v; - - return o; - } - - return v; - }, - - nodeChanged : function(o) { - var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); - - // Fix for bug #1896577 it seems that this can not be fired while the editor is loading - if (t.initialized) { - o = o || {}; - n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state - - // Get parents and add them to object - o.parents = []; - t.dom.getParent(n, function(node) { - if (node.nodeName == 'BODY') - return true; - - o.parents.push(node); - }); - - t.onNodeChange.dispatch( - t, - o ? o.controlManager || t.controlManager : t.controlManager, - n, - s.isCollapsed(), - o - ); - } - }, - - addButton : function(n, s) { - var t = this; - - t.buttons = t.buttons || {}; - t.buttons[n] = s; - }, - - addCommand : function(n, f, s) { - this.execCommands[n] = {func : f, scope : s || this}; - }, - - addQueryStateHandler : function(n, f, s) { - this.queryStateCommands[n] = {func : f, scope : s || this}; - }, - - addQueryValueHandler : function(n, f, s) { - this.queryValueCommands[n] = {func : f, scope : s || this}; - }, - - addShortcut : function(pa, desc, cmd_func, sc) { - var t = this, c; - - if (!t.settings.custom_shortcuts) - return false; - - t.shortcuts = t.shortcuts || {}; - - if (is(cmd_func, 'string')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c, false, null); - }; - } - - if (is(cmd_func, 'object')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c[0], c[1], c[2]); - }; - } - - each(explode(pa), function(pa) { - var o = { - func : cmd_func, - scope : sc || this, - desc : desc, - alt : false, - ctrl : false, - shift : false - }; - - each(explode(pa, '+'), function(v) { - switch (v) { - case 'alt': - case 'ctrl': - case 'shift': - o[v] = true; - break; - - default: - o.charCode = v.charCodeAt(0); - o.keyCode = v.toUpperCase().charCodeAt(0); - } - }); - - t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; - }); - - return true; - }, - - execCommand : function(cmd, ui, val, a) { - var t = this, s = 0, o, st; - - if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) - t.focus(); - - o = {}; - t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); - if (o.terminate) - return false; - - // Command callback - if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Registred commands - if (o = t.execCommands[cmd]) { - st = o.func.call(o.scope, ui, val); - - // Fall through on true - if (st !== true) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return st; - } - } - - // Plugin commands - each(t.plugins, function(p) { - if (p.execCommand && p.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - s = 1; - return false; - } - }); - - if (s) - return true; - - // Theme commands - if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Execute global commands - if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Editor commands - if (t.editorCommands.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Browser commands - t.getDoc().execCommand(cmd, ui, val); - t.onExecCommand.dispatch(t, cmd, ui, val, a); - }, - - queryCommandState : function(cmd) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryStateCommands[cmd]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandState(cmd); - if (o !== -1) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandState(cmd); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - queryCommandValue : function(c) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryValueCommands[c]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandValue(c); - if (is(o)) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandValue(c); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - show : function() { - var t = this; - - DOM.show(t.getContainer()); - DOM.hide(t.id); - t.load(); - }, - - hide : function() { - var t = this, d = t.getDoc(); - - // Fixed bug where IE has a blinking cursor left from the editor - if (isIE && d) - d.execCommand('SelectAll'); - - // We must save before we hide so Safari doesn't crash - t.save(); - DOM.hide(t.getContainer()); - DOM.setStyle(t.id, 'display', t.orgDisplay); - }, - - isHidden : function() { - return !DOM.isHidden(this.id); - }, - - setProgressState : function(b, ti, o) { - this.onSetProgressState.dispatch(this, b, ti, o); - - return b; - }, - - load : function(o) { - var t = this, e = t.getElement(), h; - - if (e) { - o = o || {}; - o.load = true; - - // Double encode existing entities in the value - h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); - o.element = e; - - if (!o.no_events) - t.onLoadContent.dispatch(t, o); - - o.element = e = null; - - return h; - } - }, - - save : function(o) { - var t = this, e = t.getElement(), h, f; - - if (!e || !t.initialized) - return; - - o = o || {}; - o.save = true; - - // Add undo level will trigger onchange event - if (!o.no_events) { - t.undoManager.typing = 0; - t.undoManager.add(); - } - - o.element = e; - h = o.content = t.getContent(o); - - if (!o.no_events) - t.onSaveContent.dispatch(t, o); - - h = o.content; - - if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { - e.innerHTML = h; - - // Update hidden form element - if (f = DOM.getParent(t.id, 'form')) { - each(f.elements, function(e) { - if (e.name == t.id) { - e.value = h; - return false; - } - }); - } - } else - e.value = h; - - o.element = e = null; - - return h; - }, - - setContent : function(h, o) { - var t = this; - - o = o || {}; - o.format = o.format || 'html'; - o.set = true; - o.content = h; - - if (!o.no_events) - t.onBeforeSetContent.dispatch(t, o); - - // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content - // It will also be impossible to place the caret in the editor unless there is a BR element present - if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { - o.content = t.dom.setHTML(t.getBody(), '
    '); - o.format = 'raw'; - } - - o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); - } - - if (!o.no_events) - t.onSetContent.dispatch(t, o); - - return o.content; - }, - - getContent : function(o) { - var t = this, h; - - o = o || {}; - o.format = o.format || 'html'; - o.get = true; - - if (!o.no_events) - t.onBeforeGetContent.dispatch(t, o); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - h = t.serializer.serialize(t.getBody(), o); - } else - h = t.getBody().innerHTML; - - h = h.replace(/^\s*|\s*$/g, ''); - o.content = h; - - if (!o.no_events) - t.onGetContent.dispatch(t, o); - - return o.content; - }, - - isDirty : function() { - var t = this; - - return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; - }, - - getContainer : function() { - var t = this; - - if (!t.container) - t.container = DOM.get(t.editorContainer || t.id + '_parent'); - - return t.container; - }, - - getContentAreaContainer : function() { - return this.contentAreaContainer; - }, - - getElement : function() { - return DOM.get(this.settings.content_element || this.id); - }, - - getWin : function() { - var t = this, e; - - if (!t.contentWindow) { - e = DOM.get(t.id + "_ifr"); - - if (e) - t.contentWindow = e.contentWindow; - } - - return t.contentWindow; - }, - - getDoc : function() { - var t = this, w; - - if (!t.contentDocument) { - w = t.getWin(); - - if (w) - t.contentDocument = w.document; - } - - return t.contentDocument; - }, - - getBody : function() { - return this.bodyElement || this.getDoc().body; - }, - - convertURL : function(u, n, e) { - var t = this, s = t.settings; - - // Use callback instead - if (s.urlconverter_callback) - return t.execCallback('urlconverter_callback', u, e, true, n); - - // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs - if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) - return u; - - // Convert to relative - if (s.relative_urls) - return t.documentBaseURI.toRelative(u); - - // Convert to absolute - u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); - - return u; - }, - - addVisual : function(e) { - var t = this, s = t.settings; - - e = e || t.getBody(); - - if (!is(t.hasVisual)) - t.hasVisual = s.visual; - - each(t.dom.select('table,a', e), function(e) { - var v; - - switch (e.nodeName) { - case 'TABLE': - v = t.dom.getAttrib(e, 'border'); - - if (!v || v == '0') { - if (t.hasVisual) - t.dom.addClass(e, s.visual_table_class); - else - t.dom.removeClass(e, s.visual_table_class); - } - - return; - - case 'A': - v = t.dom.getAttrib(e, 'name'); - - if (v) { - if (t.hasVisual) - t.dom.addClass(e, 'mceItemAnchor'); - else - t.dom.removeClass(e, 'mceItemAnchor'); - } - - return; - } - }); - - t.onVisualAid.dispatch(t, e, t.hasVisual); - }, - - remove : function() { - var t = this, e = t.getContainer(); - - t.removed = 1; // Cancels post remove event execution - t.hide(); - - t.execCallback('remove_instance_callback', t); - t.onRemove.dispatch(t); - - // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command - t.onExecCommand.listeners = []; - - tinymce.remove(t); - DOM.remove(e); - }, - - destroy : function(s) { - var t = this; - - // One time is enough - if (t.destroyed) - return; - - if (!s) { - tinymce.removeUnload(t.destroy); - tinyMCE.onBeforeUnload.remove(t._beforeUnload); - - // Manual destroy - if (t.theme && t.theme.destroy) - t.theme.destroy(); - - // Destroy controls, selection and dom - t.controlManager.destroy(); - t.selection.destroy(); - t.dom.destroy(); - - // Remove all events - - // Don't clear the window or document if content editable - // is enabled since other instances might still be present - if (!t.settings.content_editable) { - Event.clear(t.getWin()); - Event.clear(t.getDoc()); - } - - Event.clear(t.getBody()); - Event.clear(t.formElement); - } - - if (t.formElement) { - t.formElement.submit = t.formElement._mceOldSubmit; - t.formElement._mceOldSubmit = null; - } - - t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; - - if (t.selection) - t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; - - t.destroyed = 1; - }, - - // Internal functions - - _addEvents : function() { - // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset - var t = this, i, s = t.settings, lo = { - mouseup : 'onMouseUp', - mousedown : 'onMouseDown', - click : 'onClick', - keyup : 'onKeyUp', - keydown : 'onKeyDown', - keypress : 'onKeyPress', - submit : 'onSubmit', - reset : 'onReset', - contextmenu : 'onContextMenu', - dblclick : 'onDblClick', - paste : 'onPaste' // Doesn't work in all browsers yet - }; - - function eventHandler(e, o) { - var ty = e.type; - - // Don't fire events when it's removed - if (t.removed) - return; - - // Generic event handler - if (t.onEvent.dispatch(t, e, o) !== false) { - // Specific event handler - t[lo[e.fakeType || e.type]].dispatch(t, e, o); - } - }; - - // Add DOM events - each(lo, function(v, k) { - switch (k) { - case 'contextmenu': - if (tinymce.isOpera) { - // Fake contextmenu on Opera - t.dom.bind(t.getBody(), 'mousedown', function(e) { - if (e.ctrlKey) { - e.fakeType = 'contextmenu'; - eventHandler(e); - } - }); - } else - t.dom.bind(t.getBody(), k, eventHandler); - break; - - case 'paste': - t.dom.bind(t.getBody(), k, function(e) { - eventHandler(e); - }); - break; - - case 'submit': - case 'reset': - t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); - break; - - default: - t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); - } - }); - - t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { - t.focus(true); - }); - - - // Fixes bug where a specified document_base_uri could result in broken images - // This will also fix drag drop of images in Gecko - if (tinymce.isGecko) { - // Convert all images to absolute URLs -/* t.onSetContent.add(function(ed, o) { - each(ed.dom.select('img'), function(e) { - var v; - - if (v = e.getAttribute('_mce_src')) - e.src = t.documentBaseURI.toAbsolute(v); - }) - });*/ - - t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { - var v; - - e = e.target; - - if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) - e.src = t.documentBaseURI.toAbsolute(v); - }); - } - - // Set various midas options in Gecko - if (isGecko) { - function setOpts() { - var t = this, d = t.getDoc(), s = t.settings; - - if (isGecko && !s.readonly) { - if (t._isHidden()) { - try { - if (!s.content_editable) - d.designMode = 'On'; - } catch (ex) { - // Fails if it's hidden - } - } - - try { - // Try new Gecko method - d.execCommand("styleWithCSS", 0, false); - } catch (ex) { - // Use old method - if (!t._isHidden()) - try {d.execCommand("useCSS", 0, true);} catch (ex) {} - } - - if (!s.table_inline_editing) - try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} - - if (!s.object_resizing) - try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} - } - }; - - t.onBeforeExecCommand.add(setOpts); - t.onMouseDown.add(setOpts); - } - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - // This also fixes so it's possible to select mceItemAnchors - if (tinymce.isWebKit) { - t.onClick.add(function(ed, e) { - e = e.target; - - // Needs tobe the setBaseAndExtend or it will fail to select floated images - if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor'))) - t.selection.getSel().setBaseAndExtent(e, 0, e, 1); - }); - } - - // Add node change handlers - t.onMouseUp.add(t.nodeChanged); - t.onClick.add(t.nodeChanged); - t.onKeyUp.add(function(ed, e) { - var c = e.keyCode; - - if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) - t.nodeChanged(); - }); - - // Add reset handler - t.onReset.add(function() { - t.setContent(t.startContent, {format : 'raw'}); - }); - - // Add shortcuts - if (s.custom_shortcuts) { - if (s.custom_undo_redo_keyboard_shortcuts) { - t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); - t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); - } - - // Add default shortcuts for gecko - if (isGecko) { - t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); - t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); - t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); - } - - // BlockFormat shortcuts keys - for (i=1; i<=6; i++) - t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); - - t.addShortcut('ctrl+7', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+8', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+9', '', ['FormatBlock', false, '
    ']); - - function find(e) { - var v = null; - - if (!e.altKey && !e.ctrlKey && !e.metaKey) - return v; - - each(t.shortcuts, function(o) { - if (tinymce.isMac && o.ctrl != e.metaKey) - return; - else if (!tinymce.isMac && o.ctrl != e.ctrlKey) - return; - - if (o.alt != e.altKey) - return; - - if (o.shift != e.shiftKey) - return; - - if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { - v = o; - return false; - } - }); - - return v; - }; - - t.onKeyUp.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyPress.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyDown.add(function(ed, e) { - var o = find(e); - - if (o) { - o.func.call(o.scope); - return Event.cancel(e); - } - }); - } - - if (tinymce.isIE) { - // Fix so resize will only update the width and height attributes not the styles of an image - // It will also block mceItemNoResize items - t.dom.bind(t.getDoc(), 'controlselect', function(e) { - var re = t.resizeInfo, cb; - - e = e.target; - - // Don't do this action for non image elements - if (e.nodeName !== 'IMG') - return; - - if (re) - t.dom.unbind(re.node, re.ev, re.cb); - - if (!t.dom.hasClass(e, 'mceItemNoResize')) { - ev = 'resizeend'; - cb = t.dom.bind(e, ev, function(e) { - var v; - - e = e.target; - - if (v = t.dom.getStyle(e, 'width')) { - t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'width', ''); - } - - if (v = t.dom.getStyle(e, 'height')) { - t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'height', ''); - } - }); - } else { - ev = 'resizestart'; - cb = t.dom.bind(e, 'resizestart', Event.cancel, Event); - } - - re = t.resizeInfo = { - node : e, - ev : ev, - cb : cb - }; - }); - - t.onKeyDown.add(function(ed, e) { - switch (e.keyCode) { - case 8: - // Fix IE control + backspace browser bug - if (t.selection.getRng().item) { - ed.dom.remove(t.selection.getRng().item(0)); - return Event.cancel(e); - } - } - }); - - /*if (t.dom.boxModel) { - t.getBody().style.height = '100%'; - - Event.add(t.getWin(), 'resize', function(e) { - var docElm = t.getDoc().documentElement; - - docElm.style.height = (docElm.offsetHeight - 10) + 'px'; - }); - }*/ - } - - if (tinymce.isOpera) { - t.onClick.add(function(ed, e) { - Event.prevent(e); - }); - } - - // Add custom undo/redo handlers - if (s.custom_undo_redo) { - function addUndo() { - t.undoManager.typing = 0; - t.undoManager.add(); - }; - - t.dom.bind(t.getDoc(), 'focusout', function(e) { - if (!t.removed && t.undoManager.typing) - addUndo(); - }); - - t.onKeyUp.add(function(ed, e) { - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) - addUndo(); - }); - - t.onKeyDown.add(function(ed, e) { - // Is caracter positon keys - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { - if (t.undoManager.typing) - addUndo(); - - return; - } - - if (!t.undoManager.typing) { - t.undoManager.add(); - t.undoManager.typing = 1; - } - }); - - t.onMouseDown.add(function() { - if (t.undoManager.typing) - addUndo(); - }); - } - }, - - _isHidden : function() { - var s; - - if (!isGecko) - return 0; - - // Weird, wheres that cursor selection? - s = this.selection.getSel(); - return (!s || !s.rangeCount || s.rangeCount == 0); - }, - - // Fix for bug #1867292 - _fixNesting : function(s) { - var d = [], i; - - s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { - var e; - - // Handle end element - if (b === '/') { - if (!d.length) - return ''; - - if (c !== d[d.length - 1].tag) { - for (i=d.length - 1; i>=0; i--) { - if (d[i].tag === c) { - d[i].close = 1; - break; - } - } - - return ''; - } else { - d.pop(); - - if (d.length && d[d.length - 1].close) { - a = a + ''; - d.pop(); - } - } - } else { - // Ignore these - if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) - return a; - - // Ignore closed ones - if (/\/>$/.test(a)) - return a; - - d.push({tag : c}); // Push start element - } - - return a; - }); - - // End all open tags - for (i=d.length - 1; i>=0; i--) - s += ''; - - return s; - } - }); -})(tinymce); - -(function(tinymce) { - // Added for compression purposes - var each = tinymce.each, undefined, TRUE = true, FALSE = false; - - tinymce.EditorCommands = function(editor) { - var dom = editor.dom, - selection = editor.selection, - commands = {state: {}, exec : {}, value : {}}, - settings = editor.settings, - bookmark; - - function execCommand(command, ui, value) { - var func; - - command = command.toLowerCase(); - if (func = commands.exec[command]) { - func(command, ui, value); - return TRUE; - } - - return FALSE; - }; - - function queryCommandState(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.state[command]) - return func(command); - - return -1; - }; - - function queryCommandValue(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.value[command]) - return func(command); - - return FALSE; - }; - - function addCommands(command_list, type) { - type = type || 'exec'; - - each(command_list, function(callback, command) { - each(command.toLowerCase().split(','), function(command) { - commands[type][command] = callback; - }); - }); - }; - - // Expose public methods - tinymce.extend(this, { - execCommand : execCommand, - queryCommandState : queryCommandState, - queryCommandValue : queryCommandValue, - addCommands : addCommands - }); - - // Private methods - - function execNativeCommand(command, ui, value) { - if (ui === undefined) - ui = FALSE; - - if (value === undefined) - value = null; - - return editor.getDoc().execCommand(command, ui, value); - }; - - function isFormatMatch(name) { - return editor.formatter.match(name); - }; - - function toggleFormat(name, value) { - editor.formatter.toggle(name, value ? {value : value} : undefined); - }; - - function storeSelection(type) { - bookmark = selection.getBookmark(type); - }; - - function restoreSelection() { - selection.moveToBookmark(bookmark); - }; - - // Add execCommand overrides - addCommands({ - // Ignore these, added for compatibility - 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, - - // Add undo manager logic - 'mceEndUndoLevel,mceAddUndoLevel' : function() { - editor.undoManager.add(); - }, - - 'Cut,Copy,Paste' : function(command) { - var doc = editor.getDoc(), failed; - - // Try executing the native command - try { - execNativeCommand(command); - } catch (ex) { - // Command failed - failed = TRUE; - } - - // Present alert message about clipboard access not being available - if (failed || !doc.queryCommandEnabled(command)) { - if (tinymce.isGecko) { - editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { - if (state) - open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); - }); - } else - editor.windowManager.alert(editor.getLang('clipboard_no_support')); - } - }, - - // Override unlink command - unlink : function(command) { - if (selection.isCollapsed()) - selection.select(selection.getNode()); - - execNativeCommand(command); - selection.collapse(FALSE); - }, - - // Override justify commands to use the text formatter engine - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - var align = command.substring(7); - - // Remove all other alignments first - each('left,center,right,full'.split(','), function(name) { - if (align != name) - editor.formatter.remove('align' + name); - }); - - toggleFormat('align' + align); - }, - - // Override list commands to fix WebKit bug - 'InsertUnorderedList,InsertOrderedList' : function(command) { - var listElm, listParent; - - execNativeCommand(command); - - // WebKit produces lists within block elements so we need to split them - // we will replace the native list creation logic to custom logic later on - // TODO: Remove this when the list creation logic is removed - listElm = dom.getParent(selection.getNode(), 'ol,ul'); - if (listElm) { - listParent = listElm.parentNode; - - // If list is within a text block then split that block - if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { - storeSelection(); - dom.split(listParent, listElm); - restoreSelection(); - } - } - }, - - // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough' : function(command) { - toggleFormat(command); - }, - - // Override commands to use the text formatter engine - 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { - toggleFormat(command, value); - }, - - FontSize : function(command, ui, value) { - var fontClasses, fontSizes; - - // Convert font size 1-7 to styles - if (value >= 1 && value <= 7) { - fontSizes = tinymce.explode(settings.font_size_style_values); - fontClasses = tinymce.explode(settings.font_size_classes); - - if (fontClasses) - value = fontClasses[value - 1] || value; - else - value = fontSizes[value - 1] || value; - } - - toggleFormat(command, value); - }, - - RemoveFormat : function(command) { - editor.formatter.remove(command); - }, - - mceBlockQuote : function(command) { - toggleFormat('blockquote'); - }, - - FormatBlock : function(command, ui, value) { - return toggleFormat(value); - }, - - mceCleanup : function() { - storeSelection(); - editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); - restoreSelection(); - }, - - mceRemoveNode : function(command, ui, value) { - var node = value || selection.getNode(); - - // Make sure that the body node isn't removed - if (node != ed.getBody()) { - storeSelection(); - editor.dom.remove(node, TRUE); - restoreSelection(); - } - }, - - mceSelectNodeDepth : function(command, ui, value) { - var counter = 0; - - dom.getParent(selection.getNode(), function(node) { - if (node.nodeType == 1 && counter++ == value) { - selection.select(node); - return FALSE; - } - }, editor.getBody()); - }, - - mceSelectNode : function(command, ui, value) { - selection.select(value); - }, - - mceInsertContent : function(command, ui, value) { - selection.setContent(value); - }, - - mceInsertRawHTML : function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value)); - }, - - mceSetContent : function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent' : function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - each(selection.getSelectedBlocks(), function(element) { - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); - dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); - } else - dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); - }); - } else - execNativeCommand(command); - }, - - mceRepaint : function() { - var bookmark; - - if (tinymce.isGecko) { - try { - storeSelection(TRUE); - - if (selection.getSel()) - selection.getSel().selectAllChildren(editor.getBody()); - - selection.collapse(TRUE); - restoreSelection(); - } catch (ex) { - // Ignore - } - } - }, - - mceToggleFormat : function(command, ui, value) { - editor.formatter.toggle(value); - }, - - InsertHorizontalRule : function() { - selection.setContent('
    '); - }, - - mceToggleVisualAid : function() { - editor.hasVisual = !editor.hasVisual; - editor.addVisual(); - }, - - mceReplaceContent : function(command, ui, value) { - selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); - }, - - mceInsertLink : function(command, ui, value) { - var link = dom.getParent(selection.getNode(), 'a'); - - if (tinymce.is(value, 'string')) - value = {href : value}; - - if (!link) { - execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); - each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { - dom.setAttribs(link, value); - }); - } else { - if (value.href) - dom.setAttribs(link, value); - else - ed.dom.remove(link, TRUE); - } - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - return isFormatMatch('align' + command.substring(7)); - }, - - 'Bold,Italic,Underline,Strikethrough' : function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote : function() { - return isFormatMatch('blockquote'); - }, - - Outdent : function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - } - - return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); - }, - - 'InsertUnorderedList,InsertOrderedList' : function(command) { - return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName' : function(command) { - var value = 0, parent; - - if (parent = dom.getParent(selection.getNode(), 'span')) { - if (command == 'fontsize') - value = parent.style.fontSize; - else - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - - return value; - } - }, 'value'); - - // Add undo manager logic - if (settings.custom_undo_redo) { - addCommands({ - Undo : function() { - editor.undoManager.undo(); - }, - - Redo : function() { - editor.undoManager.redo(); - } - }); - } - }; -})(tinymce); -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher; - - tinymce.UndoManager = function(editor) { - var self, index = 0, data = []; - - function getContent() { - return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); - }; - - return self = { - typing : 0, - - onAdd : new Dispatcher(self), - onUndo : new Dispatcher(self), - onRedo : new Dispatcher(self), - - add : function(level) { - var i, settings = editor.settings, lastLevel; - - level = level || {}; - level.content = getContent(); - - // Add undo level if needed - lastLevel = data[index]; - if (lastLevel && lastLevel.content == level.content) { - if (index > 0 || data.length == 1) - return null; - } - - // Time to compress - if (settings.custom_undo_redo_levels) { - if (data.length > settings.custom_undo_redo_levels) { - for (i = 0; i < data.length - 1; i++) - data[i] = data[i + 1]; - - data.length--; - index = data.length; - } - } - - // Get a non intrusive normalized bookmark - level.bookmark = editor.selection.getBookmark(2, true); - - // Crop array if needed - if (index < data.length - 1) { - // Treat first level as initial - if (index == 0) - data = []; - else - data.length = index + 1; - } - - data.push(level); - index = data.length - 1; - - self.onAdd.dispatch(self, level); - editor.isNotDirty = 0; - - return level; - }, - - undo : function() { - var level, i; - - if (self.typing) { - self.add(); - self.typing = 0; - } - - if (index > 0) { - level = data[--index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onUndo.dispatch(self, level); - } - - return level; - }, - - redo : function() { - var level; - - if (index < data.length - 1) { - level = data[++index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onRedo.dispatch(self, level); - } - - return level; - }, - - clear : function() { - data = []; - index = self.typing = 0; - }, - - hasUndo : function() { - return index > 0 || self.typing; - }, - - hasRedo : function() { - return index < data.length - 1; - } - }; - }; -})(tinymce); - -(function(tinymce) { - // Shorten names - var Event = tinymce.dom.Event, - isIE = tinymce.isIE, - isGecko = tinymce.isGecko, - isOpera = tinymce.isOpera, - each = tinymce.each, - extend = tinymce.extend, - TRUE = true, - FALSE = false; - - // Checks if the selection/caret is at the end of the specified block element - function isAtEnd(rng, par) { - var rng2 = par.ownerDocument.createRange(); - - rng2.setStart(rng.endContainer, rng.endOffset); - rng2.setEndAfter(par); - - // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element - return rng2.cloneContents().textContent.length == 0; - }; - - function isEmpty(n) { - n = n.innerHTML; - - n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars - n = n.replace(/<[^>]+>/g, ''); // Remove all tags - - return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; - }; - - function splitList(selection, dom, li) { - var listBlock, block; - - if (isEmpty(li)) { - listBlock = dom.getParent(li, 'ul,ol'); - - if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { - dom.split(listBlock, li); - block = dom.create('p', 0, '
    '); - dom.replace(block, li); - selection.select(block, 1); - } - - return FALSE; - } - - return TRUE; - }; - - tinymce.create('tinymce.ForceBlocks', { - ForceBlocks : function(ed) { - var t = this, s = ed.settings, elm; - - t.editor = ed; - t.dom = ed.dom; - elm = (s.forced_root_block || 'p').toLowerCase(); - s.element = elm.toUpperCase(); - - ed.onPreInit.add(t.setup, t); - - t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

    <\\\/p>||

    \\s+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

    [\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); - t.reBR2Nbsp = new RegExp(']+)>\\s*
    \\s*<\\\/p>|

    \\s*
    \\s*<\\\/p>'.replace(/p/g, elm), 'gi'); - - function padd(ed, o) { - if (isOpera) - o.content = o.content.replace(t.reOpera, ''); - - o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0'); - - if (!isIE && !isOpera && o.set) { - // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
    '); - o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
    '); - } else - o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0'); - }; - - ed.onBeforeSetContent.add(padd); - ed.onPostProcess.add(padd); - - if (s.forced_root_block) { - ed.onInit.add(t.forceRoots, t); - ed.onSetContent.add(t.forceRoots, t); - ed.onBeforeGetContent.add(t.forceRoots, t); - } - }, - - setup : function() { - var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection; - - // Force root blocks when typing and when getting output - if (s.forced_root_block) { - ed.onBeforeExecCommand.add(t.forceRoots, t); - ed.onKeyUp.add(t.forceRoots, t); - ed.onPreProcess.add(t.forceRoots, t); - } - - if (s.force_br_newlines) { - // Force IE to produce BRs on enter - if (isIE) { - ed.onKeyPress.add(function(ed, e) { - var n; - - if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { - selection.setContent('
    ', {format : 'raw'}); - n = dom.get('__'); - n.removeAttribute('id'); - selection.select(n); - selection.collapse(); - return Event.cancel(e); - } - }); - } - } - - if (!isIE && s.force_p_newlines) { - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) - Event.cancel(e); - }); - - if (isGecko) { - ed.onKeyDown.add(function(ed, e) { - if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) - t.backspaceDelete(e, e.keyCode == 8); - }); - } - } - - // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 - if (tinymce.isWebKit) { - function insertBr(ed) { - var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; - - // Insert BR element - rng.insertNode(br = dom.create('br')); - - // Place caret after BR - rng.setStartAfter(br); - rng.setEndAfter(br); - selection.setRng(rng); - - // Could not place caret after BR then insert an nbsp entity and move the caret - if (selection.getSel().focusNode == br.previousSibling) { - selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); - selection.collapse(TRUE); - } - - // Create a temporary DIV after the BR and get the position as it - // seems like getPos() returns 0 for text nodes and BR elements. - dom.insertAfter(div, br); - divYPos = dom.getPos(div).y; - dom.remove(div); - - // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 - if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. - ed.getWin().scrollTo(0, divYPos); - }; - - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) { - insertBr(ed); - Event.cancel(e); - } - }); - } - - // Padd empty inline elements within block elements - // For example:

    becomes

     

    - ed.onPreProcess.add(function(ed, o) { - each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { - if (isEmpty(p)) { - each(dom.select('span,em,strong,b,i', o.node), function(n) { - if (!n.hasChildNodes()) { - n.appendChild(ed.getDoc().createTextNode('\u00a0')); - return FALSE; // Break the loop one padding is enough - } - }); - } - }); - }); - - // IE specific fixes - if (isIE) { - // Replaces IE:s auto generated paragraphs with the specified element name - if (s.element != 'P') { - ed.onKeyPress.add(function(ed, e) { - t.lastElm = selection.getNode().nodeName; - }); - - ed.onKeyUp.add(function(ed, e) { - var bl, n = selection.getNode(), b = ed.getBody(); - - if (b.childNodes.length === 1 && n.nodeName == 'P') { - n = dom.rename(n, s.element); - selection.select(n); - selection.collapse(); - ed.nodeChanged(); - } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { - bl = dom.getParent(n, 'p'); - - if (bl) { - dom.rename(bl, s.element); - ed.nodeChanged(); - } - } - }); - } - } - }, - - find : function(n, t, s) { - var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1; - - while (n = w.nextNode()) { - c++; - - // Index by node - if (t == 0 && n == s) - return c; - - // Node by index - if (t == 1 && c == s) - return n; - } - - return -1; - }, - - forceRoots : function(ed, e) { - var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF; - var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid; - - // Fix for bug #1863847 - //if (e && e.keyCode == 13) - // return TRUE; - - // Wrap non blocks into blocks - for (i = nl.length - 1; i >= 0; i--) { - nx = nl[i]; - - // Ignore internal elements - if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { - bl = null; - continue; - } - - // Is text or non block element - if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) { - if (!bl) { - // Create new block but ignore whitespace - if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { - // Store selection - if (si == -2 && r) { - if (!isIE) { - // If selection is element then mark it - if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { - // Save the id of the selected element - eid = n.getAttribute("id"); - n.setAttribute("id", "__mce"); - } else { - // If element is inside body, might not be the case in contentEdiable mode - if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) { - so = r.startOffset; - eo = r.endOffset; - si = t.find(b, 0, r.startContainer); - ei = t.find(b, 0, r.endContainer); - } - } - } else { - tr = d.body.createTextRange(); - tr.moveToElementText(b); - tr.collapse(1); - bp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(1); - sp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(0); - le = (tr.move('character', c) * -1) - sp; - - si = sp - bp; - ei = le; - } - } - - // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE - // See: http://support.microsoft.com/kb/829907 - bl = ed.dom.create(ed.settings.forced_root_block); - nx.parentNode.replaceChild(bl, nx); - bl.appendChild(nx); - } - } else { - if (bl.hasChildNodes()) - bl.insertBefore(nx, bl.firstChild); - else - bl.appendChild(nx); - } - } else - bl = null; // Time to create new block - } - - // Restore selection - if (si != -2) { - if (!isIE) { - bl = b.getElementsByTagName(ed.settings.element)[0]; - r = d.createRange(); - - // Select last location or generated block - if (si != -1) - r.setStart(t.find(b, 1, si), so); - else - r.setStart(bl, 0); - - // Select last location or generated block - if (ei != -1) - r.setEnd(t.find(b, 1, ei), eo); - else - r.setEnd(bl, 0); - - if (s) { - s.removeAllRanges(); - s.addRange(r); - } - } else { - try { - r = s.createRange(); - r.moveToElementText(b); - r.collapse(1); - r.moveStart('character', si); - r.moveEnd('character', ei); - r.select(); - } catch (ex) { - // Ignore - } - } - } else if (!isIE && (n = ed.dom.get('__mce'))) { - // Restore the id of the selected element - if (eid) - n.setAttribute('id', eid); - else - n.removeAttribute('id'); - - // Move caret before selected element - r = d.createRange(); - r.setStartBefore(n); - r.setEndBefore(n); - se.setRng(r); - } - }, - - getParentBlock : function(n) { - var d = this.dom; - - return d.getParent(n, d.isBlock); - }, - - insertPara : function(e) { - var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; - var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; - - // If root blocks are forced then use Operas default behavior since it's really good -// Removed due to bug: #1853816 -// if (se.forced_root_block && isOpera) -// return TRUE; - - // Setup before range - rb = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - rb.setStart(s.anchorNode, s.anchorOffset); - rb.collapse(TRUE); - - // Setup after range - ra = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - ra.setStart(s.focusNode, s.focusOffset); - ra.collapse(TRUE); - - // Setup start/end points - dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; - sn = dir ? s.anchorNode : s.focusNode; - so = dir ? s.anchorOffset : s.focusOffset; - en = dir ? s.focusNode : s.anchorNode; - eo = dir ? s.focusOffset : s.anchorOffset; - - // If selection is in empty table cell - if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { - if (sn.firstChild.nodeName == 'BR') - dom.remove(sn.firstChild); // Remove BR - - // Create two new block elements - if (sn.childNodes.length == 0) { - ed.dom.add(sn, se.element, null, '
    '); - aft = ed.dom.add(sn, se.element, null, '
    '); - } else { - n = sn.innerHTML; - sn.innerHTML = ''; - ed.dom.add(sn, se.element, null, n); - aft = ed.dom.add(sn, se.element, null, '
    '); - } - - // Move caret into the last one - r = d.createRange(); - r.selectNodeContents(aft); - r.collapse(1); - ed.selection.setRng(r); - - return FALSE; - } - - // If the caret is in an invalid location in FF we need to move it into the first block - if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { - sn = en = sn.firstChild; - so = eo = 0; - rb = d.createRange(); - rb.setStart(sn, 0); - ra = d.createRange(); - ra.setStart(en, 0); - } - - // Never use body as start or end node - sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - sn = sn.nodeName == "BODY" ? sn.firstChild : sn; - en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - en = en.nodeName == "BODY" ? en.firstChild : en; - - // Get start and end blocks - sb = t.getParentBlock(sn); - eb = t.getParentBlock(en); - bn = sb ? sb.nodeName : se.element; // Get block name to create - - // Return inside list use default browser behavior - if (n = t.dom.getParent(sb, 'li,pre')) { - if (n.nodeName == 'LI') - return splitList(ed.selection, t.dom, n); - - return TRUE; - } - - // If caption or absolute layers then always generate new blocks within - if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - sb = null; - } - - // If caption or absolute layers then always generate new blocks within - if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - eb = null; - } - - // Use P instead - if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { - bn = se.element; - sb = eb = null; - } - - // Setup new before and after blocks - bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); - aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); - - // Remove id from after clone - aft.removeAttribute('id'); - - // Is header and cursor is at the end, then force paragraph under - if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) - aft = ed.dom.create(se.element); - - // Find start chop node - n = sc = sn; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - sc = n; - } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); - - // Find end chop node - n = ec = en; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - ec = n; - } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); - - // Place first chop part into before block element - if (sc.nodeName == bn) - rb.setStart(sc, 0); - else - rb.setStartBefore(sc); - - rb.setEnd(sn, so); - bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Place secnd chop part within new block element - try { - ra.setEndAfter(ec); - } catch(ex) { - //console.debug(s.focusNode, s.focusOffset); - } - - ra.setStart(en, eo); - aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Create range around everything - r = d.createRange(); - if (!sc.previousSibling && sc.parentNode.nodeName == bn) { - r.setStartBefore(sc.parentNode); - } else { - if (rb.startContainer.nodeName == bn && rb.startOffset == 0) - r.setStartBefore(rb.startContainer); - else - r.setStart(rb.startContainer, rb.startOffset); - } - - if (!ec.nextSibling && ec.parentNode.nodeName == bn) - r.setEndAfter(ec.parentNode); - else - r.setEnd(ra.endContainer, ra.endOffset); - - // Delete and replace it with new block elements - r.deleteContents(); - - if (isOpera) - ed.getWin().scrollTo(0, vp.y); - - // Never wrap blocks in blocks - if (bef.firstChild && bef.firstChild.nodeName == bn) - bef.innerHTML = bef.firstChild.innerHTML; - - if (aft.firstChild && aft.firstChild.nodeName == bn) - aft.innerHTML = aft.firstChild.innerHTML; - - // Padd empty blocks - if (isEmpty(bef)) - bef.innerHTML = '
    '; - - function appendStyles(e, en) { - var nl = [], nn, n, i; - - e.innerHTML = ''; - - // Make clones of style elements - if (se.keep_styles) { - n = en; - do { - // We only want style specific elements - if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { - nn = n.cloneNode(FALSE); - dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique - nl.push(nn); - } - } while (n = n.parentNode); - } - - // Append style elements to aft - if (nl.length > 0) { - for (i = nl.length - 1, nn = e; i >= 0; i--) - nn = nn.appendChild(nl[i]); - - // Padd most inner style element - nl[0].innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - return nl[0]; // Move caret to most inner element - } else - e.innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - }; - - // Fill empty afterblook with current style - if (isEmpty(aft)) - car = appendStyles(aft, en); - - // Opera needs this one backwards for older versions - if (isOpera && parseFloat(opera.version()) < 9.5) { - r.insertNode(bef); - r.insertNode(aft); - } else { - r.insertNode(aft); - r.insertNode(bef); - } - - // Normalize - aft.normalize(); - bef.normalize(); - - function first(n) { - return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n; - }; - - // Move cursor and scroll into view - r = d.createRange(); - r.selectNodeContents(isGecko ? first(car || aft) : car || aft); - r.collapse(1); - s.removeAllRanges(); - s.addRange(r); - - // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs - y = ed.dom.getPos(aft).y; - ch = aft.clientHeight; - - // Is element within viewport - if (y < vp.y || y + ch > vp.y + vp.h) { - ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks - //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); - } - - return FALSE; - }, - - backspaceDelete : function(e, bs) { - var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn; - - // The caret sometimes gets stuck in Gecko if you delete empty paragraphs - // This workaround removes the element by hand and moves the caret to the previous element - if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { - if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { - // Find previous block element - n = sc; - while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; - - if (n) { - if (sc != b.firstChild) { - // Find last text node - w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); - while (tn = w.nextNode()) - n = tn; - - // Place caret at the end of last text node - r = ed.getDoc().createRange(); - r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); - r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); - se.setRng(r); - - // Remove the target container - ed.dom.remove(sc); - } - - return Event.cancel(e); - } - } - } - - // Gecko generates BR elements here and there, we don't like those so lets remove them - function handler(e) { - var pr; - - e = e.target; - - // A new BR was created in a block element, remove it - if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) { - pr = e.previousSibling; - - Event.remove(b, 'DOMNodeInserted', handler); - - // Is there whitespace at the end of the node before then we might need the pesky BR - // to place the caret at a correct location see bug: #2013943 - if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue)) - return; - - // Only remove BR elements that got inserted in the middle of the text - if (e.previousSibling || e.nextSibling) - ed.dom.remove(e); - } - }; - - // Listen for new nodes - Event._add(b, 'DOMNodeInserted', handler); - - // Remove listener - window.setTimeout(function() { - Event._remove(b, 'DOMNodeInserted', handler); - }, 1); - } - }); -})(tinymce); - -(function(tinymce) { - // Shorten names - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; - - tinymce.create('tinymce.ControlManager', { - ControlManager : function(ed, s) { - var t = this, i; - - s = s || {}; - t.editor = ed; - t.controls = {}; - t.onAdd = new tinymce.util.Dispatcher(t); - t.onPostRender = new tinymce.util.Dispatcher(t); - t.prefix = s.prefix || ed.id + '_'; - t._cls = {}; - - t.onPostRender.add(function() { - each(t.controls, function(c) { - c.postRender(); - }); - }); - }, - - get : function(id) { - return this.controls[this.prefix + id] || this.controls[id]; - }, - - setActive : function(id, s) { - var c = null; - - if (c = this.get(id)) - c.setActive(s); - - return c; - }, - - setDisabled : function(id, s) { - var c = null; - - if (c = this.get(id)) - c.setDisabled(s); - - return c; - }, - - add : function(c) { - var t = this; - - if (c) { - t.controls[c.id] = c; - t.onAdd.dispatch(c, t); - } - - return c; - }, - - createControl : function(n) { - var c, t = this, ed = t.editor; - - each(ed.plugins, function(p) { - if (p.createControl) { - c = p.createControl(n, t); - - if (c) - return false; - } - }); - - switch (n) { - case "|": - case "separator": - return t.createSeparator(); - } - - if (!c && ed.buttons && (c = ed.buttons[n])) - return t.createButton(n, c); - - return t.add(c); - }, - - createDropMenu : function(id, s, cc) { - var t = this, ed = t.editor, c, bm, v, cls; - - s = extend({ - 'class' : 'mceDropDown', - constrain : ed.settings.constrain_menus - }, s); - - s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; - if (v = ed.getParam('skin_variant')) - s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); - - id = t.prefix + id; - cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; - c = t.controls[id] = new cls(id, s); - c.onAddItem.add(function(c, o) { - var s = o.settings; - - s.title = ed.getLang(s.title, s.title); - - if (!s.onclick) { - s.onclick = function(v) { - if (s.cmd) - ed.execCommand(s.cmd, s.ui || false, s.value); - }; - } - }); - - ed.onRemove.add(function() { - c.destroy(); - }); - - // Fix for bug #1897785, #1898007 - if (tinymce.isIE) { - c.onShowMenu.add(function() { - // IE 8 needs focus in order to store away a range with the current collapsed caret location - ed.focus(); - - bm = ed.selection.getBookmark(1); - }); - - c.onHideMenu.add(function() { - if (bm) { - ed.selection.moveToBookmark(bm); - bm = 0; - } - }); - } - - return t.add(c); - }, - - createListBox : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - - if (ed.settings.use_native_selects) - c = new tinymce.ui.NativeListBox(id, s); - else { - cls = cc || t._cls.listbox || tinymce.ui.ListBox; - c = new cls(id, s); - } - - t.controls[id] = c; - - // Fix focus problem in Safari - if (tinymce.isWebKit) { - c.onPostRender.add(function(c, n) { - // Store bookmark on mousedown - Event.add(n, 'mousedown', function() { - ed.bookmark = ed.selection.getBookmark(1); - }); - - // Restore on focus, since it might be lost - Event.add(n, 'focus', function() { - ed.selection.moveToBookmark(ed.bookmark); - ed.bookmark = null; - }); - }); - } - - if (c.hideMenu) - ed.onMouseDown.add(c.hideMenu, c); - - return t.add(c); - }, - - createButton : function(id, s, cc) { - var t = this, ed = t.editor, o, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.label = ed.translate(s.label); - s.scope = s.scope || ed; - - if (!s.onclick && !s.menu_button) { - s.onclick = function() { - ed.execCommand(s.cmd, s.ui || false, s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - unavailable_prefix : ed.getLang('unavailable', ''), - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - - if (s.menu_button) { - cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; - c = new cls(id, s); - ed.onMouseDown.add(c.hideMenu, c); - } else { - cls = t._cls.button || tinymce.ui.Button; - c = new cls(id, s); - } - - return t.add(c); - }, - - createMenuButton : function(id, s, cc) { - s = s || {}; - s.menu_button = 1; - - return this.createButton(id, s, cc); - }, - - createSplitButton : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onclick) { - s.onclick = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; - c = t.add(new cls(id, s)); - ed.onMouseDown.add(c.hideMenu, c); - - return c; - }, - - createColorSplitButton : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls, bm; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onclick) { - s.onclick = function(v) { - if (tinymce.isIE) - bm = ed.selection.getBookmark(1); - - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - 'menu_class' : ed.getParam('skin') + 'Skin', - scope : s.scope, - more_colors_title : ed.getLang('more_colors') - }, s); - - id = t.prefix + id; - cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; - c = new cls(id, s); - ed.onMouseDown.add(c.hideMenu, c); - - // Remove the menu element when the editor is removed - ed.onRemove.add(function() { - c.destroy(); - }); - - // Fix for bug #1897785, #1898007 - if (tinymce.isIE) { - c.onShowMenu.add(function() { - // IE 8 needs focus in order to store away a range with the current collapsed caret location - ed.focus(); - bm = ed.selection.getBookmark(1); - }); - - c.onHideMenu.add(function() { - if (bm) { - ed.selection.moveToBookmark(bm); - bm = 0; - } - }); - } - - return t.add(c); - }, - - createToolbar : function(id, s, cc) { - var c, t = this, cls; - - id = t.prefix + id; - cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; - c = new cls(id, s); - - if (t.get(id)) - return null; - - return t.add(c); - }, - - createSeparator : function(cc) { - var cls = cc || this._cls.separator || tinymce.ui.Separator; - - return new cls(); - }, - - setControlType : function(n, c) { - return this._cls[n.toLowerCase()] = c; - }, - - destroy : function() { - each(this.controls, function(c) { - c.destroy(); - }); - - this.controls = null; - } - }); -})(tinymce); - -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; - - tinymce.create('tinymce.WindowManager', { - WindowManager : function(ed) { - var t = this; - - t.editor = ed; - t.onOpen = new Dispatcher(t); - t.onClose = new Dispatcher(t); - t.params = {}; - t.features = {}; - }, - - open : function(s, p) { - var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; - - // Default some options - s = s || {}; - p = p || {}; - sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window - sh = isOpera ? vp.h : screen.height; - s.name = s.name || 'mc_' + new Date().getTime(); - s.width = parseInt(s.width || 320); - s.height = parseInt(s.height || 240); - s.resizable = true; - s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); - s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); - p.inline = false; - p.mce_width = s.width; - p.mce_height = s.height; - p.mce_auto_focus = s.auto_focus; - - if (mo) { - if (isIE) { - s.center = true; - s.help = false; - s.dialogWidth = s.width + 'px'; - s.dialogHeight = s.height + 'px'; - s.scroll = s.scrollbars || false; - } - } - - // Build features string - each(s, function(v, k) { - if (tinymce.is(v, 'boolean')) - v = v ? 'yes' : 'no'; - - if (!/^(name|url)$/.test(k)) { - if (isIE && mo) - f += (f ? ';' : '') + k + ':' + v; - else - f += (f ? ',' : '') + k + '=' + v; - } - }); - - t.features = s; - t.params = p; - t.onOpen.dispatch(t, s, p); - - u = s.url || s.file; - u = tinymce._addVer(u); - - try { - if (isIE && mo) { - w = 1; - window.showModalDialog(u, window, f); - } else - w = window.open(u, s.name, f); - } catch (ex) { - // Ignore - } - - if (!w) - alert(t.editor.getLang('popup_blocked')); - }, - - close : function(w) { - w.close(); - this.onClose.dispatch(this); - }, - - createInstance : function(cl, a, b, c, d, e) { - var f = tinymce.resolve(cl); - - return new f(a, b, c, d, e); - }, - - confirm : function(t, cb, s, w) { - w = w || window; - - cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); - }, - - alert : function(tx, cb, s, w) { - var t = this; - - w = w || window; - w.alert(t._decode(t.editor.getLang(tx, tx))); - - if (cb) - cb.call(s || t); - }, - - resizeBy : function(dw, dh, win) { - win.resizeBy(dw, dh); - }, - - // Internal functions - - _decode : function(s) { - return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); - } - }); -}(tinymce)); -(function(tinymce) { - function CommandManager() { - var execCommands = {}, queryStateCommands = {}, queryValueCommands = {}; - - function add(collection, cmd, func, scope) { - if (typeof(cmd) == 'string') - cmd = [cmd]; - - tinymce.each(cmd, function(cmd) { - collection[cmd.toLowerCase()] = {func : func, scope : scope}; - }); - }; - - tinymce.extend(this, { - add : function(cmd, func, scope) { - add(execCommands, cmd, func, scope); - }, - - addQueryStateHandler : function(cmd, func, scope) { - add(queryStateCommands, cmd, func, scope); - }, - - addQueryValueHandler : function(cmd, func, scope) { - add(queryValueCommands, cmd, func, scope); - }, - - execCommand : function(scope, cmd, ui, value, args) { - if (cmd = execCommands[cmd.toLowerCase()]) { - if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false) - return true; - } - }, - - queryCommandValue : function() { - if (cmd = queryValueCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - }, - - queryCommandState : function() { - if (cmd = queryStateCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - } - }); - }; - - tinymce.GlobalCommands = new CommandManager(); -})(tinymce); -(function(tinymce) { - tinymce.Formatter = function(ed) { - var formats = {}, - each = tinymce.each, - dom = ed.dom, - selection = ed.selection, - TreeWalker = tinymce.dom.TreeWalker, - rangeUtils = new tinymce.dom.RangeUtils(dom), - isValid = ed.schema.isValid, - isBlock = dom.isBlock, - forcedRootBlock = ed.settings.forced_root_block, - nodeIndex = dom.nodeIndex, - INVISIBLE_CHAR = '\uFEFF', - MCE_ATTR_RE = /^(src|href|style)$/, - FALSE = false, - TRUE = true, - undefined, - pendingFormats = {apply : [], remove : []}; - - function isArray(obj) { - return obj instanceof Array; - }; - - function getParents(node, selector) { - return dom.getParents(node, selector, dom.getRoot()); - }; - - function isCaretNode(node) { - return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); - }; - - // Public functions - - function get(name) { - return name ? formats[name] : formats; - }; - - function register(name, format) { - if (name) { - if (typeof(name) !== 'string') { - each(name, function(format, name) { - register(name, format); - }); - } else { - // Force format into array and add it to internal collection - format = format.length ? format : [format]; - - each(format, function(format) { - // Set deep to false by default on selector formats this to avoid removing - // alignment on images inside paragraphs when alignment is changed on paragraphs - if (format.deep === undefined) - format.deep = !format.selector; - - // Default to true - if (format.split === undefined) - format.split = !format.selector || format.inline; - - // Default to true - if (format.remove === undefined && format.selector && !format.inline) - format.remove = 'none'; - - // Mark format as a mixed format inline + block level - if (format.selector && format.inline) { - format.mixed = true; - format.block_expand = true; - } - - // Split classes if needed - if (typeof(format.classes) === 'string') - format.classes = format.classes.split(/\s+/); - }); - - formats[name] = format; - } - } - }; - - function apply(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, rng, i; - - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, - walker, node; - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1 || container.nodeValue === "") { - walker = new TreeWalker(container.childNodes[offset]); - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { - rng.setStart(node, 0); - break; - } - } - } - - return rng; - }; - - function setElementFormat(elm, fmt) { - fmt = fmt || format; - - if (elm) { - each(fmt.styles, function(value, name) { - dom.setStyle(elm, name, replaceVars(value, vars)); - }); - - each(fmt.attributes, function(value, name) { - dom.setAttrib(elm, name, replaceVars(value, vars)); - }); - - each(fmt.classes, function(value) { - value = replaceVars(value, vars); - - if (!dom.hasClass(elm, value)) - dom.addClass(elm, value); - }); - } - }; - - function applyRngStyle(rng) { - var newWrappers = [], wrapName, wrapElm; - - // Setup wrapper element - wrapName = format.inline || format.block; - wrapElm = dom.create(wrapName); - setElementFormat(wrapElm); - - rangeUtils.walk(rng, function(nodes) { - var currentWrapElm; - - function process(node) { - var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; - - // Stop wrapping on br elements - if (isEq(nodeName, 'br')) { - currentWrapElm = 0; - - // Remove any br elements when we wrap things - if (format.block) - dom.remove(node); - - return; - } - - // If node is wrapper type - if (format.wrapper && matchNode(node, name, vars)) { - currentWrapElm = 0; - return; - } - - // Can we rename the block - if (format.block && !format.wrapper && isTextBlock(nodeName)) { - node = dom.rename(node, wrapName); - setElementFormat(node); - newWrappers.push(node); - currentWrapElm = 0; - return; - } - - // Handle selector patterns - if (format.selector) { - // Look for matching formats - each(formatList, function(format) { - if (dom.is(node, format.selector) && !isCaretNode(node)) { - setElementFormat(node, format); - found = true; - } - }); - - // Contine processing if a selector match wasn't found and a inline element is defined - if (!format.inline || found) { - currentWrapElm = 0; - return; - } - } - - // Is it valid to wrap this item - if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { - // Start wrapping - if (!currentWrapElm) { - // Wrap the node - currentWrapElm = wrapElm.cloneNode(FALSE); - node.parentNode.insertBefore(currentWrapElm, node); - newWrappers.push(currentWrapElm); - } - - currentWrapElm.appendChild(node); - } else { - // Start a new wrapper for possible children - currentWrapElm = 0; - - each(tinymce.grep(node.childNodes), process); - - // End the last wrapper - currentWrapElm = 0; - } - }; - - // Process siblings from range - each(nodes, process); - }); - - // Cleanup - each(newWrappers, function(node) { - var childCount; - - function getChildCount(node) { - var count = 0; - - each(node.childNodes, function(node) { - if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) - count++; - }); - - return count; - }; - - function mergeStyles(node) { - var child, clone; - - each(node.childNodes, function(node) { - if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { - child = node; - return FALSE; // break loop - } - }); - - // If child was found and of the same type as the current node - if (child && matchName(child, format)) { - clone = child.cloneNode(FALSE); - setElementFormat(clone); - - dom.replace(clone, node, TRUE); - dom.remove(child, 1); - } - - return clone || node; - }; - - childCount = getChildCount(node); - - // Remove empty nodes - if (childCount === 0) { - dom.remove(node, 1); - return; - } - - if (format.inline || format.wrapper) { - // Merges the current node with it's children of similar type to reduce the number of elements - if (!format.exact && childCount === 1) - node = mergeStyles(node); - - // Remove/merge children - each(formatList, function(format) { - // Merge all children of similar type will move styles from child to parent - // this: text - // will become: text - each(dom.select(format.inline, node), function(child) { - removeFormat(format, vars, child, format.exact ? child : null); - }); - }); - - // Look for parent with similar style format - dom.getParent(node.parentNode, function(parent) { - if (matchNode(parent, name, vars)) { - dom.remove(node, 1); - node = 0; - return TRUE; - } - }); - - // Merge next and previous siblings if they are similar texttext becomes texttext - if (node) { - node = mergeSiblings(getNonWhiteSpaceSibling(node), node); - node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); - } - } - }); - }; - - if (format) { - if (node) { - rng = dom.createRng(); - - rng.setStartBefore(node); - rng.setEndAfter(node); - - applyRngStyle(rng); - } else { - if (!selection.isCollapsed() || !format.inline) { - // Apply formatting to selection - bookmark = selection.getBookmark(); - applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); - - selection.moveToBookmark(bookmark); - selection.setRng(moveStart(selection.getRng(TRUE))); - ed.nodeChanged(); - } else - performCaretAction('apply', name, vars); - } - } - }; - - function remove(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, i, rng; - - // Merges the styles for each node - function process(node) { - var children, i, l; - - // Grab the children first since the nodelist might be changed - children = tinymce.grep(node.childNodes); - - // Process current node - for (i = 0, l = formatList.length; i < l; i++) { - if (removeFormat(formatList[i], vars, node, node)) - break; - } - - // Process the children - if (format.deep) { - for (i = 0, l = children.length; i < l; i++) - process(children[i]); - } - }; - - function findFormatRoot(container) { - var formatRoot; - - // Find format root - each(getParents(container.parentNode).reverse(), function(parent) { - var format; - - // Find format root element - if (!formatRoot && parent.id != '_start' && parent.id != '_end') { - // Is the node matching the format we are looking for - format = matchNode(parent, name, vars); - if (format && format.split !== false) - formatRoot = parent; - } - }); - - return formatRoot; - }; - - function wrapAndSplit(format_root, container, target, split) { - var parent, clone, lastClone, firstClone, i, formatRootParent; - - // Format root found then clone formats and split it - if (format_root) { - formatRootParent = format_root.parentNode; - - for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { - clone = parent.cloneNode(FALSE); - - for (i = 0; i < formatList.length; i++) { - if (removeFormat(formatList[i], vars, clone, clone)) { - clone = 0; - break; - } - } - - // Build wrapper node - if (clone) { - if (lastClone) - clone.appendChild(lastClone); - - if (!firstClone) - firstClone = clone; - - lastClone = clone; - } - } - - // Never split block elements if the format is mixed - if (split && (!format.mixed || !isBlock(format_root))) - container = dom.split(format_root, container); - - // Wrap container in cloned formats - if (lastClone) { - target.parentNode.insertBefore(lastClone, target); - firstClone.appendChild(target); - } - } - - return container; - }; - - function splitToFormatRoot(container) { - return wrapAndSplit(findFormatRoot(container), container, container, true); - }; - - function unwrap(start) { - var node = dom.get(start ? '_start' : '_end'), - out = node[start ? 'firstChild' : 'lastChild']; - - dom.remove(node, 1); - - return out; - }; - - function removeRngStyle(rng) { - var startContainer, endContainer; - - rng = expandRng(rng, formatList, TRUE); - - if (format.split) { - startContainer = getContainer(rng, TRUE); - endContainer = getContainer(rng); - - if (startContainer != endContainer) { - // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); - - // Split start/end - splitToFormatRoot(startContainer); - splitToFormatRoot(endContainer); - - // Unwrap start/end to get real elements again - startContainer = unwrap(TRUE); - endContainer = unwrap(); - } else - startContainer = endContainer = splitToFormatRoot(startContainer); - - // Update range positions since they might have changed after the split operations - rng.startContainer = startContainer.parentNode; - rng.startOffset = nodeIndex(startContainer); - rng.endContainer = endContainer.parentNode; - rng.endOffset = nodeIndex(endContainer) + 1; - } - - // Remove items between start/end - rangeUtils.walk(rng, function(nodes) { - each(nodes, function(node) { - process(node); - }); - }); - }; - - // Handle node - if (node) { - rng = dom.createRng(); - rng.setStartBefore(node); - rng.setEndAfter(node); - removeRngStyle(rng); - return; - } - - if (!selection.isCollapsed() || !format.inline) { - bookmark = selection.getBookmark(); - removeRngStyle(selection.getRng(TRUE)); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } else - performCaretAction('remove', name, vars); - }; - - function toggle(name, vars, node) { - if (match(name, vars, node)) - remove(name, vars, node); - else - apply(name, vars, node); - }; - - function matchNode(node, name, vars) { - var formatList = get(name), format, i, classes; - - function matchItems(node, format, item_name) { - var key, value, items = format[item_name], i; - - // Check all items - if (items) { - // Non indexed object - if (items.length === undefined) { - for (key in items) { - if (items.hasOwnProperty(key)) { - if (item_name === 'attributes') - value = dom.getAttrib(node, key); - else - value = getStyle(node, key); - - if (!isEq(value, replaceVars(items[key], vars))) - return; - } - } - } else { - // Only one match needed for indexed arrays - for (i = 0; i < items.length; i++) { - if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) - return format; - } - } - } - - return format; - }; - - if (formatList && node) { - // Check each format in list - for (i = 0; i < formatList.length; i++) { - format = formatList[i]; - - // Name name, attributes, styles and classes - if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { - // Match classes - if (classes = format.classes) { - for (i = 0; i < classes.length; i++) { - if (!dom.hasClass(node, classes[i])) - return; - } - } - - return format; - } - } - } - }; - - function match(name, vars, node) { - var startNode, i; - - function matchParents(node) { - // Find first node with similar format settings - node = dom.getParent(node, function(node) { - return !!matchNode(node, name, vars); - }); - - // Do an exact check on the similar format element - return matchNode(node, name, vars); - }; - - // Check specified node - if (node) - return matchParents(node); - - // Check pending formats - if (selection.isCollapsed()) { - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - if (pendingFormats.apply[i].name == name) - return true; - } - - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - if (pendingFormats.remove[i].name == name) - return false; - } - - return matchParents(selection.getNode()); - } - - // Check selected node - node = selection.getNode(); - if (matchParents(node)) - return TRUE; - - // Check start node if it's different - startNode = selection.getStart(); - if (startNode != node) { - if (matchParents(startNode)) - return TRUE; - } - - return FALSE; - }; - - function matchAll(names, vars) { - var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; - - // If the selection is collapsed then check pending formats - if (selection.isCollapsed()) { - for (ni = 0; ni < names.length; ni++) { - // If the name is to be removed, then stop it from being added - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - name = names[ni]; - - if (pendingFormats.remove[i].name == name) { - checkedMap[name] = true; - break; - } - } - } - - // If the format is to be applied - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - for (ni = 0; ni < names.length; ni++) { - name = names[ni]; - - if (!checkedMap[name] && pendingFormats.apply[i].name == name) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - } - } - - // Check start of selection for formats - startElement = selection.getStart(); - dom.getParent(startElement, function(node) { - var i, name; - - for (i = 0; i < names.length; i++) { - name = names[i]; - - if (!checkedMap[name] && matchNode(node, name, vars)) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - }); - - return matchedFormatNames; - }; - - function canApply(name) { - var formatList = get(name), startNode, parents, i, x, selector; - - if (formatList) { - startNode = selection.getStart(); - parents = getParents(startNode); - - for (x = formatList.length - 1; x >= 0; x--) { - selector = formatList[x].selector; - - // Format is not selector based, then always return TRUE - if (!selector) - return TRUE; - - for (i = parents.length - 1; i >= 0; i--) { - if (dom.is(parents[i], selector)) - return TRUE; - } - } - } - - return FALSE; - }; - - // Expose to public - tinymce.extend(this, { - get : get, - register : register, - apply : apply, - remove : remove, - toggle : toggle, - match : match, - matchAll : matchAll, - matchNode : matchNode, - canApply : canApply - }); - - // Private functions - - function matchName(node, format) { - // Check for inline match - if (isEq(node, format.inline)) - return TRUE; - - // Check for block match - if (isEq(node, format.block)) - return TRUE; - - // Check for selector match - if (format.selector) - return dom.is(node, format.selector); - }; - - function isEq(str1, str2) { - str1 = str1 || ''; - str2 = str2 || ''; - - str1 = '' + (str1.nodeName || str1); - str2 = '' + (str2.nodeName || str2); - - return str1.toLowerCase() == str2.toLowerCase(); - }; - - function getStyle(node, name) { - var styleVal = dom.getStyle(node, name); - - // Force the format to hex - if (name == 'color' || name == 'backgroundColor') - styleVal = dom.toHex(styleVal); - - // Opera will return bold as 700 - if (name == 'fontWeight' && styleVal == 700) - styleVal = 'bold'; - - return '' + styleVal; - }; - - function replaceVars(value, vars) { - if (typeof(value) != "string") - value = value(vars); - else if (vars) { - value = value.replace(/%(\w+)/g, function(str, name) { - return vars[name] || str; - }); - } - - return value; - }; - - function isWhiteSpaceNode(node) { - return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue); - }; - - function wrap(node, name, attrs) { - var wrapper = dom.create(name, attrs); - - node.parentNode.insertBefore(wrapper, node); - wrapper.appendChild(node); - - return wrapper; - }; - - function expandRng(rng, format, remove) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, sibling, lastIdx; - - // This function walks up the tree if there is no siblings before/after the node - function findParentContainer(container, child_name, sibling_name, root) { - var parent, child; - - root = root || dom.getRoot(); - - for (;;) { - // Check if we can move up are we at root level or body level - parent = container.parentNode; - - // Stop expanding on block elements or root depending on format - if (parent == root || (!format[0].block_expand && isBlock(parent))) - return container; - - for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return container; - - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return container; - } - - container = container.parentNode; - } - - return container; - }; - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - lastIdx = startContainer.childNodes.length - 1; - startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; - - if (startContainer.nodeType == 3) - startOffset = 0; - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - lastIdx = endContainer.childNodes.length - 1; - endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; - - if (endContainer.nodeType == 3) - endOffset = endContainer.nodeValue.length; - } - - // Exclude bookmark nodes if possible - if (isBookmarkNode(startContainer.parentNode)) - startContainer = startContainer.parentNode; - - if (isBookmarkNode(startContainer)) - startContainer = startContainer.nextSibling || startContainer; - - if (isBookmarkNode(endContainer.parentNode)) - endContainer = endContainer.parentNode; - - if (isBookmarkNode(endContainer)) - endContainer = endContainer.previousSibling || endContainer; - - // Move start/end point up the tree if the leaves are sharp and if we are in different containers - // Example * becomes !: !

    *texttext*

    ! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - function findSelectorEndPoint(container, sibling_name) { - var parents, i, y; - - if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) - container = container[sibling_name]; - - parents = getParents(container); - for (i = 0; i < parents.length; i++) { - for (y = 0; y < format.length; y++) { - if (dom.is(parents[i], format[y].selector)) - return parents[i]; - } - } - - return container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - function findBlockEndPoint(container, sibling_name, sibling_name2) { - var node; - - // Expand to block of similar type - if (!format[0].wrapper) - node = dom.getParent(container, format[0].block); - - // Expand to first wrappable block element or any block element - if (!node) - node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); - - // Exclude inner lists from wrapping - if (node && format[0].wrapper) - node = getParents(node, 'ul,ol').reverse()[0] || node; - - // Didn't find a block element look for first/last wrappable element - if (!node) { - node = container; - - while (node[sibling_name] && !isBlock(node[sibling_name])) { - node = node[sibling_name]; - - // Break on BR but include it will be removed later on - // we can't remove it now since we need to check if it can be wrapped - if (isEq(node, 'br')) - break; - } - } - - return node || container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - - if (!isBlock(endContainer)) - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer : startContainer, - startOffset : startOffset, - endContainer : endContainer, - endOffset : endOffset - }; - } - - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format)) - return FALSE; - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(getStyle(compare_node, name), value)) - dom.setStyle(node, name, ''); - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') == '') { - node.removeAttribute('style'); - node.removeAttribute('_mce_style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\w+/.test(cls)) - valueOut += (valueOut ? ' ' : '') + cls; - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") - node.removeAttribute('className'); - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) - node.removeAttribute('_mce_' + name); - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) - dom.removeClass(node, value); - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName.indexOf('_') !== 0) - return FALSE; - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - }; - - function removeNode(node, format) { - var parentNode = node.parentNode, rootBlockElm; - - if (format.block) { - if (!forcedRootBlock) { - function find(node, next, inc) { - node = getNonWhiteSpaceSibling(node, next, inc); - - return !node || (node.nodeName == 'BR' || isBlock(node)); - }; - - // Append BR elements if needed before we remove the block - if (isBlock(node) && !isBlock(parentNode)) { - if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) - node.insertBefore(dom.create('br'), node.firstChild); - - if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) - node.appendChild(dom.create('br')); - } - } else { - // Wrap the block in a forcedRootBlock if we are at the root of document - if (parentNode == dom.getRoot()) { - if (!format.list_block || !isEq(node, format.list_block)) { - each(tinymce.grep(node.childNodes), function(node) { - if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { - if (!rootBlockElm) - rootBlockElm = wrap(node, forcedRootBlock); - else - rootBlockElm.appendChild(node); - } else - rootBlockElm = 0; - }); - } - } - } - } - - // Never remove nodes that isn't the specified inline element if a selector is specified too - if (format.selector && format.inline && !isEq(format.inline, node)) - return; - - dom.remove(node, 1); - }; - - function getNonWhiteSpaceSibling(node, next, inc) { - if (node) { - next = next ? 'nextSibling' : 'previousSibling'; - - for (node = inc ? node : node[next]; node; node = node[next]) { - if (node.nodeType == 1 || !isWhiteSpaceNode(node)) - return node; - } - } - }; - - function isBookmarkNode(node) { - return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; - }; - - function mergeSiblings(prev, next) { - var marker, sibling, tmpSibling; - - function compareElements(node1, node2) { - // Not the same name - if (node1.nodeName != node2.nodeName) - return FALSE; - - function getAttribs(node) { - var attribs = {}; - - each(dom.getAttribs(node), function(attr) { - var name = attr.nodeName.toLowerCase(); - - // Don't compare internal attributes or style - if (name.indexOf('_') !== 0 && name !== 'style') - attribs[name] = dom.getAttrib(node, name); - }); - - return attribs; - }; - - function compareObjects(obj1, obj2) { - var value, name; - - for (name in obj1) { - // Obj1 has item obj2 doesn't have - if (obj1.hasOwnProperty(name)) { - value = obj2[name]; - - // Obj2 doesn't have obj1 item - if (value === undefined) - return FALSE; - - // Obj2 item has a different value - if (obj1[name] != value) - return FALSE; - - // Delete similar value - delete obj2[name]; - } - } - - // Check if obj 2 has something obj 1 doesn't have - for (name in obj2) { - // Obj2 has item obj1 doesn't have - if (obj2.hasOwnProperty(name)) - return FALSE; - } - - return TRUE; - }; - - // Attribs are not the same - if (!compareObjects(getAttribs(node1), getAttribs(node2))) - return FALSE; - - // Styles are not the same - if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) - return FALSE; - - return TRUE; - }; - - // Check if next/prev exists and that they are elements - if (prev && next) { - function findElementSibling(node, sibling_name) { - for (sibling = node; sibling; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return node; - - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return sibling; - } - - return node; - }; - - // If previous sibling is empty then jump over it - prev = findElementSibling(prev, 'previousSibling'); - next = findElementSibling(next, 'nextSibling'); - - // Compare next and previous nodes - if (compareElements(prev, next)) { - // Append nodes between - for (sibling = prev.nextSibling; sibling && sibling != next;) { - tmpSibling = sibling; - sibling = sibling.nextSibling; - prev.appendChild(tmpSibling); - } - - // Remove next node - dom.remove(next); - - // Move children into prev node - each(tinymce.grep(next.childNodes), function(node) { - prev.appendChild(node); - }); - - return prev; - } - } - - return next; - }; - - function isTextBlock(name) { - return /^(h[1-6]|p|div|pre|address)$/.test(name); - }; - - function getContainer(rng, start) { - var container, offset, lastIdx; - - container = rng[start ? 'startContainer' : 'endContainer']; - offset = rng[start ? 'startOffset' : 'endOffset']; - - if (container.nodeType == 1) { - lastIdx = container.childNodes.length - 1; - - if (!start && offset) - offset--; - - container = container.childNodes[offset > lastIdx ? lastIdx : offset]; - } - - return container; - }; - - function performCaretAction(type, name, vars) { - var i, currentPendingFormats = pendingFormats[type], - otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply']; - - function hasPending() { - return pendingFormats.apply.length || pendingFormats.remove.length; - }; - - function resetPending() { - pendingFormats.apply = []; - pendingFormats.remove = []; - }; - - function perform(caret_node) { - // Apply pending formats - each(pendingFormats.apply.reverse(), function(item) { - apply(item.name, item.vars, caret_node); - }); - - // Remove pending formats - each(pendingFormats.remove.reverse(), function(item) { - remove(item.name, item.vars, caret_node); - }); - - dom.remove(caret_node, 1); - resetPending(); - }; - - // Check if it already exists then ignore it - for (i = currentPendingFormats.length - 1; i >= 0; i--) { - if (currentPendingFormats[i].name == name) - return; - } - - currentPendingFormats.push({name : name, vars : vars}); - - // Check if it's in the other type, then remove it - for (i = otherPendingFormats.length - 1; i >= 0; i--) { - if (otherPendingFormats[i].name == name) - otherPendingFormats.splice(i, 1); - } - - // Pending apply or remove formats - if (hasPending()) { - ed.getDoc().execCommand('FontName', false, 'mceinline'); - - // IE will convert the current word - each(dom.select('font,span'), function(node) { - var bookmark; - - if (isCaretNode(node)) { - bookmark = selection.getBookmark(); - perform(node); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } - }); - - // Only register listeners once if we need to - if (!pendingFormats.isListening && hasPending()) { - pendingFormats.isListening = true; - - each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { - ed[event].addToTop(function(ed, e) { - if (hasPending()) { - each(dom.select('font,span'), function(node) { - var bookmark, textNode, rng; - - // Look for marker - if (isCaretNode(node)) { - textNode = node.firstChild; - - perform(node); - - rng = dom.createRng(); - rng.setStart(textNode, textNode.nodeValue.length); - rng.setEnd(textNode, textNode.nodeValue.length); - selection.setRng(rng); - ed.nodeChanged(); - } - }); - - // Always unbind and clear pending styles on keyup - if (e.type == 'keyup' || e.type == 'mouseup') - resetPending(); - } - }); - }); - } - } - }; - }; -})(tinymce); - -tinymce.onAddEditor.add(function(tinymce, ed) { - var filters, fontSizes, dom, settings = ed.settings; - - if (settings.inline_styles) { - fontSizes = tinymce.explode(settings.font_size_style_values); - - function replaceWithSpan(node, styles) { - dom.replace(dom.create('span', { - style : styles - }), node, 1); - }; - - filters = { - font : function(dom, node) { - replaceWithSpan(node, { - backgroundColor : node.style.backgroundColor, - color : node.color, - fontFamily : node.face, - fontSize : fontSizes[parseInt(node.size) - 1] - }); - }, - - u : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'underline' - }); - }, - - strike : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'line-through' - }); - } - }; - - function convert(editor, params) { - dom = editor.dom; - - if (settings.convert_fonts_to_spans) { - tinymce.each(dom.select('font,u,strike', params.node), function(node) { - filters[node.nodeName.toLowerCase()](ed.dom, node); - }); - } - }; - - ed.onPreProcess.add(convert); - - ed.onInit.add(function() { - ed.selection.onSetContent.add(convert); - }); - } -}); - +(function(win) { + var whiteSpaceRe = /^\s*|\s*$/g, + undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; + + var tinymce = { + majorVersion : '3', + + minorVersion : '4.7', + + releaseDate : '2011-11-03', + + _init : function() { + var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; + + t.isOpera = win.opera && opera.buildNumber; + + t.isWebKit = /WebKit/.test(ua); + + t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); + + t.isIE6 = t.isIE && /MSIE [56]/.test(ua); + + t.isIE7 = t.isIE && /MSIE [7]/.test(ua); + + t.isIE8 = t.isIE && /MSIE [8]/.test(ua); + + t.isIE9 = t.isIE && /MSIE [9]/.test(ua); + + t.isGecko = !t.isWebKit && /Gecko/.test(ua); + + t.isMac = ua.indexOf('Mac') != -1; + + t.isAir = /adobeair/i.test(ua); + + t.isIDevice = /(iPad|iPhone)/.test(ua); + + t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; + + // TinyMCE .NET webcontrol might be setting the values for TinyMCE + if (win.tinyMCEPreInit) { + t.suffix = tinyMCEPreInit.suffix; + t.baseURL = tinyMCEPreInit.base; + t.query = tinyMCEPreInit.query; + return; + } + + // Get suffix and base + t.suffix = ''; + + // If base element found, add that infront of baseURL + nl = d.getElementsByTagName('base'); + for (i=0; i : + s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); + cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name + + // Create namespace for new class + ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); + + // Class already exists + if (ns[cn]) + return; + + // Make pure static class + if (s[2] == 'static') { + ns[cn] = p; + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn]); + + return; + } + + // Create default constructor + if (!p[cn]) { + p[cn] = function() {}; + de = 1; + } + + // Add constructor and methods + ns[cn] = p[cn]; + t.extend(ns[cn].prototype, p); + + // Extend + if (s[5]) { + sp = t.resolve(s[5]).prototype; + scn = s[5].match(/\.(\w+)$/i)[1]; // Class name + + // Extend constructor + c = ns[cn]; + if (de) { + // Add passthrough constructor + ns[cn] = function() { + return sp[scn].apply(this, arguments); + }; + } else { + // Add inherit constructor + ns[cn] = function() { + this.parent = sp[scn]; + return c.apply(this, arguments); + }; + } + ns[cn].prototype[cn] = ns[cn]; + + // Add super methods + t.each(sp, function(f, n) { + ns[cn].prototype[n] = sp[n]; + }); + + // Add overridden methods + t.each(p, function(f, n) { + // Extend methods if needed + if (sp[n]) { + ns[cn].prototype[n] = function() { + this.parent = sp[n]; + return f.apply(this, arguments); + }; + } else { + if (n != cn) + ns[cn].prototype[n] = f; + } + }); + } + + // Add static methods + t.each(p['static'], function(f, n) { + ns[cn][n] = f; + }); + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn].prototype); + }, + + walk : function(o, f, n, s) { + s = s || this; + + if (o) { + if (n) + o = o[n]; + + tinymce.each(o, function(o, i) { + if (f.call(s, o, i, n) === false) + return false; + + tinymce.walk(o, f, n, s); + }); + } + }, + + createNS : function(n, o) { + var i, v; + + o = o || win; + + n = n.split('.'); + for (i=0; i= items.length) { + for (i = 0, l = base.length; i < l; i++) { + if (i >= items.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (base.length < items.length) { + for (i = 0, l = items.length; i < l; i++) { + if (i >= base.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (bp == 1) + return path; + + for (i = 0, l = base.length - (bp - 1); i < l; i++) + out += "../"; + + for (i = bp - 1, l = items.length; i < l; i++) { + if (i != bp - 1) + out += "/" + items[i]; + else + out += items[i]; + } + + return out; + }, + + toAbsPath : function(base, path) { + var i, nb = 0, o = [], tr, outPath; + + // Split paths + tr = /\/$/.test(path) ? '/' : ''; + base = base.split('/'); + path = path.split('/'); + + // Remove empty chunks + each(base, function(k) { + if (k) + o.push(k); + }); + + base = o; + + // Merge relURLParts chunks + for (i = path.length - 1, o = []; i >= 0; i--) { + // Ignore empty or . + if (path[i].length == 0 || path[i] == ".") + continue; + + // Is parent + if (path[i] == '..') { + nb++; + continue; + } + + // Move up + if (nb > 0) { + nb--; + continue; + } + + o.push(path[i]); + } + + i = base.length - nb; + + // If /a/b/c or / + if (i <= 0) + outPath = o.reverse().join('/'); + else + outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); + + // Add front / if it's needed + if (outPath.indexOf('/') !== 0) + outPath = '/' + outPath; + + // Add traling / if it's needed + if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) + outPath += tr; + + return outPath; + }, + + getURI : function(nh) { + var s, t = this; + + // Rebuild source + if (!t.source || nh) { + s = ''; + + if (!nh) { + if (t.protocol) + s += t.protocol + '://'; + + if (t.userInfo) + s += t.userInfo + '@'; + + if (t.host) + s += t.host; + + if (t.port) + s += ':' + t.port; + } + + if (t.path) + s += t.path; + + if (t.query) + s += '?' + t.query; + + if (t.anchor) + s += '#' + t.anchor; + + t.source = s; + } + + return t.source; + } + }); +})(); + +(function() { + var each = tinymce.each; + + tinymce.create('static tinymce.util.Cookie', { + getHash : function(n) { + var v = this.get(n), h; + + if (v) { + each(v.split('&'), function(v) { + v = v.split('='); + h = h || {}; + h[unescape(v[0])] = unescape(v[1]); + }); + } + + return h; + }, + + setHash : function(n, v, e, p, d, s) { + var o = ''; + + each(v, function(v, k) { + o += (!o ? '' : '&') + escape(k) + '=' + escape(v); + }); + + this.set(n, o, e, p, d, s); + }, + + get : function(n) { + var c = document.cookie, e, p = n + "=", b; + + // Strict mode + if (!c) + return; + + b = c.indexOf("; " + p); + + if (b == -1) { + b = c.indexOf(p); + + if (b != 0) + return null; + } else + b += 2; + + e = c.indexOf(";", b); + + if (e == -1) + e = c.length; + + return unescape(c.substring(b + p.length, e)); + }, + + set : function(n, v, e, p, d, s) { + document.cookie = n + "=" + escape(v) + + ((e) ? "; expires=" + e.toGMTString() : "") + + ((p) ? "; path=" + escape(p) : "") + + ((d) ? "; domain=" + d : "") + + ((s) ? "; secure" : ""); + }, + + remove : function(n, p) { + var d = new Date(); + + d.setTime(d.getTime() - 1000); + + this.set(n, '', d, p, d); + } + }); +})(); + +(function() { + function serialize(o, quote) { + var i, v, t; + + quote = quote || '"'; + + if (o == null) + return 'null'; + + t = typeof o; + + if (t == 'string') { + v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; + + return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { + // Make sure single quotes never get encoded inside double quotes for JSON compatibility + if (quote === '"' && a === "'") + return a; + + i = v.indexOf(b); + + if (i + 1) + return '\\' + v.charAt(i + 1); + + a = b.charCodeAt().toString(16); + + return '\\u' + '0000'.substring(a.length) + a; + }) + quote; + } + + if (t == 'object') { + if (o.hasOwnProperty && o instanceof Array) { + for (i=0, v = '['; i 0 ? ',' : '') + serialize(o[i], quote); + + return v + ']'; + } + + v = '{'; + + for (i in o) { + if (o.hasOwnProperty(i)) { + v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : ''; + } + } + + return v + '}'; + } + + return '' + o; + }; + + tinymce.util.JSON = { + serialize: serialize, + + parse: function(s) { + try { + return eval('(' + s + ')'); + } catch (ex) { + // Ignore + } + } + + }; +})(); + +tinymce.create('static tinymce.util.XHR', { + send : function(o) { + var x, t, w = window, c = 0; + + // Default settings + o.scope = o.scope || this; + o.success_scope = o.success_scope || o.scope; + o.error_scope = o.error_scope || o.scope; + o.async = o.async === false ? false : true; + o.data = o.data || ''; + + function get(s) { + x = 0; + + try { + x = new ActiveXObject(s); + } catch (ex) { + } + + return x; + }; + + x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); + + if (x) { + if (x.overrideMimeType) + x.overrideMimeType(o.content_type); + + x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); + + if (o.content_type) + x.setRequestHeader('Content-Type', o.content_type); + + x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + x.send(o.data); + + function ready() { + if (!o.async || x.readyState == 4 || c++ > 10000) { + if (o.success && c < 10000 && x.status == 200) + o.success.call(o.success_scope, '' + x.responseText, x, o); + else if (o.error) + o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); + + x = null; + } else + w.setTimeout(ready, 10); + }; + + // Syncronous request + if (!o.async) + return ready(); + + // Wait for response, onReadyStateChange can not be used since it leaks memory in IE + t = w.setTimeout(ready, 10); + } + } +}); + +(function() { + var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; + + tinymce.create('tinymce.util.JSONRequest', { + JSONRequest : function(s) { + this.settings = extend({ + }, s); + this.count = 0; + }, + + send : function(o) { + var ecb = o.error, scb = o.success; + + o = extend(this.settings, o); + + o.success = function(c, x) { + c = JSON.parse(c); + + if (typeof(c) == 'undefined') { + c = { + error : 'JSON Parse error.' + }; + } + + if (c.error) + ecb.call(o.error_scope || o.scope, c.error, x); + else + scb.call(o.success_scope || o.scope, c.result); + }; + + o.error = function(ty, x) { + if (ecb) + ecb.call(o.error_scope || o.scope, ty, x); + }; + + o.data = JSON.serialize({ + id : o.id || 'c' + (this.count++), + method : o.method, + params : o.params + }); + + // JSON content type for Ruby on rails. Bug: #1883287 + o.content_type = 'application/json'; + + XHR.send(o); + }, + + 'static' : { + sendRPC : function(o) { + return new tinymce.util.JSONRequest().send(o); + } + } + }); +}()); +(function(tinymce){ + tinymce.VK = { + DELETE: 46, + BACKSPACE: 8, + ENTER: 13, + TAB: 9, + SPACEBAR: 32, + UP: 38, + DOWN: 40 + } +})(tinymce); + +(function(tinymce) { + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE; + + function cleanupStylesWhenDeleting(ed) { + var dom = ed.dom, selection = ed.selection; + + ed.onKeyDown.add(function(ed, e) { + var rng, blockElm, node, clonedSpan, isDelete; + + isDelete = e.keyCode == DELETE; + if (isDelete || e.keyCode == BACKSPACE) { + e.preventDefault(); + rng = selection.getRng(); + + // Find root block + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + + // On delete clone the root span of the next block element + if (isDelete) + blockElm = dom.getNext(blockElm, dom.isBlock); + + // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace + if (blockElm) { + node = blockElm.firstChild; + + // Ignore empty text nodes + while (node && node.nodeType == 3 && node.nodeValue.length == 0) + node = node.nextSibling; + + if (node && node.nodeName === 'SPAN') { + clonedSpan = node.cloneNode(false); + } + } + + // Do the backspace/delete actiopn + ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Find all odd apple-style-spans + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { + var bm = selection.getBookmark(); + + if (clonedSpan) { + dom.replace(clonedSpan.cloneNode(false), span, true); + } else { + dom.remove(span, true); + } + + // Restore the selection + selection.moveToBookmark(bm); + }); + } + }); + }; + + function emptyEditorWhenDeleting(ed) { + ed.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == DELETE || keyCode == BACKSPACE) { + if (ed.dom.isEmpty(ed.getBody())) { + ed.setContent('', {format : 'raw'}); + ed.nodeChanged(); + return; + } + } + }); + }; + + function inputMethodFocus(ed) { + ed.dom.bind(ed.getDoc(), 'focusin', function() { + ed.selection.setRng(ed.selection.getRng()); + }); + }; + + function removeHrOnBackspace(ed) { + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode === BACKSPACE) { + if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) { + var node = ed.selection.getNode(); + var previousSibling = node.previousSibling; + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { + ed.dom.remove(previousSibling); + tinymce.dom.Event.cancel(e); + } + } + } + }) + } + + function focusBody(ed) { + // Fix for a focus bug in FF 3.x where the body element + // wouldn't get proper focus if the user clicked on the HTML element + if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 + ed.onMouseDown.add(function(ed, e) { + if (e.target.nodeName === "HTML") { + var body = ed.getBody(); + + // Blur the body it's focused but not correctly focused + body.blur(); + + // Refocus the body after a little while + setTimeout(function() { + body.focus(); + }, 0); + } + }); + } + }; + + function selectControlElements(ed) { + ed.onClick.add(function(ed, e) { + e = e.target; + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // Needs tobe the setBaseAndExtend or it will fail to select floated images + if (/^(IMG|HR)$/.test(e.nodeName)) + ed.selection.getSel().setBaseAndExtent(e, 0, e, 1); + + if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor')) + ed.selection.select(e); + + ed.nodeChanged(); + }); + }; + + function selectionChangeNodeChanged(ed) { + var lastRng, selectionTimer; + + ed.dom.bind(ed.getDoc(), 'selectionchange', function() { + if (selectionTimer) { + clearTimeout(selectionTimer); + selectionTimer = 0; + } + + selectionTimer = window.setTimeout(function() { + var rng = ed.selection.getRng(); + + // Compare the ranges to see if it was a real change or not + if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { + ed.nodeChanged(); + lastRng = rng; + } + }, 50); + }); + } + + function ensureBodyHasRoleApplication(ed) { + document.body.setAttribute("role", "application"); + } + + tinymce.create('tinymce.util.Quirks', { + Quirks: function(ed) { + // WebKit + if (tinymce.isWebKit) { + cleanupStylesWhenDeleting(ed); + emptyEditorWhenDeleting(ed); + inputMethodFocus(ed); + selectControlElements(ed); + + // iOS + if (tinymce.isIDevice) { + selectionChangeNodeChanged(ed); + } + } + + // IE + if (tinymce.isIE) { + removeHrOnBackspace(ed); + emptyEditorWhenDeleting(ed); + ensureBodyHasRoleApplication(ed); + } + + // Gecko + if (tinymce.isGecko) { + removeHrOnBackspace(ed); + focusBody(ed); + } + } + }); +})(tinymce); + +(function(tinymce) { + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#x|#)?([\w]+);/g, + asciiMap = { + 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", + 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", + 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", + 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", + 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" + }; + + // Raw entities + baseEntities = { + '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; + + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" + }; + + // Decodes text by using the browser + function nativeDecode(text) { + var elm; + + elm = document.createElement("div"); + elm.innerHTML = text; + + return elm.textContent || elm.innerText || text; + }; + + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; + + if (items) { + items = items.split(','); + radix = radix || 10; + + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } + + return lookup; + } + }; + + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + namedEntities = buildEntitiesLookup( + '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro' + , 32); + + tinymce.html = tinymce.html || {}; + + tinymce.html.Entities = { + encodeRaw : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeNumeric : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) + return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; + + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, + + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; + + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); + }, + + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; + + entities = buildEntitiesLookup(entities) || namedEntities; + + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + }; + + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; + + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); + + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; + + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; + + return Entities.encodeNamed; + } + + // Numeric + if (name.numeric) + return Entities.encodeNumeric; + + // Raw encoder + return Entities.encodeRaw; + }, + + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value, numeric.length === 2 ? 16 : 10); + + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; + + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); + } + + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); + +tinymce.html.Styles = function(settings, schema) { + var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, + urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, + styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, + trimRightRegExp = /\s+$/, + urlColorRegExp = /rgb/, + undef, i, encodingLookup = {}, encodingItems; + + settings = settings || {}; + + encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '\uFEFF' + i; + encodingLookup['\uFEFF' + i] = encodingItems[i]; + } + + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); + + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; + + return '#' + hex(r) + hex(g) + hex(b); + }; + + return { + toHex : function(color) { + return color.replace(rgbRegExp, toHex); + }, + + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; + + function compress(prefix, suffix) { + var top, right, bottom, left; + + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; + + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; + + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; + + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; + + // Compress + styles[prefix + suffix] = left; + delete styles[prefix + '-top' + suffix]; + delete styles[prefix + '-right' + suffix]; + delete styles[prefix + '-bottom' + suffix]; + delete styles[prefix + '-left' + suffix]; + }; + + function canCompress(key) { + var value = styles[key], i; + + if (!value || value.indexOf(' ') < 0) + return; + + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; + } + + styles[key] = value[0]; + + return true; + }; + + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; + + if (!canCompress(b)) + return; + + if (!canCompress(c)) + return; + + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; + + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; + + return encodingLookup[str]; + }; + + // Decodes the specified string by replacing all _ with it's original value \" \' etc + // It will also decode the \" \' if keep_slashes is set to fale or omitted + function decode(str, keep_slashes) { + if (isEncoded) { + str = str.replace(/\uFEFF[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } + + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); + + return str; + } + + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); + }); + + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); + + if (name && value.length > 0) { + // Opera will produce 700 instead of bold in their style values + if (name === 'font-weight' && value === '700') + value = 'bold'; + else if (name === 'color' || name === 'background-color') // Lowercase colors like RED + value = value.toLowerCase(); + + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); + + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) { + str = str || str2; + + if (str) { + str = decode(str); + + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } + + url = decode(url || url2 || url3); + + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); + + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }); + + styles[name] = isEncoded ? decode(value, true) : value; + } + + styleRegExp.lastIndex = matches.index + matches[0].length; + } + + // Compress the styles to reduce it's size for example IE will expand styles + compress("border", ""); + compress("border", "-width"); + compress("border", "-color"); + compress("border", "-style"); + compress("padding", ""); + compress("margin", ""); + compress2('border', 'border-width', 'border-style', 'border-color'); + + // Remove pointless border, IE produces these + if (styles.border === 'medium none') + delete styles.border; + } + + return styles; + }, + + serialize : function(styles, element_name) { + var css = '', name, value; + + function serializeStyles(name) { + var styleList, i, l, value; + + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; + + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(element_name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + + return css; + } + }; +}; + +(function(tinymce) { + var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {}, + defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each; + + function split(str, delim) { + return str.split(delim || ','); + }; + + function unpack(lookup, data) { + var key, elements = {}; + + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; + + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } + + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); + + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) + } + }); + + return elements; + }; + + // Build a lookup table for block elements both lowercase and uppercase + blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + + 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + + 'noscript,menu,isindex,samp,header,footer,article,section,hgroup'; + blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase())); + + // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size + transitional = unpack({ + Z : 'H|K|N|O|P', + Y : 'X|form|R|Q', + ZG : 'E|span|width|align|char|charoff|valign', + X : 'p|T|div|U|W|isindex|fieldset|table', + ZF : 'E|align|char|charoff|valign', + W : 'pre|hr|blockquote|address|center|noframes', + ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', + ZD : '[E][S]', + U : 'ul|ol|dl|menu|dir', + ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', + T : 'h1|h2|h3|h4|h5|h6', + ZB : 'X|S|Q', + S : 'R|P', + ZA : 'a|G|J|M|O|P', + R : 'a|H|K|N|O', + Q : 'noscript|P', + P : 'ins|del|script', + O : 'input|select|textarea|label|button', + N : 'M|L', + M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', + L : 'sub|sup', + K : 'J|I', + J : 'tt|i|b|u|s|strike', + I : 'big|small|font|basefont', + H : 'G|F', + G : 'br|span|bdo', + F : 'object|applet|img|map|iframe', + E : 'A|B|C', + D : 'accesskey|tabindex|onfocus|onblur', + C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : 'lang|xml:lang|dir', + A : 'id|class|style|title' + }, 'script[id|charset|type|language|src|defer|xml:space][]' + + 'style[B|id|type|media|title|xml:space][]' + + 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + + 'param[id|name|value|valuetype|type][]' + + 'p[E|align][#|S]' + + 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + + 'br[A|clear][]' + + 'span[E][#|S]' + + 'bdo[A|C|B][#|S]' + + 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + + 'h1[E|align][#|S]' + + 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + + 'map[B|C|A|name][X|form|Q|area]' + + 'h2[E|align][#|S]' + + 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + + 'h3[E|align][#|S]' + + 'tt[E][#|S]' + + 'i[E][#|S]' + + 'b[E][#|S]' + + 'u[E][#|S]' + + 's[E][#|S]' + + 'strike[E][#|S]' + + 'big[E][#|S]' + + 'small[E][#|S]' + + 'font[A|B|size|color|face][#|S]' + + 'basefont[id|size|color|face][]' + + 'em[E][#|S]' + + 'strong[E][#|S]' + + 'dfn[E][#|S]' + + 'code[E][#|S]' + + 'q[E|cite][#|S]' + + 'samp[E][#|S]' + + 'kbd[E][#|S]' + + 'var[E][#|S]' + + 'cite[E][#|S]' + + 'abbr[E][#|S]' + + 'acronym[E][#|S]' + + 'sub[E][#|S]' + + 'sup[E][#|S]' + + 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + + 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + + 'optgroup[E|disabled|label][option]' + + 'option[E|selected|disabled|label|value][]' + + 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + + 'label[E|for|accesskey|onfocus|onblur][#|S]' + + 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + + 'h4[E|align][#|S]' + + 'ins[E|cite|datetime][#|Y]' + + 'h5[E|align][#|S]' + + 'del[E|cite|datetime][#|Y]' + + 'h6[E|align][#|S]' + + 'div[E|align][#|Y]' + + 'ul[E|type|compact][li]' + + 'li[E|type|value][#|Y]' + + 'ol[E|type|compact|start][li]' + + 'dl[E|compact][dt|dd]' + + 'dt[E][#|S]' + + 'dd[E][#|Y]' + + 'menu[E|compact][li]' + + 'dir[E|compact][li]' + + 'pre[E|width|xml:space][#|ZA]' + + 'hr[E|align|noshade|size|width][]' + + 'blockquote[E|cite][#|Y]' + + 'address[E][#|S|p]' + + 'center[E][#|Y]' + + 'noframes[E][#|Y]' + + 'isindex[A|B|prompt][]' + + 'fieldset[E][#|legend|Y]' + + 'legend[E|accesskey|align][#|S]' + + 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + + 'caption[E|align][#|S]' + + 'col[ZG][]' + + 'colgroup[ZG][col]' + + 'thead[ZF][tr]' + + 'tr[ZF|bgcolor][th|td]' + + 'th[E|ZE][#|Y]' + + 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + + 'noscript[E][#|Y]' + + 'td[E|ZE][#|Y]' + + 'tfoot[ZF][tr]' + + 'tbody[ZF][tr]' + + 'area[E|D|shape|coords|href|nohref|alt|target][]' + + 'base[id|href|target][]' + + 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' + ); + + boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls'); + shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source'); + nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap); + defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea'); + selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); + + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap; + + settings = settings || {}; + + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; + + // Build styles list + if (settings.valid_styles) { + validStyles = {}; + + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); + }); + } + + whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap; + + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; + + // Parses the specified valid_elements string and adds to the current rules + // This function is a bit hard to read since it's heavily optimized for speed + function addValidElements(valid_elements) { + var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, + elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, + attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, + hasPatternsRegExp = /[*?+]/; + + if (valid_elements) { + // Split valid elements into an array with rules + valid_elements = split(valid_elements); + + if (elements['@']) { + globalAttributes = elements['@'].attributes; + globalAttributesOrder = elements['@'].attributesOrder; + } + + // Loop all rules + for (ei = 0, el = valid_elements.length; ei < el; ei++) { + // Parse element rule + matches = elementRuleRegExp.exec(valid_elements[ei]); + if (matches) { + // Setup local names for matches + prefix = matches[1]; + elementName = matches[2]; + outputName = matches[3]; + attrData = matches[4]; + + // Create new attributes and attributesOrder + attributes = {}; + attributesOrder = []; + + // Create the new element + element = { + attributes : attributes, + attributesOrder : attributesOrder + }; + + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; + + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; + + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; + + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); + } + + // Attributes defined + if (attrData) { + attrData = split(attrData, '|'); + for (ai = 0, al = attrData.length; ai < al; ai++) { + matches = attrRuleRegExp.exec(attrData[ai]); + if (matches) { + attr = {}; + attrType = matches[1]; + attrName = matches[2].replace(/::/g, ':'); + prefix = matches[3]; + value = matches[4]; + + // Required + if (attrType === '!') { + element.attributesRequired = element.attributesRequired || []; + element.attributesRequired.push(attrName); + attr.required = true; + } + + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } + + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } + + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } + + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } + + // Check for attribute patterns + if (hasPatternsRegExp.test(attrName)) { + element.attributePatterns = element.attributePatterns || []; + attr.pattern = patternToRegExp(attrName); + element.attributePatterns.push(attr); + } else { + // Add attribute to order list if it doesn't already exist + if (!attributes[attrName]) + attributesOrder.push(attrName); + + attributes[attrName] = attr; + } + } + } + } + + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } + + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } + + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } + } + } + }; + + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; + + addValidElements(valid_elements); + + each(transitional, function(element, name) { + children[name] = element.children; + }); + }; + + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; + + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + inline = matches[1] === '~', + cloneName = inline ? 'span' : 'div', + name = matches[2]; + + children[name] = children[cloneName]; + customElementsMap[name] = cloneName; + + // If it's not marked as inline then add it to valid block elements + if (!inline) + blockElementsMap[name] = {}; + + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); + } + }; + + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; + + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; + + if (matches) { + prefix = matches[1]; + + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; + + parent = children[matches[2]]; + + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); + } + }; + + function getElementRule(name) { + var element = elements[name], i; + + // Exact match found + if (element) + return element; + + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; + } + }; + + if (!settings.valid_elements) { + // No valid elements defined then clone the elements from the transitional spec + each(transitional, function(element, name) { + elements[name] = { + attributes : element.attributes, + attributesOrder : element.attributesOrder + }; + + children[name] = element.children; + }); + + // Switch these + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); + + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; + + // Remove these if they are empty by default + each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) { + elements[name].removeEmpty = true; + }); + + // Padd these by default + each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { + elements[name].paddEmpty = true; + }); + } else + setValidElements(settings.valid_elements); + + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); + + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); + + // If the user didn't allow span only allow internal spans + if (!getElementRule('span')) + addValidElements('span[!data-mce-type|*]'); + + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } + + self.children = children; + + self.styles = validStyles; + + self.getBoolAttrs = function() { + return boolAttrMap; + }; + + self.getBlockElements = function() { + return blockElementsMap; + }; + + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; + + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; + + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; + + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; + + self.isValidChild = function(name, child) { + var parent = children[name]; + + return !!(parent && parent[child]); + }; + + self.getElementRule = getElementRule; + + self.getCustomElements = function() { + return customElementsMap; + }; + + self.addValidElements = addValidElements; + + self.setValidElements = setValidElements; + + self.addCustomElements = addCustomElements; + + self.addValidChildren = addValidChildren; + }; + + // Expose boolMap and blockElementMap as static properties for usage in DOMUtils + tinymce.html.Schema.boolAttrMap = boolAttrMap; + tinymce.html.Schema.blockElementsMap = blockElementsMap; +})(tinymce); + +(function(tinymce) { + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; + + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); + + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; + + // Add handler functions from settings and setup default handlers + tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { + if (name) + self[name] = settings[name] || noop; + }); + + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; + + function processEndTag(name) { + var pos, i; + + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } + + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; + + if (name.valid) + self.end(name.name); + } + + // Remove the open elements from the stack + stack.length = pos; + } + }; + + // Precompile RegExps and map objects + tokenRegExp = new RegExp('<(?:' + + '(?:!--([\\w\\W]*?)-->)|' + // Comment + '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA + '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE + '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI + '(?:\\/([^>]+)>)|' + // End element + '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/)>)' + // Start element + ')', 'g'); + + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; + specialElements = { + 'script' : /<\/script[^>]*>/gi, + 'style' : /<\/style[^>]*>/gi, + 'noscript' : /<\/noscript[^>]*>/gi + }; + + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + removeInternalElements = settings.remove_internals; + fixSelfClosing = settings.fix_self_closing; + isIE = tinymce.isIE; + invalidPrefixRegExp = /^:/; + + while (matches = tokenRegExp.exec(html)) { + // Text + if (index < matches.index) + self.text(decode(html.substr(index, matches.index - index))); + + if (value = matches[6]) { // End element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + processEndTag(value); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + isShortEnded = value in shortEndedElements; + + // Is self closing tag for example an
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } + + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element + + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; + + attrList = []; + attrList.map = {}; + + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; + + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute + + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; + + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } + + // No rule matched + if (i === -1) + attrRule = null; + } + + // No attribute rule found + if (!attrRule) + return; + + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } + + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } + + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; + + if (!(name in attrList.map)) { + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } + + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } + + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } + + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } + + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; + + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; + + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); + + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } + + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } + + index = matches.index + matches[0].length; + } + + // Text + if (index < html.length) + self.text(decode(html.substr(index))); + + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; + + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); + +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; + + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; + + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; + + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; + + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; + + if (sibling) + return sibling; + } + } + }; + + function Node(name, type) { + this.name = name; + this.type = type; + + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } + + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; + + if (node.parent) + node.remove(); + + self.insert(node, self); + self.remove(); + + return self; + }, + + attr : function(name, value) { + var self = this, attrs, i, undef; + + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); + + return self; + } + + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; + + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } + } + + return self; + } + + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; + } + } + } else + attrs.push({name: name, value: value}); + + attrs.map[name] = value; + + return self; + } else { + return attrs.map[name]; + } + } + }, + + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; + + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; + + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; + + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } + + clone.attributes = cloneAttrs; + } + + clone.value = self.value; + clone.shortEnded = self.shortEnded; + + return clone; + }, + + wrap : function(wrapper) { + var self = this; + + self.parent.insert(wrapper, self); + wrapper.append(self); + + return self; + }, + + unwrap : function() { + var self = this, node, next; + + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } + + self.remove(); + }, + + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; + + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; + + if (next) + next.prev = null; + } else { + prev.next = next; + } + + if (parent.lastChild === self) { + parent.lastChild = prev; + + if (prev) + prev.next = null; + } else { + next.prev = prev; + } + + self.parent = self.next = self.prev = null; + } + + return self; + }, + + append : function(node) { + var self = this, last; + + if (node.parent) + node.remove(); + + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; + + node.parent = self; + + return node; + }, + + insert : function(node, ref_node, before) { + var parent; + + if (node.parent) + node.remove(); + + parent = ref_node.parent || this; + + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; + + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; + + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } + + node.parent = parent; + + return node; + }, + + getAll : function(name) { + var self = this, node, collection = []; + + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); + } + + return collection; + }, + + empty : function() { + var self = this, nodes, i, node; + + // Remove all children + if (self.firstChild) { + nodes = []; + + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); + + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } + + self.firstChild = self.lastChild = null; + + return self; + }, + + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; + + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; + + // Keep empty elements like + if (elements[node.name]) + return false; + + // Keep elements with data attributes or name attribute like + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } + } + + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } + + return true; + }, + + walk : function(prev) { + return walk(this, null, prev); + } + }); + + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; + + // Create node + node = new Node(name, typeLookup[name] || 1); + + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } + + return node; + } + }); + + tinymce.html.Node = Node; +})(tinymce); + +(function(tinymce) { + var Node = tinymce.html.Node; + + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); + + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); + + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; + + // Already removed + if (!node.parent) + continue; + + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); + + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); + + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); + + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; + + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } + + currentNode = tempNode; + } + + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } + + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } + + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } + + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } + + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; + + self.filterNode = function(node) { + var i, name, list; + + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; + + if (name in node.attributes.map) { + list = matchedAttributes[name]; + + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } + + return node; + }; + + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; + + if (!list) + nodeFilters[name] = list = []; + + list.push(callback); + }); + }; + + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } + + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; + + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], + endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } + + node = next; + }; + }; + + function createNode(name, type) { + var node = new Node(name, type), list; + + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + return node; + }; + + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; + + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; + + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results + + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, + + text: function(text, raw) { + var textNode; + + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); + + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } + + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, + + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, + + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, + + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, + + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; + + node.append(newNode); + + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); + + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; + + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; + + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } + + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + } + }, + + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!whiteSpaceElements[node.name]) { + // Trim whitespace at beginning of block + for (textNode = node.firstChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } + + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } + + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } + + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like + if (!node.attributes.map.name) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } + + node = node.parent; + } + } + }, schema); + + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; + } + + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); + + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } + + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; + + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } + + return rootNode; + }; + + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = schema.getBlockElements(), + nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the right of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } + + prev = prev.prev; + } + + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } + } + }); + } + } +})(tinymce); + +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; + + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; + + return { + start: function(name, attrs, empty) { + var i, l, attr, value; + + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } + } + + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; + + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + end: function(name) { + var value; + + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ + + html.push(''); + + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, + + cdata: function(text) { + html.push(''); + }, + + comment: function(text) { + html.push(''); + }, + + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); + + if (indent) + html.push('\n'); + }, + + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, + + reset: function() { + html.length = 0; + }, + + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; + +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; + + self.serialize = function(node) { + var handlers, validate; + + validate = settings.validate; + + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, + + // #comment + 8: function(node) { + writer.comment(node.value); + }, + + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, + + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; + + writer.reset(); + + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; + + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; + + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; + + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; + + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + attrs = sortedAttrs; + } + + writer.start(node.name, attrs, isEmpty); + + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + + writer.end(name); + } + } else + handler(node); + } + + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); + + return writer.getContent(); + }; + } +})(tinymce); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + blockElementsMap = tinymce.html.Schema.blockElementsMap, + whiteSpaceRegExp = /^[ \t\r\n]*$/; + + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, + + DOMUtils : function(d, s) { + var t = this, globalStyle, name; + + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); + + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); + + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } + } + + if (isIE && s.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + d.createElement(name); + }); + + // Create all custom elements + for (name in s.schema.getCustomElements()) { + d.createElement(name); + } + } + + tinymce.addUnload(t.destroy, t); + }, + + getRoot : function() { + var t = this, s = t.settings; + + return (s && t.get(s.root_element)) || t.doc.body; + }, + + getViewPort : function(w) { + var d, b; + + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; + + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, + + getRect : function(e) { + var p, t = this, sr; + + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); + + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, + + getSize : function(e) { + var t = this, w, h; + + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); + + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; + + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; + + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, + + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, + + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; + + n = t.get(n); + c = c === undefined; + + if (se.strict_root) + r = r || t.getRoot(); + + // Wrap node name as func + if (is(f, 'string')) { + na = f; + + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } + + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; + + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } + + n = n.parentNode; + } + + return c ? o : null; + }, + + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; + } + + return e; + }, + + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, + + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, + + + add : function(p, n, a, h, c) { + var t = this; + + return this.run(p, function(p) { + var e, k; + + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); + + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } + + return !c ? p.appendChild(e) : e; + }); + }, + + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, + + createHTML : function(n, a, h) { + var o = '', t = this, k; + + o += '<' + n; + + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } + + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; + + return o + ' />'; + }, + + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } + } + + return parent.removeChild(node); + }); + }, + + setStyle : function(n, na, v) { + var t = this; + + return t.run(n, function(e) { + var s, i; + + s = e.style; + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; + + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } + + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; + + case 'float': + isIE ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } + + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, + + getStyle : function(n, na, c) { + n = this.get(n); + + if (!n) + return; + + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } + } + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; + + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; + + return n.style ? n.style[na] : undefined; + }, + + setStyles : function(e, o) { + var t = this, s = t.settings, ol; + + ol = s.update_styles; + s.update_styles = 0; + + each(o, function(v, n) { + t.setStyle(e, n, v); + }); + + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, + + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, + + setAttrib : function(e, n, v) { + var t = this; + + // Whats the point + if (!e || !n) + return; + + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); + + return this.run(e, function(e) { + var s = t.settings; + if (v !== null) { + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); + + return; + } + + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } + + e.style.cssText = v; + break; + + case "class": + e.className = v || ''; // Fix IE null bug + break; + + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + + t.setAttrib(e, 'data-mce-' + n, v, 2); + } + + break; + + case "shape": + e.setAttribute('data-mce-style', v); + break; + } + } + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + }); + }, + + setAttribs : function(e, o) { + var t = this; + + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, + + getAttrib : function(e, n, dv) { + var v, t = this, undef; + + e = t.get(e); + + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; + + if (!is(dv)) + dv = ''; + + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); + + if (v) + return v; + } + + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } + + if (!v) + v = e.getAttribute(n, 2); + + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; + + return v ? n : ''; + } + + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); + + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); + } + } + + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; + + break; + + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; + + break; + + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; + + break; + + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; + + break; + + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; + + break; + + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; + + return dv; + + case 'shape': + v = v.toLowerCase(); + break; + + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); + } + } + + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; + } + + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } + + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } + + return {x : x, y : y}; + }, + + parseStyle : function(st) { + return this.styles.parse(st); + }, + + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, + + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = t.select('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; + } + + head.appendChild(link); + }); + }, + + addClass : function(e, c) { + return this.run(e, function(e) { + var o; + + if (!c) + return 0; + + if (this.hasClass(e, c)) + return e.className; + + o = this.removeClass(e, c); + + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, + + removeClass : function(e, c) { + var t = this, re; + + return t.run(e, function(e) { + var v; + + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); + + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); + } + + return v; + } + + return e.className; + }); + }, + + hasClass : function(n, c) { + n = this.get(n); + + if (!n || !c) + return false; + + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, + + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, + + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, + + isHidden : function(e) { + e = this.get(e); + + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, + + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, + + setHTML : function(element, html) { + var self = this; + + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + element = self.create('div'); + element.innerHTML = '
    ' + html; + + // Add all children from div to target + each (element.childNodes, function(node, i) { + // Skip br element + if (i) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; + + return html; + }); + }, + + getOuterHTML : function(elm) { + var doc, self = this; + + elm = self.get(elm); + + if (!elm) + return null; + + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; + + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); + + return doc.innerHTML; + }, + + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + }; + + return this.run(e, function(e) { + e = t.get(e); + + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; + + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); + } + }); + }, + + decode : Entities.decode, + + encode : Entities.encodeAllRaw, + + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); + + return this.run(node, function(node) { + var parent, nextSibling; + + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); + + return node; + }); + }, + + isBlock : function(node) { + var type = node.nodeType; + + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); + + return !!blockElementsMap[node]; + }, + + replace : function(n, o, k) { + var t = this; + + if (is(o, 'array')) + n = n.cloneNode(true); + + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + rename : function(elm, name) { + var t = this, newElm; + + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); + + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); + + // Replace block + t.replace(newElm, elm, 1); + } + + return newElm || elm; + }, + + findCommonAncestor : function(a, b) { + var ps = a, pe; + + while (ps) { + pe = b; + + while (pe && ps != pe) + pe = pe.parentNode; + + if (ps == pe) + break; + + ps = ps.parentNode; + } + + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; + + return ps; + }, + + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + + function hex(s) { + s = parseInt(s).toString(16); + + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; + + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + + return s; + } + + return s; + }, + + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + + if (t.classes) + return t.classes; + + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); + + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); + + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; + + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + + // Filter classes + if (f && !(v = f(v, ov))) + return; + + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; + + // Import + case 3: + addClasses(r.styleSheet); + break; + } + }); + }; + + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } + + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); + } + }); + + return o; + } + + return f.call(s, e); + }, + + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; + } + + return n.attributes; + }, + + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, parentNode; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    + parentNode = node.parentNode; + if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) { + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } + } + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); + } + + return true; + }, + + destroy : function(s) { + var t = this; + + if (t.events) + t.events.destroy(); + + t.win = t.doc = t.root = t.events = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); + }, + + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; + + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + idx++; + lastNodeType = nodeType; + } + } + + return idx; + }, + + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; + } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; + } + }, + + bind : function(target, name, func, scope) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.add(target, name, func, scope || this); + }, + + unbind : function(target, name, func) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.remove(target, name, func); + }, + + + _findSib : function(node, selector, name) { + var t = this, f = selector; + + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _isRes : function(c) { + // Is live resizble element + return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); + } + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); + } + */ + + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; + + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } + + return s; + } + */ + }); + + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); + +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; + + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, + + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange + }); + + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; + + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; + + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; + + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; + + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; + + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; + } + + t.collapsed = TRUE; + }; + + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; + + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; + + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; + + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; + + function deleteContents() { + _traverse(DELETE); + }; + + function extractContents() { + return _traverse(EXTRACT); + }; + + function cloneContents() { + return _traverse(CLONE); + }; + + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; + + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; + + function surroundContents(n) { + var f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; + + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; + + // Private methods + + function _getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child) + return child; + + return container; + }; + + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; + + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal + + if (offsetA < offsetB) + return -1; // before + + return 1; // after + } + + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; + + if (c) { + offsetC = 0; + n = containerA.firstChild; + + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + + if (offsetA <= offsetC) + return -1; // before + + return 1; // after + } + + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } + + if (c) { + offsetC = 0; + n = containerB.firstChild; + + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } + + if (offsetC < offsetB) + return -1; // before + + return 1; // after + } + + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; + + if (!childA) + childA = cmnRoot; + + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; + + if (!childB) + childB = cmnRoot; + + if (childA == childB) + return 0; // equal + + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before + + if (n == childB) + return 1; // after + + n = n.nextSibling; + } + }; + + function _setEndPoint(st, n, o) { + var ec, sc; + + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; + } + + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); + + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; + + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); + + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } + + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } + + return _traverseCommonAncestors(startNode, endNode, how); + }; + + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; + + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } + + if (how == DELETE) + return; + + frag.appendChild(doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + } + + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; + } + + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + }; + + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = n.cloneNode(FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return n.cloneNode(FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? n.cloneNode(TRUE) : n; + + n.parentNode.removeChild(n); + }; + }; + + ns.Range = Range; +})(tinymce.dom); + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng); + + // Fix for edge case:
    ..
    ab|c
    + if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) { + checkRng = rng.duplicate(); + checkRng.collapse(start); + + offset = -1; + while (parent == checkRng.parentElement()) { + if (checkRng.move('character', -1) == 0) + break; + + offset++; + } + } + + offset = offset || checkRng.text.replace('\r\n', ' ').length; + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng); + + // Get the length of the text to find where the endpoint is relative to it's container + offset = checkRng.text.replace('\r\n', ' ').length; + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var node, parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root && parent.nodeType !== 9) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else { + // Empty node selection for example
    |
    + marker = doc.createTextNode('\uFEFF'); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { + if (startOffset == endOffset - 1) { + try { + ctrlRng = body.createControlRange(); + ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.select(); + return; + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); + + +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + + tinymce.create('tinymce.dom.EventUtils', { + EventUtils : function() { + this.inits = []; + this.events = []; + }, + + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; + + if (n instanceof Array) { + r = []; + + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + o = DOM.get(o); + + if (!o) + return; + + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; + + e = e || window.event; + + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; + + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } + + if (!s) + return f(e); + + return f.call(s, e); + }; + + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; + } + + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); + + return cb; + } + + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); + + t._add(o, n, cb); + + return f; + }, + + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); + + return r; + } + + o = DOM.get(o); + + each(a, function(e, i) { + if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { + a.splice(i, 1); + t._remove(o, n, e.cfunc); + s = true; + return false; + } + }); + + return s; + }, + + clear : function(o) { + var t = this, a = t.events, i, e; + + if (o) { + o = DOM.get(o); + + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; + + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } + }, + + cancel : function(e) { + if (!e) + return false; + + this.stop(e); + + return this.prevent(e); + }, + + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; + + return false; + }, + + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + + return false; + }, + + destroy : function() { + var t = this; + + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); + + t.events = []; + t = null; + }, + + _add : function(o, n, f) { + if (o.attachEvent) + o.attachEvent('on' + n, f); + else if (o.addEventListener) + o.addEventListener(n, f, false); + else + o['on' + n] = f; + }, + + _remove : function(o, n, f) { + if (o) { + try { + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; + } catch (ex) { + // Might fail with permission denined on IE so we just ignore that + } + } + }, + + _pageInit : function(win) { + var t = this; + + // Keep it from running more than once + if (t.domLoaded) + return; + + t.domLoaded = true; + + each(t.inits, function(c) { + c(); + }); + + t.inits = []; + }, + + _wait : function(win) { + var t = this, doc = win.document; + + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } + + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); + + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; + + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + t._pageInit(win); + })(); + } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } + + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, + + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, + + stopPropagation : function() { + this.cancelBubble = true; + } + } + }); + + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); + + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; + + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); + + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/) + , function(k) { + t[k] = function() { + var a = [id], i; + + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); + + a = dom[k].apply(dom, a); + t.update(k); + + return a; + }; + }); + + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, + + getSize : function() { + var n = dom.get(t.id); + + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = t.getXY(); + + t.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = t.getSize(); + + t.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var b; + + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; + + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; + + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } + + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); + + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); + } + } + }); + }; +})(tinymce); + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + + // Add events + each([ + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); + + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); + + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); + + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, + + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); + }, + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); + + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + + if (r.cloneContents) { + n = r.cloneContents(); + + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); + + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; + + if (/\s+$/.test(e.innerHTML)) + wa = ' '; + + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; + }, + + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; + + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; + + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; + + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); + + if (doc.body.childNodes.length == 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); + + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } + } + + // Move to caret marker + caretNode = self.dom.get('__caret'); + + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); + + // Remove the caret position + self.dom.remove('__caret'); + + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } + + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); + } else + rng.pasteHTML(content); + } + + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, + + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; + + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); + + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); + + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; + } + } + + return startElement; + } else { + startElement = rng.startContainer; + + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; + + return startElement; + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e, eo; + + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild || e; + + return e; + } else { + e = r.endContainer; + eo = r.endOffset; + + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; + + if (e && e.nodeType == 3) + return e.parentNode; + + return e; + } + }, + + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + + function findIndex(name, element) { + var index = 0; + + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); + + return index; + }; + + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } + + point.push(offset); + } else { + childNodes = container.childNodes; + + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } + + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } + + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); + + return point; + }; + + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (t.tridentSel) + return t.tridentSel.getBookmark(type); + + return getLocation(); + } + + // Handle simple range + if (type) + return {rng : t.getRng()}; + + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; + + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); + + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) == 0) + rng2.move('character', -1); + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; + + // W3C method + rng2 = rng.cloneRange(); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } + + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); + + return {id : id}; + }, + + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; + + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) + return; + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); + + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } + + return true; + }; + + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); + + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; + + function addBogus(node) { + // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly + if (dom.isBlock(node) && !node.innerHTML) + node.innerHTML = !isIE ? '
    ' : ' '; + + return node; + }; + + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); + + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); + } + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) + t.setRng(bookmark.rng); + } + }, + + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; + + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + setPoint(node, 1); + setPoint(node); + } + + t.setRng(rng); + } + + return node; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; + + return !s || r.collapsed; + }, + + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; + + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } + + rng.collapse(!!to_start); + self.setRng(rng); + }, + + getSel : function() { + var t = this, w = this.win; + + return w.getSelection ? w.getSelection() : w.document.selection; + }, + + getRng : function(w3c) { + var t = this, s, r, elm, doc = t.win.document; + + // Found tridentSel object then we need to use that one + if (w3c && t.tridentSel) + return t.tridentSel.getRangeAt(0); + + try { + if (s = t.getSel()) + r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } + + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + r = doc.createRange(); + r.setStartBefore(elm); + r.setEndAfter(elm); + } + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!r) + r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; + } + } + + return r; + }, + + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); + + if (s) { + t.explicitRange = r; + + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } + + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } + + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } + }, + + setNode : function(n) { + var t = this; + + t.setContent(t.dom.getOuterHTML(n)); + + return n; + }, + + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; + + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); + + if (rng.setStart) { + elm = rng.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } + + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + } + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } + + if (start && start === end) + return start; + } + } + + if (elm && elm.nodeType == 3) + return elm.parentNode; + + return elm; + } + + return rng.item ? rng.item(0) : rng.parentElement(); + }, + + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; + + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); + + if (sb) + bl.push(sb); + + if (sb && eb && sb != eb) { + n = sb; + + var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot()); + while ((n = walker.next()) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } + + if (eb && sb != eb) + bl.push(eb); + + return bl; + }, + + normalize : function() { + var self = this, rng, normalized; + + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; + + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node; + + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; + + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = container.body; + offset = 0; + } + + // If the container is body try move it into the closest text node or position + // TODO: Add more logic here to handle element selection cases + if (container === body) { + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes()) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new tinymce.dom.TreeWalker(container, body); + do { + // Found a text node use that position + if (node.nodeType === 3) { + offset = start ? 0 : node.nodeValue.length - 1; + container = node; + break; + } + + // Found a BR element that we can place the caret before + if (node.nodeName === 'BR') { + offset = dom.nodeIndex(node); + container = node.parentNode; + break; + } + } while (node = (start ? walker.next() : walker.prev())); + + normalized = true; + } + } + } + + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; + + rng = self.getRng(); + + // Normalize the end points + normalizeEndPoint(true); + + if (rng.collapsed) + normalizeEndPoint(); + + // Set the selection if it was normalized + if (normalized) { + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng); + } + }, + + destroy : function(s) { + var t = this; + + t.win = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + }; + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); + + pointRng.select(); + } + } else + endSelection(); + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + }; + + // Detect when user selects outside BODY + dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { + if (e.target.nodeName === 'HTML') { + if (started) + endSelection(); + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Serializer = function(settings, dom, schema) { + var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; + + // Support the old apply_source_formatting option + if (!settings.apply_source_formatting) + settings.indent = false; + + settings.remove_trailing_brs = true; + + // Default DOM and Schema if they are undefined + dom = dom || tinymce.DOM; + schema = schema || new tinymce.html.Schema(settings); + settings.entity_encoding = settings.entity_encoding || 'named'; + + onPreProcess = new tinymce.util.Dispatcher(self); + + onPostProcess = new tinymce.util.Dispatcher(self); + + htmlParser = new tinymce.html.DomParser(settings, schema); + + // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed + htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; + + while (i--) { + node = nodes[i]; + + value = node.attributes.map[internalName]; + if (value !== undef) { + // Set external name to internal value and remove internal + node.attr(name, value.length > 0 ? value : null); + node.attr(internalName, null); + } else { + // No internal attribute found then convert the value we have in the DOM + value = node.attributes.map[name]; + + if (name === "style") + value = dom.serializeStyle(dom.parseStyle(value), node.name); + else if (urlConverter) + value = urlConverter.call(urlConverterScope, value, name, node.name); + + node.attr(name, value.length > 0 ? value : null); + } + } + }); + + // Remove internal classes mceItem<..> + htmlParser.addAttributeFilter('class', function(nodes, name) { + var i = nodes.length, node, value; + + while (i--) { + node = nodes[i]; + value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, ''); + node.attr('class', value.length > 0 ? value : null); + } + }); + + // Remove bookmark elements + htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) + node.remove(); + } + }); + + // Force script into CDATA sections and remove the mce- prefix also add comments around styles + htmlParser.addNodeFilter('script,style', function(nodes, name) { + var i = nodes.length, node, value; + + function trim(value) { + return value.replace(/()/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); + }; + + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; + + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); + + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); + + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); + + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); + + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; + + while (i--) { + node = nodes[i]; + parentNode = node.parent; + + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } + } + }); + } + + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + // Return public methods + return { + schema : schema, + + addNodeFilter : htmlParser.addNodeFilter, + + addAttributeFilter : htmlParser.addAttributeFilter, + + onPreProcess : onPreProcess, + + onPostProcess : onPostProcess, + + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; + + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); + + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; + + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } + + args = args || {}; + args.format = args.format || 'html'; + + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } + + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); + + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args) + ); + + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF|\u200B/g, ''); + + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); + + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; + + args.node = null; + + return args.content; + }, + + addRules : function(rules) { + schema.addValidElements(rules); + }, + + setRules : function(rules) { + schema.setValidElements(rules); + } + }; + }; +})(tinymce); +(function(tinymce) { + tinymce.dom.ScriptLoader = function(settings) { + var QUEUED = 0, + LOADING = 1, + LOADED = 2, + states = {}, + queue = [], + scriptLoadedCallbacks = {}, + queueLoadedCallbacks = [], + loading = 0, + undefined; + + function loadScript(url, callback) { + var t = this, dom = tinymce.DOM, elm, uri, loc, id; + + // Execute callback when script is loaded + function done() { + dom.remove(id); + + if (elm) + elm.onreadystatechange = elm.onload = elm = null; + + callback(); + }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; + + id = dom.uniqueId(); + + if (tinymce.isIE6) { + uri = new tinymce.util.URI(url); + loc = location; + + // If script is from same domain and we + // use IE 6 then use XHR since it's more reliable + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { + tinymce.util.XHR.send({ + url : tinymce._addVer(uri.getURI()), + success : function(content) { + // Create new temp script element + var script = dom.create('script', { + type : 'text/javascript' + }); + + // Evaluate script in global scope + script.text = content; + document.getElementsByTagName('head')[0].appendChild(script); + dom.remove(script); + + done(); + }, + + error : error + }); + + return; + } + } + + // Create new script element + elm = dom.create('script', { + id : id, + type : 'text/javascript', + src : tinymce._addVer(url) + }); + + // Add onload listener for non IE browsers since IE9 + // fires onload event before the script is parsed and executed + if (!tinymce.isIE) + elm.onload = done; + + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; + + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } + + // Most browsers support this feature so we report errors + // for those at least to help users track their missing plugins etc + // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option + /*elm.onerror = function() { + alert('Failed to load: ' + url); + };*/ + + // Add script to document + (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); + }; + + this.isDone = function(url) { + return states[url] == LOADED; + }; + + this.markDone = function(url) { + states[url] = LOADED; + }; + + this.add = this.load = function(url, callback, scope) { + var item, state = states[url]; + + // Add url to load queue + if (state == undefined) { + queue.push(url); + states[url] = QUEUED; + } + + if (callback) { + // Store away callback for later execution + if (!scriptLoadedCallbacks[url]) + scriptLoadedCallbacks[url] = []; + + scriptLoadedCallbacks[url].push({ + func : callback, + scope : scope || this + }); + } + }; + + this.loadQueue = function(callback, scope) { + this.loadScripts(queue, callback, scope); + }; + + this.loadScripts = function(scripts, callback, scope) { + var loadScripts; + + function execScriptLoadedCallbacks(url) { + // Execute URL callback functions + tinymce.each(scriptLoadedCallbacks[url], function(callback) { + callback.func.call(callback.scope); + }); + + scriptLoadedCallbacks[url] = undefined; + }; + + queueLoadedCallbacks.push({ + func : callback, + scope : scope || this + }); + + loadScripts = function() { + var loadingScripts = tinymce.grep(scripts); + + // Current scripts has been handled + scripts.length = 0; + + // Load scripts that needs to be loaded + tinymce.each(loadingScripts, function(url) { + // Script is already loaded then execute script callbacks directly + if (states[url] == LOADED) { + execScriptLoadedCallbacks(url); + return; + } + + // Is script not loading then start loading it + if (states[url] != LOADING) { + states[url] = LOADING; + loading++; + + loadScript(url, function() { + states[url] = LOADED; + loading--; + + execScriptLoadedCallbacks(url); + + // Load more scripts if they where added by the recently loaded script + loadScripts(); + }); + } + }); + + // No scripts are currently loading then execute all pending queue loaded callbacks + if (!loading) { + tinymce.each(queueLoadedCallbacks, function(callback) { + callback.func.call(callback.scope); + }); + + queueLoadedCallbacks.length = 0; + } + }; + + loadScripts(); + }; + }; + + // Global script loader + tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); +})(tinymce); + +tinymce.dom.TreeWalker = function(start_node, root_node) { + var node = start_node; + + function findSibling(node, start_name, sibling_name, shallow) { + var sibling, parent; + + if (node) { + // Walk into nodes if it has a start + if (!shallow && node[start_name]) + return node[start_name]; + + // Return the sibling if it has one + if (node != root_node) { + sibling = node[sibling_name]; + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { + sibling = parent[sibling_name]; + if (sibling) + return sibling; + } + } + } + }; + + this.current = function() { + return node; + }; + + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; + + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); + }; +}; + +(function(tinymce) { + tinymce.dom.RangeUtils = function(dom) { + var INVISIBLE_CHAR = '\uFEFF'; + + this.walk = function(rng, callback) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, + ancestor, startPoint, + endPoint, node, parent, siblings, nodes; + + // Handle table cell selection the table plugin enables + // you to fake select table cells and perform formatting actions on them + nodes = dom.select('td.mceSelected,th.mceSelected'); + if (nodes.length > 0) { + tinymce.each(nodes, function(node) { + callback([node]); + }); + + return; + } + + function exclude(nodes) { + var node; + + // First node is excluded + node = nodes[0]; + if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { + nodes.splice(0, 1); + } + + // Last node is excluded + node = nodes[nodes.length - 1]; + if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { + nodes.splice(nodes.length - 1, 1); + } + + return nodes; + }; + + function collectSiblings(node, name, end_node) { + var siblings = []; + + for (; node && node != end_node; node = node[name]) + siblings.push(node); + + return siblings; + }; + + function findEndPoint(node, root) { + do { + if (node.parentNode == root) + return node; + + node = node.parentNode; + } while(node); + }; + + function walkBoundary(start_node, end_node, next) { + var siblingName = next ? 'nextSibling' : 'previousSibling'; + + for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { + parent = node.parentNode; + siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); + + if (siblings.length) { + if (!next) + siblings.reverse(); + + callback(exclude(siblings)); + } + } + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) + startContainer = startContainer.childNodes[startOffset]; + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; + + // Same container + if (startContainer == endContainer) + return callback(exclude([startContainer])); + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(startContainer, endContainer); + + // Process left side + for (node = startContainer; node; node = node.parentNode) { + if (node === endContainer) + return walkBoundary(startContainer, ancestor, true); + + if (node === ancestor) + break; + } + + // Process right side + for (node = endContainer; node; node = node.parentNode) { + if (node === startContainer) + return walkBoundary(endContainer, ancestor); + + if (node === ancestor) + break; + } + + // Find start/end point + startPoint = findEndPoint(startContainer, ancestor) || startContainer; + endPoint = findEndPoint(endContainer, ancestor) || endContainer; + + // Walk left leaf + walkBoundary(startContainer, startPoint, true); + + // Walk the middle from start to end point + siblings = collectSiblings( + startPoint == startContainer ? startPoint : startPoint.nextSibling, + 'nextSibling', + endPoint == endContainer ? endPoint.nextSibling : endPoint + ); + + if (siblings.length) + callback(exclude(siblings)); + + // Walk right leaf + walkBoundary(endContainer, endPoint); + }; + + this.split = function(rng) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + function splitText(node, offset) { + return node.splitText(offset); + }; + + // Handle single text node + if (startContainer == endContainer && startContainer.nodeType == 3) { + if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { + endContainer = splitText(startContainer, startOffset); + startContainer = endContainer.previousSibling; + + if (endOffset > startOffset) { + endOffset = endOffset - startOffset; + startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + startOffset = 0; + } else { + endOffset = 0; + } + } + } else { + // Split startContainer text node if needed + if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { + startContainer = splitText(startContainer, startOffset); + startOffset = 0; + } + + // Split endContainer text node if needed + if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { + endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + } + } + + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + }; + + }; + + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; +})(tinymce); + +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.KeyboardNavigation', { + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + t.destroy = function() { + each(items, function(item) { + dom.unbind(dom.get(item.id), 'focus', itemFocussed); + dom.unbind(dom.get(item.id), 'blur', itemBlurred); + }); + + dom.unbind(dom.get(root), 'focus', rootFocussed); + dom.unbind(dom.get(root), 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + if (excludeFromTabOrder) { + dom.bind(item.id, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + dom.setAttrib(item.id, 'tabindex', tabindex); + dom.bind(dom.get(item.id), 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + dom.bind(dom.get(root), 'focus', rootFocussed); + dom.bind(dom.get(root), 'keydown', rootKeydown); + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten class names + var DOM = tinymce.DOM, is = tinymce.is; + + tinymce.create('tinymce.ui.Control', { + Control : function(id, s, editor) { + this.id = id; + this.settings = s = s || {}; + this.rendered = false; + this.onRender = new tinymce.util.Dispatcher(this); + this.classPrefix = ''; + this.scope = s.scope || this; + this.disabled = 0; + this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); + }, + + setDisabled : function(s) { + if (s != this.disabled) { + this.setAriaProperty('disabled', s); + + this.setState('Disabled', s); + this.setState('Enabled', !s); + this.disabled = s; + } + }, + + isDisabled : function() { + return this.disabled; + }, + + setActive : function(s) { + if (s != this.active) { + this.setState('Active', s); + this.active = s; + this.setAriaProperty('pressed', s); + } + }, + + isActive : function() { + return this.active; + }, + + setState : function(c, s) { + var n = DOM.get(this.id); + + c = this.classPrefix + c; + + if (s) + DOM.addClass(n, c); + else + DOM.removeClass(n, c); + }, + + isRendered : function() { + return this.rendered; + }, + + renderHTML : function() { + }, + + renderTo : function(n) { + DOM.setHTML(n, this.renderHTML()); + }, + + postRender : function() { + var t = this, b; + + // Set pending states + if (is(t.disabled)) { + b = t.disabled; + t.disabled = -1; + t.setDisabled(b); + } + + if (is(t.active)) { + b = t.active; + t.active = -1; + t.setActive(b); + } + }, + + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + + destroy : function() { + tinymce.dom.Event.clear(this.id); + } + }); +})(tinymce); +tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { + Container : function(id, s, editor) { + this.parent(id, s, editor); + + this.controls = []; + + this.lookup = {}; + }, + + add : function(c) { + this.lookup[c.id] = c; + this.controls.push(c); + + return c; + }, + + get : function(n) { + return this.lookup[n]; + } +}); + + +tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + this.setDisabled(true); + }, + + renderHTML : function() { + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); + } +}); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { + MenuItem : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceMenuItem'; + }, + + setSelected : function(s) { + this.setState('Selected', s); + this.setAriaProperty('checked', !!s); + this.selected = s; + }, + + isSelected : function() { + return this.selected; + }, + + postRender : function() { + var t = this; + + t.parent(); + + // Set pending state + if (is(t.selected)) + t.setSelected(t.selected); + } + }); +})(tinymce); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { + Menu : function(id, s) { + var t = this; + + t.parent(id, s); + t.items = {}; + t.collapsed = false; + t.menuCount = 0; + t.onAddItem = new tinymce.util.Dispatcher(this); + }, + + expand : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.expand) + o.expand(); + }, 'items', t); + } + + t.collapsed = false; + }, + + collapse : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.collapse) + o.collapse(); + }, 'items', t); + } + + t.collapsed = true; + }, + + isCollapsed : function() { + return this.collapsed; + }, + + add : function(o) { + if (!o.settings) + o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); + + this.onAddItem.dispatch(this, o); + + return this.items[o.id] = o; + }, + + addSeparator : function() { + return this.add({separator : true}); + }, + + addMenu : function(o) { + if (!o.collapse) + o = this.createMenu(o); + + this.menuCount++; + + return this.add(o); + }, + + hasMenus : function() { + return this.menuCount !== 0; + }, + + remove : function(o) { + delete this.items[o.id]; + }, + + removeAll : function() { + var t = this; + + walk(t, function(o) { + if (o.removeAll) + o.removeAll(); + else + o.remove(); + + o.destroy(); + }, 'items', t); + + t.items = {}; + }, + + createMenu : function(o) { + var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); + + m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); + + return m; + } + }); +})(tinymce); +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; + + tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { + DropMenu : function(id, s) { + s = s || {}; + s.container = s.container || DOM.doc.body; + s.offset_x = s.offset_x || 0; + s.offset_y = s.offset_y || 0; + s.vp_offset_x = s.vp_offset_x || 0; + s.vp_offset_y = s.vp_offset_y || 0; + + if (is(s.icons) && !s.icons) + s['class'] += ' mceNoIcons'; + + this.parent(id, s); + this.onShowMenu = new tinymce.util.Dispatcher(this); + this.onHideMenu = new tinymce.util.Dispatcher(this); + this.classPrefix = 'mceMenu'; + }, + + createMenu : function(s) { + var t = this, cs = t.settings, m; + + s.container = s.container || cs.container; + s.parent = t; + s.constrain = s.constrain || cs.constrain; + s['class'] = s['class'] || cs['class']; + s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; + s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; + m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); + + m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); + + return m; + }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, + + update : function() { + var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; + + tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth; + th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight; + + if (!DOM.boxModel) + t.element.setStyles({width : tw + 2, height : th + 2}); + else + t.element.setStyles({width : tw, height : th}); + + if (s.max_width) + DOM.setStyle(co, 'width', tw); + + if (s.max_height) { + DOM.setStyle(co, 'height', th); + + if (tb.clientHeight < s.max_height) + DOM.setStyle(co, 'overflow', 'hidden'); + } + }, + + showMenu : function(x, y, px) { + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; + + t.collapse(1); + + if (t.isMenuVisible) + return; + + if (!t.rendered) { + co = DOM.add(t.settings.container, t.renderNode()); + + each(t.items, function(o) { + o.postRender(); + }); + + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + } else + co = DOM.get('menu_' + t.id); + + // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug + if (!tinymce.isOpera) + DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); + + DOM.show(co); + t.update(); + + x += s.offset_x || 0; + y += s.offset_y || 0; + vp.w -= 4; + vp.h -= 4; + + // Move inside viewport if not submenu + if (s.constrain) { + w = co.clientWidth - ot; + h = co.clientHeight - ot; + mx = vp.x + vp.w; + my = vp.y + vp.h; + + if ((x + s.vp_offset_x + w) > mx) + x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); + + if ((y + s.vp_offset_y + h) > my) + y = Math.max(0, (my - s.vp_offset_y) - h); + } + + DOM.setStyles(co, {left : x , top : y}); + t.element.update(); + + t.isMenuVisible = 1; + t.mouseClickFunc = Event.add(co, 'click', function(e) { + var m; + + e = e.target; + + if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { + m = t.items[e.id]; + + if (m.isDisabled()) + return; + + dm = t; + + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + + if (m.settings.onclick) + m.settings.onclick(e); + + return Event.cancel(e); // Cancel to fix onbeforeunload problem + } + }); + + if (t.hasMenus()) { + t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { + var m, r, mi; + + e = e.target; + if (e && (e = DOM.getParent(e, 'tr'))) { + m = t.items[e.id]; + + if (t.lastMenu) + t.lastMenu.collapse(1); + + if (m.isDisabled()) + return; + + if (e && DOM.hasClass(e, cp + 'ItemSub')) { + //p = DOM.getPos(s.container); + r = DOM.getRect(e); + m.showMenu((r.x + r.w - ot), r.y - ot, r.x); + t.lastMenu = m; + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); + } + } + }); + } + + Event.add(co, 'keydown', t._keyHandler, t); + + t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + t._setupKeyboardNav(); + } + }, + + hideMenu : function(c) { + var t = this, co = DOM.get('menu_' + t.id), e; + + if (!t.isMenuVisible) + return; + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + DOM.hide(co); + t.isMenuVisible = 0; + + if (!c) + t.collapse(1); + + if (t.element) + t.element.hide(); + + if (e = DOM.get(t.id)) + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); + + t.onHideMenu.dispatch(t); + }, + + add : function(o) { + var t = this, co; + + o = t.parent(o); + + if (t.isRendered && (co = DOM.get('menu_' + t.id))) + t._add(DOM.select('tbody', co)[0], o); + + return o; + }, + + collapse : function(d) { + this.parent(d); + this.hideMenu(1); + }, + + remove : function(o) { + DOM.remove(o.id); + this.destroy(); + + return this.parent(o); + }, + + destroy : function() { + var t = this, co = DOM.get('menu_' + t.id); + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + + if (t.element) + t.element.remove(); + + DOM.remove(co); + }, + + renderNode : function() { + var t = this, s = t.settings, n, tb, co, w; + + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + + if (s.menu_line) + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); + +// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); + tb = DOM.add(n, 'tbody'); + + each(t.items, function(o) { + t._add(tb, o); + }); + + t.rendered = true; + + return w; + }, + + // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.select('#menu_' + t.id)[0]; + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, + + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; + } + }, + + _add : function(tb, o) { + var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; + + if (s.separator) { + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); + + if (n = ro.previousSibling) + DOM.addClass(n, 'mceLast'); + + return; + } + + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } + + DOM.addClass(it, s['class']); +// n = DOM.add(n, 'span', {'class' : 'item'}); + + ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); + + if (s.icon_src) + DOM.add(ic, 'img', {src : s.icon_src}); + + n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); + + if (o.settings.style) + DOM.setAttrib(n, 'style', o.settings.style); + + if (tb.childNodes.length == 1) + DOM.addClass(ro, 'mceFirst'); + + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) + DOM.addClass(ro, 'mceFirst'); + + if (o.collapse) + DOM.addClass(ro, cp + 'ItemSub'); + + if (n = ro.previousSibling) + DOM.removeClass(n, 'mceLast'); + + DOM.addClass(ro, 'mceLast'); + } + }); +})(tinymce); +(function(tinymce) { + var DOM = tinymce.DOM; + + tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { + Button : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceButton'; + }, + + renderHTML : function() { + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + l; + else + h += '' + (l ? '' + l + '' : ''); + + h += ''; + h += ''; + return h; + }, + + postRender : function() { + var t = this, s = t.settings; + + tinymce.dom.Event.add(t.id, 'click', function(e) { + if (!t.isDisabled()) + return s.onclick.call(s.scope, e); + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { + ListBox : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.items = []; + + t.onChange = new Dispatcher(t); + + t.onPostRender = new Dispatcher(t); + + t.onAdd = new Dispatcher(t); + + t.onRenderMenu = new tinymce.util.Dispatcher(this); + + t.classPrefix = 'mceListBox'; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + var t = this, e, o, label; + + if (idx != t.selectedIndex) { + e = DOM.get(t.id + '_text'); + label = DOM.get(t.id + '_voiceDesc'); + o = t.items[idx]; + + if (o) { + t.selectedValue = o.value; + t.selectedIndex = idx; + DOM.setHTML(e, DOM.encode(o.title)); + DOM.setHTML(label, t.settings.title + " - " + o.title); + DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); + } else { + DOM.setHTML(e, DOM.encode(t.settings.title)); + DOM.setHTML(label, DOM.encode(t.settings.title)); + DOM.addClass(e, 'mceTitle'); + t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); + } + e = 0; + } + }, + + add : function(n, v, o) { + var t = this; + + o = o || {}; + o = tinymce.extend(o, { + title : n, + value : v + }); + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = ''; + h += ''; + h += ''; + h += ''; + + return h; + }, + + showMenu : function() { + var t = this, p2, e = DOM.get(this.id), m; + + if (t.isDisabled() || t.items.length == 0) + return; + + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus + + // Select in menu + if (t.oldID) + m.items[t.oldID].setSelected(0); + + each(t.items, function(o) { + if (o.value === t.selectedValue) { + m.items[o.id].setSelected(1); + t.oldID = o.id; + } + }); + + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); + + //DOM.get(t.id + '_text').focus(); + }, + + hideMenu : function(e) { + var t = this; + + if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + t.menu.hideMenu(); + } + } + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : t.classPrefix + 'Menu mceNoIcons', + max_width : 150, + max_height : 150 + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + m.add({ + title : t.settings.title, + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + + each(t.items, function(o) { + // No value then treat it as a title + if (o.value === undefined) { + m.add({ + title : o.title, + role : "option", + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + } else { + o.id = DOM.uniqueId(); + o.role= "option"; + o.onclick = function() { + if (t.settings.onselect(o.value) !== false) + t.select(o.value); // Must be runned after + }; + + m.add(o); + } + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + postRender : function() { + var t = this, cp = t.classPrefix; + + Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { + if (!t._focused) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { + // Fake select on enter + v = t.selectedValue; + t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); + t.settings.onselect(v); + } + }); + } + + t._focused = 1; + }); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); + }); + } + + t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); + Event.clear(this.id + '_open'); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { + NativeListBox : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceNativeListBox'; + }, + + setDisabled : function(s) { + DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); + }, + + isDisabled : function() { + return DOM.get(this.id).disabled; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + DOM.get(this.id).selectedIndex = idx + 1; + this.selectedValue = this.items[idx] ? this.items[idx].value : null; + }, + + add : function(n, v, a) { + var o, t = this; + + a = a || {}; + a.value = v; + + if (t.isRendered()) + DOM.add(DOM.get(this.id), 'option', a, n); + + o = { + title : n, + value : v, + attribs : a + }; + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h, t = this; + + h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); + + each(t.items, function(it) { + h += DOM.createHTML('option', {value : it.value}, it.title); + }); + + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); + return h; + }, + + postRender : function() { + var t = this, ch, changeListenerAdded = true; + + t.rendered = true; + + function onChange(e) { + var v = t.items[e.target.selectedIndex - 1]; + + if (v && (v = v.value)) { + t.onChange.dispatch(t, v); + + if (t.settings.onselect) + t.settings.onselect(v); + } + }; + + Event.add(t.id, 'change', onChange); + + // Accessibility keyhandler + Event.add(t.id, 'keydown', function(e) { + var bf; + + Event.remove(t.id, 'change', ch); + changeListenerAdded = false; + + bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; + Event.add(t.id, 'change', onChange); + Event.remove(t.id, 'blur', bf); + }); + + //prevent default left and right keys on chrome - so that the keyboard navigation is used. + if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { + return Event.prevent(e); + } + + if (e.keyCode == 13 || e.keyCode == 32) { + onChange(e); + return Event.cancel(e); + } + }); + + t.onPostRender.dispatch(t, DOM.get(t.id)); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); + + this.onRenderMenu = new tinymce.util.Dispatcher(this); + + s.menu_container = s.menu_container || DOM.doc.body; + }, + + showMenu : function() { + var t = this, p1, p2, e = DOM.get(t.id), m; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + p1 = DOM.getPos(t.settings.menu_container); + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.vp_offset_x = p2.x; + m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.setState('Selected', 1); + + t.isMenuVisible = 1; + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : this.classPrefix + 'Menu', + icons : t.settings.icons + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + hideMenu : function(e) { + var t = this; + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + t.setState('Selected', 0); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + if (t.menu) + t.menu.hideMenu(); + } + + t.isMenuVisible = 0; + }, + + postRender : function() { + var t = this, s = t.settings; + + Event.add(t.id, 'click', function() { + if (!t.isDisabled()) { + if (s.onclick) + s.onclick(t.value); + + t.showMenu(); + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceSplitButton'; + }, + + renderHTML : function() { + var h, t = this, s = t.settings, h1; + + h = ''; + + if (s.image) + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); + else + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); + + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h += ''; + h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); + }, + + postRender : function() { + var t = this, s = t.settings, activate; + + if (s.onclick) { + activate = function(evt) { + if (!t.isDisabled()) { + s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } + }); + } + + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.addClass(t.id, 'mceSplitButtonHover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.removeClass(t.id, 'mceSplitButtonHover'); + }); + } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); + Event.clear(this.id); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; + + tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { + ColorSplitButton : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.settings = s = tinymce.extend({ + colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', + grid_width : 8, + default_color : '#888888' + }, t.settings); + + t.onShowMenu = new tinymce.util.Dispatcher(t); + + t.onHideMenu = new tinymce.util.Dispatcher(t); + + t.value = s.default_color; + }, + + showMenu : function() { + var t = this, r, p, e, p2; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + e = DOM.get(t.id); + DOM.show(t.id + '_menu'); + DOM.addClass(e, 'mceSplitButtonSelected'); + p2 = DOM.getPos(e); + DOM.setStyles(t.id + '_menu', { + left : p2.x, + top : p2.y + e.clientHeight, + zIndex : 200000 + }); + e = 0; + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.onShowMenu.dispatch(t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.isMenuVisible = 1; + }, + + hideMenu : function(e) { + var t = this; + + if (t.isMenuVisible) { + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } + + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + } + }, + + renderMenu : function() { + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; + + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); + DOM.add(m, 'span', {'class' : 'mceMenuLine'}); + + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); + tb = DOM.add(n, 'tbody'); + + // Generate color grid + i = 0; + each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { + c = c.replace(/^#/, ''); + + if (!i--) { + tr = DOM.add(tb, 'tr'); + i = s.grid_width - 1; + } + + n = DOM.add(tr, 'td'); + n = DOM.add(n, 'a', { + role : 'option', + href : 'javascript:;', + style : { + backgroundColor : '#' + c + }, + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c + }); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } + }); + + if (s.more_colors_func) { + n = DOM.add(tb, 'tr'); + n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); + n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + + Event.add(n, 'click', function(e) { + s.more_colors_func.call(s.more_colors_scope || this); + return Event.cancel(e); // Cancel to fix onbeforeunload problem + }); + } + + DOM.addClass(m, 'mceColorSplitMenu'); + + new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); + + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = DOM.getParent(e.target, 'a', tb); + + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) + t.setColor(c); + + return Event.cancel(e); // Prevent IE auto save warning + }); + + return w; + }, + + setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + displayColor : function(c) { + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); + + t.value = c; + }, + + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_menu'); + Event.clear(this.id + '_more'); + DOM.remove(this.id + '_menu'); + } + }); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + var t = this; + dom.get(t.id).focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + //Move focus if webkit so that navigation back will read the item. + if (tinymce.isWebKit) { + dom.get(t.editor.id+"_ifr").focus(); + } + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; +tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) + c += ' mceToolbarEndButton'; + else if (co.SplitButton) + c += ' mceToolbarEndSplitButton'; + else if (co.ListBox) + c += ' mceToolbarEndListBox'; + + h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); + + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); + } +}); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; + + tinymce.create('tinymce.AddOnManager', { + AddOnManager : function() { + var self = this; + + self.items = []; + self.urls = {}; + self.lookup = {}; + self.onAdd = new Dispatcher(self); + }, + + get : function(n) { + if (this.lookup[n]) { + return this.lookup[n].instance; + } else { + return undefined; + } + }, + + dependencies : function(n) { + var result; + if (this.lookup[n]) { + result = this.lookup[n].dependencies; + } + return result || []; + }, + + requireLangPack : function(n) { + var s = tinymce.settings; + + if (s && s.language && s.language_load !== false) + tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); + }, + + add : function(id, o, dependencies) { + this.items.push(o); + this.lookup[id] = {instance:o, dependencies:dependencies}; + this.onAdd.dispatch(this, id, o); + + return o; + }, + createUrl: function(baseUrl, dep) { + if (typeof dep === "object") { + return dep + } else { + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; + } + }, + + addComponents: function(pluginName, scripts) { + var pluginUrl = this.urls[pluginName]; + tinymce.each(scripts, function(script){ + tinymce.ScriptLoader.add(pluginUrl+"/"+script); + }); + }, + + load : function(n, u, cb, s) { + var t = this, url = u; + + function loadDependencies() { + var dependencies = t.dependencies(n); + tinymce.each(dependencies, function(dep) { + var newUrl = t.createUrl(u, dep); + t.load(newUrl.resource, newUrl, undefined, undefined); + }); + if (cb) { + if (s) { + cb.call(s); + } else { + cb.call(tinymce.ScriptLoader); + } + } + } + + if (t.urls[n]) + return; + if (typeof u === "object") + url = u.prefix + u.resource + u.suffix; + + if (url.indexOf('/') != 0 && url.indexOf('://') == -1) + url = tinymce.baseURL + '/' + url; + + t.urls[n] = url.substring(0, url.lastIndexOf('/')); + + if (t.lookup[n]) { + loadDependencies(); + } else { + tinymce.ScriptLoader.add(url, loadDependencies, s); + } + } + }); + + // Create plugin and theme managers + tinymce.PluginManager = new tinymce.AddOnManager(); + tinymce.ThemeManager = new tinymce.AddOnManager(); +}(tinymce)); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, extend = tinymce.extend, + DOM = tinymce.DOM, Event = tinymce.dom.Event, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode, + Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + + tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + tinymce.onBeforeUnload = new Dispatcher(tinymce); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + tinymce.onBeforeUnload.dispatch(tinymce, e); + }); + + tinymce.onAddEditor = new Dispatcher(tinymce); + + tinymce.onRemoveEditor = new Dispatcher(tinymce); + + tinymce.EditorManager = extend(tinymce, { + editors : [], + + i18n : {}, + + activeEditor : null, + + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; + + function execCallback(se, n, s) { + var f = se[n]; + + if (!f) + return; + + if (tinymce.is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + } + + return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); + }; + + s = extend({ + theme : "simple", + language : "en" + }, s); + + t.settings = s; + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + switch (s.mode) { + case "exact": + l = s.elements || ''; + + if(l.length > 0) { + each(explode(l), function(v) { + if (DOM.get(v)) { + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } else { + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + instanceCounter++; + DOM.setAttrib(e, 'id', v); + + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } + }); + }); + } + }); + } + break; + + case "textareas": + case "specific_textareas": + function hasClass(n, c) { + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); + }; + + each(DOM.select('textarea'), function(v) { + if (s.editor_deselector && hasClass(v, s.editor_deselector)) + return; + + if (!s.editor_selector || hasClass(v, s.editor_selector)) { + // Can we use the name + e = DOM.get(v.name); + if (!v.id && !e) + v.id = v.name; + + // Generate unique name if missing or already exists + if (!v.id || t.get(v.id)) + v.id = DOM.uniqueId(); + + ed = new tinymce.Editor(v.id, s); + el.push(ed); + ed.render(1); + } + }); + break; + } + + // Call onInit when all editors are initialized + if (s.oninit) { + l = co = 0; + + each(el, function(ed) { + co++; + + if (!ed.initialized) { + // Wait for it + ed.onInit.add(function() { + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } else + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } + }); + }, + + get : function(id) { + if (id === undefined) + return this.editors; + + return this.editors[id]; + }, + + getInstanceById : function(id) { + return this.get(id); + }, + + add : function(editor) { + var self = this, editors = self.editors; + + // Add named and index editor instance + editors[editor.id] = editor; + editors.push(editor); + + self._setActive(editor); + self.onAddEditor.dispatch(self, editor); + + + // Patch the tinymce.Editor instance with jQuery adapter logic + if (tinymce.adapter) + tinymce.adapter.patchEditor(editor); + + + return editor; + }, + + remove : function(editor) { + var t = this, i, editors = t.editors; + + // Not in the collection + if (!editors[editor.id]) + return null; + + delete editors[editor.id]; + + for (i = 0; i < editors.length; i++) { + if (editors[i] == editor) { + editors.splice(i, 1); + break; + } + } + + // Select another editor since the active one was removed + if (t.activeEditor == editor) + t._setActive(editors[0]); + + editor.destroy(); + t.onRemoveEditor.dispatch(t, editor); + + return editor; + }, + + execCommand : function(c, u, v) { + var t = this, ed = t.get(v), w; + + // Manager commands + switch (c) { + case "mceFocus": + ed.focus(); + return true; + + case "mceAddEditor": + case "mceAddControl": + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + + return true; + + case "mceAddFrameControl": + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE) { + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + w.attachEvent('onunload', clr); + } + + v.page_window = null; + + return true; + + case "mceRemoveEditor": + case "mceRemoveControl": + if (ed) + ed.remove(); + + return true; + + case 'mceToggleEditor': + if (!ed) { + t.execCommand('mceAddControl', 0, v); + return true; + } + + if (ed.isHidden()) + ed.show(); + else + ed.hide(); + + return true; + } + + // Run command on active editor + if (t.activeEditor) + return t.activeEditor.execCommand(c, u, v); + + return false; + }, + + execInstanceCommand : function(id, c, u, v) { + var ed = this.get(id); + + if (ed) + return ed.execCommand(c, u, v); + + return false; + }, + + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + addI18n : function(p, o) { + var lo, i18n = this.i18n; + + if (!tinymce.is(p, 'string')) { + each(p, function(o, lc) { + each(o, function(o, g) { + each(o, function(o, k) { + if (g === 'common') + i18n[lc + '.' + k] = o; + else + i18n[lc + '.' + g + '.' + k] = o; + }); + }); + }); + } else { + each(o, function(o, k) { + i18n[p + '.' + k] = o; + }); + } + }, + + // Private methods + + _setActive : function(editor) { + this.selectedInstance = this.activeEditor = editor; + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten these names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, + Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko, + isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; + + tinymce.create('tinymce.Editor', { + Editor : function(id, s) { + var t = this; + + t.id = t.editorId = id; + + t.execCommands = {}; + t.queryStateCommands = {}; + t.queryValueCommands = {}; + + t.isNotDirty = false; + + t.plugins = {}; + + // Add events to the editor + each([ + 'onPreInit', + + 'onBeforeRenderUI', + + 'onPostRender', + + 'onInit', + + 'onRemove', + + 'onActivate', + + 'onDeactivate', + + 'onClick', + + 'onEvent', + + 'onMouseUp', + + 'onMouseDown', + + 'onDblClick', + + 'onKeyDown', + + 'onKeyUp', + + 'onKeyPress', + + 'onContextMenu', + + 'onSubmit', + + 'onReset', + + 'onPaste', + + 'onPreProcess', + + 'onPostProcess', + + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent', + + 'onLoadContent', + + 'onSaveContent', + + 'onNodeChange', + + 'onChange', + + 'onBeforeExecCommand', + + 'onExecCommand', + + 'onUndo', + + 'onRedo', + + 'onVisualAid', + + 'onSetProgressState' + ], function(e) { + t[e] = new Dispatcher(t); + }); + + t.settings = s = extend({ + id : id, + language : 'en', + docs_language : 'en', + theme : 'simple', + skin : 'default', + delta_width : 0, + delta_height : 0, + popup_css : '', + plugins : '', + document_base_url : tinymce.documentBaseURL, + add_form_submit_trigger : 1, + submit_patch : 1, + add_unload_trigger : 1, + convert_urls : 1, + relative_urls : 1, + remove_script_host : 1, + table_inline_editing : 0, + object_resizing : 1, + cleanup : 1, + accessibility_focus : 1, + custom_shortcuts : 1, + custom_undo_redo_keyboard_shortcuts : 1, + custom_undo_redo_restore_selection : 1, + custom_undo_redo : 1, + doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll + visual_table_class : 'mceItemTable', + visual : 1, + font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', + font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size + apply_source_formatting : 1, + directionality : 'ltr', + forced_root_block : 'p', + hidden_input : 1, + padd_empty_editor : 1, + render_ui : 1, + init_theme : 1, + force_p_newlines : 1, + indentation : '30px', + keep_styles : 1, + fix_table_elements : 1, + inline_styles : 1, + convert_fonts_to_spans : true, + indent : 'simple', + indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + validate : true, + entity_encoding : 'named', + url_converter : t.convertURL, + url_converter_scope : t, + ie7_compat : true + }, s); + + t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, { + base_uri : tinyMCE.baseURI + }); + + t.baseURI = tinymce.baseURI; + + t.contentCSS = []; + + // Call setup + t.execCallback('setup', t); + }, + + render : function(nst) { + var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; + + // Page is not loaded yet, wait for it + if (!Event.domLoaded) { + Event.add(document, 'init', function() { + t.render(); + }); + return; + } + + tinyMCE.settings = s; + + // Element not found, then skip initialization + if (!t.getElement()) + return; + + // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff + // here since the browser says it has contentEditable support but there is no visible + // caret We will remove this check ones Apple implements full contentEditable support + if (tinymce.isIDevice && !tinymce.isIOS5) + return; + + // Add hidden input for non input elements inside form elements + if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) + DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); + + if (tinymce.WindowManager) + t.windowManager = new tinymce.WindowManager(t); + + if (s.encoding == 'xml') { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = DOM.encode(o.content); + }); + } + + if (s.add_form_submit_trigger) { + t.onSubmit.addToTop(function() { + if (t.initialized) { + t.save(); + t.isNotDirty = 1; + } + }); + } + + if (s.add_unload_trigger) { + t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { + if (t.initialized && !t.destroyed && !t.isHidden()) + t.save({format : 'raw', no_events : true}); + }); + } + + tinymce.addUnload(t.destroy, t); + + if (s.submit_patch) { + t.onBeforeRenderUI.add(function() { + var n = t.getElement().form; + + if (!n) + return; + + // Already patched + if (n._mceOldSubmit) + return; + + // Check page uses id="submit" or name="submit" for it's submit button + if (!n.submit.nodeType && !n.submit.length) { + t.formElement = n; + n._mceOldSubmit = n.submit; + n.submit = function() { + // Save all instances + tinymce.triggerSave(); + t.isNotDirty = 1; + + return t.formElement._mceOldSubmit(t.formElement); + }; + } + + n = null; + }); + } + + // Load scripts + function loadScripts() { + if (s.language && s.language_load !== false) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + each(explode(s.plugins), function(p) { + if (p &&!PluginManager.urls[p]) { + if (p.charAt(0) == '-') { + p = p.substr(1, p.length); + var dependencies = PluginManager.dependencies(p); + each(dependencies, function(dep) { + var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; + var dep = PluginManager.createUrl(defaultSettings, dep); + PluginManager.load(dep.resource, dep); + + }); + } else { + // Skip safari plugin, since it is removed as of 3.3b1 + if (p == 'safari') { + return; + } + PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); + } + } + }); + + // Init when que is loaded + sl.loadQueue(function() { + if (!t.removed) + t.init(); + }); + }; + + loadScripts(); + }, + + init : function() { + var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; + + tinymce.add(t); + + s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); + + if (s.theme) { + s.theme = s.theme.replace(/-/, ''); + o = ThemeManager.get(s.theme); + t.theme = new o(); + + if (t.theme.init && s.init_theme) + t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); + } + function initPlugin(p) { + var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; + if (c && tinymce.inArray(initializedPlugins,p) === -1) { + each(PluginManager.dependencies(p), function(dep){ + initPlugin(dep); + }); + po = new c(t, u); + + t.plugins[p] = po; + + if (po.init) { + po.init(t, u); + initializedPlugins.push(p); + } + } + } + + // Create all plugins + each(explode(s.plugins.replace(/\-/g, '')), initPlugin); + + // Setup popup CSS path(s) + if (s.popup_css !== false) { + if (s.popup_css) + s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); + else + s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); + } + + if (s.popup_css_add) + s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); + + t.controlManager = new tinymce.ControlManager(t); + + if (s.custom_undo_redo) { + t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.beforeChange(); + }); + + t.onExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.add(); + }); + } + + t.onExecCommand.add(function(ed, c) { + // Don't refresh the select lists until caret move + if (!/^(FontName|FontSize)$/.test(c)) + t.nodeChanged(); + }); + + // Remove ghost selections on images and tables in Gecko + if (isGecko) { + function repaint(a, o) { + if (!o || !o.initial) + t.execCommand('mceRepaint'); + }; + + t.onUndo.add(repaint); + t.onRedo.add(repaint); + t.onSetContent.add(repaint); + } + + // Enables users to override the control factory + t.onBeforeRenderUI.dispatch(t, t.controlManager); + + // Measure box + if (s.render_ui) { + w = s.width || e.style.width || e.offsetWidth; + h = s.height || e.style.height || e.offsetHeight; + t.orgDisplay = e.style.display; + re = /^[0-9\.]+(|px)$/i; + + if (re.test('' + w)) + w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100); + + if (re.test('' + h)) + h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100); + + // Render UI + o = t.theme.renderUI({ + targetNode : e, + width : w, + height : h, + deltaWidth : s.delta_width, + deltaHeight : s.delta_height + }); + + t.editorContainer = o.editorContainer; + } + + + // User specified a document.domain value + if (document.domain && location.hostname != document.domain) + tinymce.relaxedDomain = document.domain; + + // Resize editor + DOM.setStyles(o.sizeContainer || o.editorContainer, { + width : w, + height : h + }); + + // Load specified content CSS last + if (s.content_css) { + tinymce.each(explode(s.content_css), function(u) { + t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); + }); + } + + h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); + if (h < 100) + h = 100; + + t.iframeHTML = s.doctype + ''; + + // We only need to override paths if we have to + // IE has a bug where it remove site absolute urls to relative ones if this is specified + if (s.document_base_url != tinymce.documentBaseURL) + t.iframeHTML += ''; + + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + + t.iframeHTML += ''; + + // Load the CSS by injecting them into the HTML this will reduce "flicker" + for (i = 0; i < t.contentCSS.length; i++) { + t.iframeHTML += ''; + } + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += '
    '; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; + } + + // Create iframe + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + allowTransparency : "true", + title : s.aria_label, + style : { + width : '100%', + height : h, + display : 'block' // Important for Gecko to render the iframe correctly + } + }); + + t.contentAreaContainer = o.iframeContainer; + DOM.get(o.editorContainer).style.display = t.orgDisplay; + DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); + + if (!tinymce.relaxedDomain || !u) + t.setupIframe(); + + e = n = o = null; // Cleanup + }, + + setupIframe : function() { + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; + + // Setup iframe body + if (!isIE || !tinymce.relaxedDomain) { + d.open(); + d.write(t.iframeHTML); + d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; + } + + // It will not steal focus while setting contentEditable + b = t.getBody(); + b.disabled = true; + + if (!s.readonly) + b.contentEditable = true; + + b.disabled = false; + + t.schema = new tinymce.html.Schema(s); + + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + update_styles : 1, + fix_ie_paragraphs : 1, + schema : t.schema + }); + + t.parser = new tinymce.html.DomParser(s, t.schema); + + // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. + if (!t.settings.allow_html_in_named_anchor) { + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + } + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + if (name === "style") + node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr(internalName, t.convertURL(value, name, node.name)); + } + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); + } + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); + + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + + t.formatter = new tinymce.Formatter(this); + + // Register default formats + t.formatter.register({ + alignleft : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} + ], + + bold : [ + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b', remove : 'all'} + ], + + italic : [ + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i', remove : 'all'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u', remove : 'all'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'strike', remove : 'all'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, + + link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, + onmatch : function(node) { + return true; + }, + + onformat : function(elm, fmt, vars) { + each(vars, function(value, key) { + t.dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + t.formatter.register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + t.formatter.register(t.settings.formats); + + t.undoManager = new tinymce.UndoManager(t); + + // Pass through + t.undoManager.onAdd.add(function(um, l) { + if (um.hasUndo()) + return t.onChange.dispatch(t, l, um); + }); + + t.undoManager.onUndo.add(function(um, l) { + return t.onUndo.dispatch(t, l, um); + }); + + t.undoManager.onRedo.add(function(um, l) { + return t.onRedo.dispatch(t, l, um); + }); + + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + + if (!s.gecko_spellcheck) + t.getBody().spellcheck = 0; + + if (!s.readonly) + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + t.quirks = new tinymce.util.Quirks(this); + + if (s.directionality) + t.getBody().dir = s.directionality; + + if (s.nowrap) + t.getBody().style.whiteSpace = "nowrap"; + + if (s.handle_node_change_callback) { + t.onNodeChange.add(function(ed, cm, n) { + t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); + }); + } + + if (s.save_callback) { + t.onSaveContent.add(function(ed, o) { + var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); + + if (h) + o.content = h; + }); + } + + if (s.onchange_callback) { + t.onChange.add(function(ed, l) { + t.execCallback('onchange_callback', t, l); + }); + } + + if (s.protect) { + t.onBeforeSetContent.add(function(ed, o) { + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } + }); + } + + if (s.convert_newlines_to_brs) { + t.onBeforeSetContent.add(function(ed, o) { + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
    '); + }); + } + + if (s.preformatted) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^\s*/, ''); + o.content = o.content.replace(/<\/pre>\s*$/, ''); + + if (o.set) + o.content = '
    ' + o.content + '
    '; + }); + } + + if (s.verify_css_classes) { + t.serializer.attribValueFilter = function(n, v) { + var s, cl; + + if (n == 'class') { + // Build regexp for classes + if (!t.classesRE) { + cl = t.dom.getClasses(); + + if (cl.length > 0) { + s = ''; + + each (cl, function(o) { + s += (s ? '|' : '') + o['class']; + }); + + t.classesRE = new RegExp('(' + s + ')', 'gi'); + } + } + + return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; + } + + return v; + }; + } + + if (s.cleanup_callback) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + t.onPreProcess.add(function(ed, o) { + if (o.set) + t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + t.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + if (s.save_callback) { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); + }); + } + + if (s.handle_event_callback) { + t.onEvent.add(function(ed, e, o) { + if (t.execCallback('handle_event_callback', e, ed, o) === false) + Event.cancel(e); + }); + } + + // Add visual aids when new contents is added + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + // Remove empty contents + if (s.padd_empty_editor) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); + }); + } + + if (isGecko) { + // Fix gecko link bug, when a link is placed at the end of block elements there is + // no way to move the caret behind the link. This fix adds a bogus br element after the link + function fixLinks(ed, o) { + each(ed.dom.select('a'), function(n) { + var pn = n.parentNode; + + if (ed.dom.isBlock(pn) && pn.lastChild === n) + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); + }); + }; + + t.onExecCommand.add(function(ed, cmd) { + if (cmd === 'CreateLink') + fixLinks(ed); + }); + + t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); + } + + t.load({initial : true, format : 'html'}); + t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); + t.initialized = true; + + t.onInit.dispatch(t); + t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); + t.execCallback('init_instance_callback', t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); + + // Handle auto focus + if (s.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(s.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getBody().focus(); + ed.getWin().focus(); + }, 100); + } + + e = null; + }, + + + focus : function(sf) { + var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); + + if (!sf) { + // Get selected control element + ieRng = selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + t._refreshContentEditable(); + selection.normalize(); + + // Is not content editable + if (!ce) + t.getWin().focus(); + + // Focus the body as well since it's contentEditable + if (tinymce.isGecko) { + t.getBody().focus(); + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + + } + + if (tinymce.activeEditor != t) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + tinymce._setActive(t); + }, + + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getStart() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + o = o || {}; + n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + t.dom.getParent(n, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + n, + s.isCollapsed(), + o + ); + } + }, + + addButton : function(n, s) { + var t = this; + + t.buttons = t.buttons || {}; + t.buttons[n] = s; + }, + + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; + }, + + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (!t.settings.custom_shortcuts) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : desc, + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + o = {}; + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); + if (o.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + show : function() { + var t = this; + + DOM.show(t.getContainer()); + DOM.hide(t.id); + t.load(); + }, + + hide : function() { + var t = this, d = t.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && d) + d.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + t.save(); + DOM.hide(t.getContainer()); + DOM.setStyle(t.id, 'display', t.orgDisplay); + }, + + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + // Add undo level will trigger onchange event + if (!o.no_events) { + t.undoManager.typing = false; + t.undoManager.add(); + } + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(), forcedRootBlockName; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + forcedRootBlockName = self.settings.forced_root_block; + if (forcedRootBlockName) + content = '<' + forcedRootBlockName + '>
    '; + else + content = '
    '; + + body.innerHTML = content; + self.selection.select(body, true); + self.selection.collapse(true); + return; + } + + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); + } + + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + self.selection.normalize(); + + return args.content; + }, + + getContent : function(args) { + var self = this, content; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; + + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); + + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); + + args.content = tinymce.trim(content); + + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); + + return args.content; + }, + + isDirty : function() { + var self = this; + + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; + }, + + getContainer : function() { + var t = this; + + if (!t.container) + t.container = DOM.get(t.editorContainer || t.id + '_parent'); + + return t.container; + }, + + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + getWin : function() { + var t = this, e; + + if (!t.contentWindow) { + e = DOM.get(t.id + "_ifr"); + + if (e) + t.contentWindow = e.contentWindow; + } + + return t.contentWindow; + }, + + getDoc : function() { + var t = this, w; + + if (!t.contentDocument) { + w = t.getWin(); + + if (w) + t.contentDocument = w.document; + } + + return t.contentDocument; + }, + + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + convertURL : function(u, n, e) { + var t = this, s = t.settings; + + // Use callback instead + if (s.urlconverter_callback) + return t.execCallback('urlconverter_callback', u, e, true, n); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) + return u; + + // Convert to relative + if (s.relative_urls) + return t.documentBaseURI.toRelative(u); + + // Convert to absolute + u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); + + return u; + }, + + addVisual : function(e) { + var t = this, s = t.settings; + + e = e || t.getBody(); + + if (!is(t.hasVisual)) + t.hasVisual = s.visual; + + each(t.dom.select('table,a', e), function(e) { + var v; + + switch (e.nodeName) { + case 'TABLE': + v = t.dom.getAttrib(e, 'border'); + + if (!v || v == '0') { + if (t.hasVisual) + t.dom.addClass(e, s.visual_table_class); + else + t.dom.removeClass(e, s.visual_table_class); + } + + return; + + case 'A': + v = t.dom.getAttrib(e, 'name'); + + if (v) { + if (t.hasVisual) + t.dom.addClass(e, 'mceItemAnchor'); + else + t.dom.removeClass(e, 'mceItemAnchor'); + } + + return; + } + }); + + t.onVisualAid.dispatch(t, e, t.hasVisual); + }, + + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + tinymce.remove(t); + DOM.remove(e); + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _addEvents : function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var t = this, i, s = t.settings, dom = t.dom, lo = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + function eventHandler(e, o) { + var ty = e.type; + + // Don't fire events when it's removed + if (t.removed) + return; + + // Generic event handler + if (t.onEvent.dispatch(t, e, o) !== false) { + // Specific event handler + t[lo[e.fakeType || e.type]].dispatch(t, e, o); + } + }; + + // Add DOM events + each(lo, function(v, k) { + switch (k) { + case 'contextmenu': + dom.bind(t.getDoc(), k, eventHandler); + break; + + case 'paste': + dom.bind(t.getBody(), k, function(e) { + eventHandler(e); + }); + break; + + case 'submit': + case 'reset': + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + break; + + default: + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + } + }); + + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + t.focus(true); + }); + + + // Fixes bug where a specified document_base_uri could result in broken images + // This will also fix drag drop of images in Gecko + if (tinymce.isGecko) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + var v; + + e = e.target; + + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) + e.src = t.documentBaseURI.toAbsolute(v); + }); + } + + // Set various midas options in Gecko + if (isGecko) { + function setOpts() { + var t = this, d = t.getDoc(), s = t.settings; + + if (isGecko && !s.readonly) { + t._refreshContentEditable(); + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, false); + } catch (ex) { + // Use old method + if (!t._isHidden()) + try {d.execCommand("useCSS", 0, true);} catch (ex) {} + } + + if (!s.table_inline_editing) + try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} + + if (!s.object_resizing) + try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} + } + }; + + t.onBeforeExecCommand.add(setOpts); + t.onMouseDown.add(setOpts); + } + + // Add node change handlers + t.onMouseUp.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); + t.onKeyUp.add(function(ed, e) { + var c = e.keyCode; + + if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) + t.nodeChanged(); + }); + + + // Add block quote deletion handler + t.onKeyDown.add(function(ed, e) { + // Was the BACKSPACE key pressed? + if (e.keyCode != 8) + return; + + var n = ed.selection.getRng().startContainer; + var offset = ed.selection.getRng().startOffset; + + while (n && n.nodeType && n.nodeType != 1 && n.parentNode) + n = n.parentNode; + + // Is the cursor at the beginning of a blockquote? + if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) { + // Remove the blockquote + ed.formatter.toggle('blockquote', null, n.parentNode); + + // Move the caret to the beginning of n + var rng = ed.selection.getRng(); + rng.setStart(n, 0); + rng.setEnd(n, 0); + ed.selection.setRng(rng); + ed.selection.collapse(false); + } + }); + + + + // Add reset handler + t.onReset.add(function() { + t.setContent(t.startContent, {format : 'raw'}); + }); + + // Add shortcuts + if (s.custom_shortcuts) { + if (s.custom_undo_redo_keyboard_shortcuts) { + t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); + t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); + } + + // Add default shortcuts for gecko + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); + + // BlockFormat shortcuts keys + for (i=1; i<=6; i++) + t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + + t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); + t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); + + function find(e) { + var v = null; + + if (!e.altKey && !e.ctrlKey && !e.metaKey) + return v; + + each(t.shortcuts, function(o) { + if (tinymce.isMac && o.ctrl != e.metaKey) + return; + else if (!tinymce.isMac && o.ctrl != e.ctrlKey) + return; + + if (o.alt != e.altKey) + return; + + if (o.shift != e.shiftKey) + return; + + if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { + v = o; + return false; + } + }); + + return v; + }; + + t.onKeyUp.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyPress.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyDown.add(function(ed, e) { + var o = find(e); + + if (o) { + o.func.call(o.scope); + return Event.cancel(e); + } + }); + } + + if (tinymce.isIE) { + // Fix so resize will only update the width and height attributes not the styles of an image + // It will also block mceItemNoResize items + dom.bind(t.getDoc(), 'controlselect', function(e) { + var re = t.resizeInfo, cb; + + e = e.target; + + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + + if (re) + dom.unbind(re.node, re.ev, re.cb); + + if (!dom.hasClass(e, 'mceItemNoResize')) { + ev = 'resizeend'; + cb = dom.bind(e, ev, function(e) { + var v; + + e = e.target; + + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); + } + + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); + } + }); + } else { + ev = 'resizestart'; + cb = dom.bind(e, 'resizestart', Event.cancel, Event); + } + + re = t.resizeInfo = { + node : e, + ev : ev, + cb : cb + }; + }); + } + + if (tinymce.isOpera) { + t.onClick.add(function(ed, e) { + Event.prevent(e); + }); + } + + // Add custom undo/redo handlers + if (s.custom_undo_redo) { + function addUndo() { + t.undoManager.typing = false; + t.undoManager.add(); + }; + + dom.bind(t.getDoc(), 'focusout', function(e) { + if (!t.removed && t.undoManager.typing) + addUndo(); + }); + + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + + t.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey) + addUndo(); + }); + + t.onKeyDown.add(function(ed, e) { + var keyCode = e.keyCode, sel; + + if (keyCode == 8) { + sel = t.getDoc().selection; + + // Fix IE control + backspace browser bug + if (sel && sel.createRange && sel.createRange().item) { + t.undoManager.beforeChange(); + ed.dom.remove(sel.createRange().item(0)); + addUndo(); + + return Event.cancel(e); + } + } + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + + if (t.undoManager.typing) + addUndo(); + + return; + } + + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + }); + + t.onMouseDown.add(function() { + if (t.undoManager.typing) + addUndo(); + }); + } + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + + if (target !== t.getBody()) { + t.dom.setAttrib(target, "style", null); + + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + var s = t.selection; + + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } + + t.onKeyPress.add(function(ed, e) { + var applyAttributes; + + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); + + return Event.cancel(e); + } + }); + + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); + + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); + } + }); + } + }, + + _refreshContentEditable : function() { + var self = this, body, parent; + + // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again + if (self._isHidden()) { + body = self.getBody(); + parent = body.parentNode; + + parent.removeChild(body); + parent.appendChild(body); + + body.focus(); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); + } + }); +})(tinymce); + +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undefined, TRUE = true, FALSE = false; + + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + formatter = editor.formatter, + bookmark; + + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) + ui = FALSE; + + if (value === undefined) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return formatter.match(name); + }; + + function toggleFormat(name, value) { + formatter.toggle(name, value ? {value : value} : undefined); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + execCommand('mceRepaint'); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + var parser, serializer, parentNode, rootNode, fragment, args, + marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; + + // Setup parser and serializer + parser = editor.parser; + serializer = new tinymce.html.Serializer({}, editor.schema); + bookmarkHtml = '\uFEFF'; + + // Run beforeSetContent handlers on the HTML to be inserted + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Replace the caret marker with a span bookmark element + value = value.replace(/\{\$caret\}/, bookmarkHtml); + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) + editor.getDoc().execCommand('Delete', false, null); + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + args = {context : parentNode.nodeName.toLowerCase()}; + fragment = parser.parse(value, args); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + node.parent.insert(marker, node, node.name === 'br'); + break; + } + } + } + + // If parser says valid we can insert the contents into that parent + if (!args.invalid) { + value = serializer.serialize(fragment); + + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + node = parentNode.firstChild; + node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) + dom.setHTML(parentNode, value); + else + selection.setContent(value); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = editor.selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) + parentNode = node = rootNode; + else + node = parentNode; + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function() { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) + dom.setHTML(rootNode, value); + else + dom.setOuterHTML(parentNode, value); + } + + marker = dom.get('mce_marker'); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + nodeRect = dom.getRect(marker); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); + viewportBodyElement.scrollLeft = nodeRect.x; + viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; + } + + // Move selection before marker and remove it + rng = dom.createRng(); + + // If previous sibling is a text node set the selection to the end of that node + node = marker.previousSibling; + if (node && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + } else { + // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node + rng.setStartBefore(marker); + rng.setEndBefore(marker); + } + + // Remove the marker node and set the new range + dom.remove(marker); + selection.setRng(rng); + + // Dispatch after event and add any visual elements needed + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + editor.execCommand('mceInsertContent', false, '
    '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var anchor; + + if (typeof(value) == 'string') + value = {href : value}; + + anchor = dom.getParent(selection.getNode(), 'a'); + + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + + // Remove existing links if there could be child links or that the href isn't specified + if (!anchor || !value.href) { + formatter.remove('link'); + } + + // Apply new link to selection + if (value.href) { + formatter.apply('link', value, anchor); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + editor.selection.setRng(rng); + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + return isFormatMatch('align' + command.substring(7)); + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + if (settings.custom_undo_redo) { + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + } + }; +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + tinymce.UndoManager = function(editor) { + var self, index = 0, data = [], beforeBookmark; + + function getContent() { + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); + }; + + return self = { + typing : false, + + onAdd : new Dispatcher(self), + + onUndo : new Dispatcher(self), + + onRedo : new Dispatcher(self), + + beforeChange : function() { + beforeBookmark = editor.selection.getBookmark(2, true); + }, + + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) + return null; + + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = beforeBookmark; + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) + data.length = index + 1; + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = false; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.beforeBookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + clear : function() { + data = []; + index = 0; + self.typing = false; + }, + + hasUndo : function() { + return index > 0 || this.typing; + }, + + hasRedo : function() { + return index < data.length - 1 && !this.typing; + } + }; + }; +})(tinymce); + +(function(tinymce) { + // Shorten names + var Event = tinymce.dom.Event, + isIE = tinymce.isIE, + isGecko = tinymce.isGecko, + isOpera = tinymce.isOpera, + each = tinymce.each, + extend = tinymce.extend, + TRUE = true, + FALSE = false; + + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + + // Checks if the selection/caret is at the end of the specified block element + function isAtEnd(rng, par) { + var rng2 = par.ownerDocument.createRange(); + + rng2.setStart(rng.endContainer, rng.endOffset); + rng2.setEndAfter(par); + + // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element + return rng2.cloneContents().textContent.length == 0; + }; + + function splitList(selection, dom, li) { + var listBlock, block; + + if (dom.isEmpty(li)) { + listBlock = dom.getParent(li, 'ul,ol'); + + if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { + dom.split(listBlock, li); + block = dom.create('p', 0, '
    '); + dom.replace(block, li); + selection.select(block, 1); + } + + return FALSE; + } + + return TRUE; + }; + + tinymce.create('tinymce.ForceBlocks', { + ForceBlocks : function(ed) { + var t = this, s = ed.settings, elm; + + t.editor = ed; + t.dom = ed.dom; + elm = (s.forced_root_block || 'p').toLowerCase(); + s.element = elm.toUpperCase(); + + ed.onPreInit.add(t.setup, t); + }, + + setup : function() { + var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements(); + + // Force root blocks + if (s.forced_root_block) { + function addRootBlocks() { + var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF; + + if (!node || node.nodeType !== 1) + return; + + // Check if node is wrapped in block + while (node != rootNode) { + if (blockElements[node.nodeName]) + return; + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + } else { + // Force control range into text range + if (rng.item) { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rng.item(0)); + } + + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + for (node = rootNode.firstChild; node; node) { + if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { + if (!rootBlockNode) { + rootBlockNode = dom.create(s.forced_root_block); + node.parentNode.insertBefore(rootBlockNode, node); + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + try { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) + rng.moveEnd('character', endOffset); + + rng.select(); + } catch (ex) { + // Ignore + } + } + + ed.nodeChanged(); + }; + + ed.onKeyUp.add(addRootBlocks); + ed.onClick.add(addRootBlocks); + } + + if (s.force_br_newlines) { + // Force IE to produce BRs on enter + if (isIE) { + ed.onKeyPress.add(function(ed, e) { + var n; + + if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { + selection.setContent('
    ', {format : 'raw'}); + n = dom.get('__'); + n.removeAttribute('id'); + selection.select(n); + selection.collapse(); + return Event.cancel(e); + } + }); + } + } + + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + selection.collapse(true); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } + + if (isGecko) { + ed.onKeyDown.add(function(ed, e) { + if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) + t.backspaceDelete(e, e.keyCode == 8); + }); + } + } + + // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 + if (tinymce.isWebKit) { + function insertBr(ed) { + var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; + + // Insert BR element + rng.insertNode(br = dom.create('br')); + + // Place caret after BR + rng.setStartAfter(br); + rng.setEndAfter(br); + selection.setRng(rng); + + // Could not place caret after BR then insert an nbsp entity and move the caret + if (selection.getSel().focusNode == br.previousSibling) { + selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); + selection.collapse(TRUE); + } + + // Create a temporary DIV after the BR and get the position as it + // seems like getPos() returns 0 for text nodes and BR elements. + dom.insertAfter(div, br); + divYPos = dom.getPos(div).y; + dom.remove(div); + + // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 + if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. + ed.getWin().scrollTo(0, divYPos); + }; + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { + insertBr(ed); + Event.cancel(e); + } + }); + } + + // IE specific fixes + if (isIE) { + // Replaces IE:s auto generated paragraphs with the specified element name + if (s.element != 'P') { + ed.onKeyPress.add(function(ed, e) { + t.lastElm = selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, n = selection.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = dom.rename(n, s.element); + selection.select(n); + selection.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = dom.getParent(n, 'p'); + + if (bl) { + dom.rename(bl, s.element); + ed.nodeChanged(); + } + } + }); + } + } + }, + + getParentBlock : function(n) { + var d = this.dom; + + return d.getParent(n, d.isBlock); + }, + + insertPara : function(e) { + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + + ed.undoManager.beforeChange(); + + // If root blocks are forced then use Operas default behavior since it's really good +// Removed due to bug: #1853816 +// if (se.forced_root_block && isOpera) +// return TRUE; + + // Setup before range + rb = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + rb.setStart(s.anchorNode, s.anchorOffset); + rb.collapse(TRUE); + + // Setup after range + ra = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + ra.setStart(s.focusNode, s.focusOffset); + ra.collapse(TRUE); + + // Setup start/end points + dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; + sn = dir ? s.anchorNode : s.focusNode; + so = dir ? s.anchorOffset : s.focusOffset; + en = dir ? s.focusNode : s.anchorNode; + eo = dir ? s.focusOffset : s.anchorOffset; + + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + if (sn.firstChild.nodeName == 'BR') + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + if (sn.childNodes.length == 0) { + ed.dom.add(sn, se.element, null, '
    '); + aft = ed.dom.add(sn, se.element, null, '
    '); + } else { + n = sn.innerHTML; + sn.innerHTML = ''; + ed.dom.add(sn, se.element, null, n); + aft = ed.dom.add(sn, se.element, null, '
    '); + } + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return FALSE; + } + + // If the caret is in an invalid location in FF we need to move it into the first block + if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { + sn = en = sn.firstChild; + so = eo = 0; + rb = d.createRange(); + rb.setStart(sn, 0); + ra = d.createRange(); + ra.setStart(en, 0); + } + + // If the body is totally empty add a BR element this might happen on webkit + if (!d.body.hasChildNodes()) { + d.body.appendChild(dom.create('br')); + } + + // Never use body as start or end node + sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + sn = sn.nodeName == "BODY" ? sn.firstChild : sn; + en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + en = en.nodeName == "BODY" ? en.firstChild : en; + + // Get start and end blocks + sb = t.getParentBlock(sn); + eb = t.getParentBlock(en); + bn = sb ? sb.nodeName : se.element; // Get block name to create + + // Return inside list use default browser behavior + if (n = t.dom.getParent(sb, 'li,pre')) { + if (n.nodeName == 'LI') + return splitList(ed.selection, t.dom, n); + + return TRUE; + } + + // If caption or absolute layers then always generate new blocks within + if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + sb = null; + } + + // If caption or absolute layers then always generate new blocks within + if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + eb = null; + } + + // Use P instead + if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { + bn = se.element; + sb = eb = null; + } + + // Setup new before and after blocks + bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); + aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); + + // Remove id from after clone + aft.removeAttribute('id'); + + // Is header and cursor is at the end, then force paragraph under + if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) + aft = ed.dom.create(se.element); + + // Find start chop node + n = sc = sn; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + sc = n; + } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); + + // Find end chop node + n = ec = en; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + ec = n; + } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); + + // Place first chop part into before block element + if (sc.nodeName == bn) + rb.setStart(sc, 0); + else + rb.setStartBefore(sc); + + rb.setEnd(sn, so); + bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Place secnd chop part within new block element + try { + ra.setEndAfter(ec); + } catch(ex) { + //console.debug(s.focusNode, s.focusOffset); + } + + ra.setStart(en, eo); + aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Create range around everything + r = d.createRange(); + if (!sc.previousSibling && sc.parentNode.nodeName == bn) { + r.setStartBefore(sc.parentNode); + } else { + if (rb.startContainer.nodeName == bn && rb.startOffset == 0) + r.setStartBefore(rb.startContainer); + else + r.setStart(rb.startContainer, rb.startOffset); + } + + if (!ec.nextSibling && ec.parentNode.nodeName == bn) + r.setEndAfter(ec.parentNode); + else + r.setEnd(ra.endContainer, ra.endOffset); + + // Delete and replace it with new block elements + r.deleteContents(); + + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + + // Never wrap blocks in blocks + if (bef.firstChild && bef.firstChild.nodeName == bn) + bef.innerHTML = bef.firstChild.innerHTML; + + if (aft.firstChild && aft.firstChild.nodeName == bn) + aft.innerHTML = aft.firstChild.innerHTML; + + function appendStyles(e, en) { + var nl = [], nn, n, i; + + e.innerHTML = ''; + + // Make clones of style elements + if (se.keep_styles) { + n = en; + do { + // We only want style specific elements + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { + nn = n.cloneNode(FALSE); + dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique + nl.push(nn); + } + } while (n = n.parentNode); + } + + // Append style elements to aft + if (nl.length > 0) { + for (i = nl.length - 1, nn = e; i >= 0; i--) + nn = nn.appendChild(nl[i]); + + // Padd most inner style element + nl[0].innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + return nl[0]; // Move caret to most inner element + } else + e.innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + }; + + // Padd empty blocks + if (dom.isEmpty(bef)) + appendStyles(bef, sn); + + // Fill empty afterblook with current style + if (dom.isEmpty(aft)) + car = appendStyles(aft, en); + + // Opera needs this one backwards for older versions + if (isOpera && parseFloat(opera.version()) < 9.5) { + r.insertNode(bef); + r.insertNode(aft); + } else { + r.insertNode(aft); + r.insertNode(bef); + } + + // Normalize + aft.normalize(); + bef.normalize(); + + // Move cursor and scroll into view + ed.selection.select(aft, true); + ed.selection.collapse(true); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + //ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + 25 > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ + } + + ed.undoManager.add(); + + return FALSE; + }, + + backspaceDelete : function(e, bs) { + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } + + // The caret sometimes gets stuck in Gecko if you delete empty paragraphs + // This workaround removes the element by hand and moves the caret to the previous element + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + + if (n) { + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + + return Event.cancel(e); + } + } + } + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + tinymce.create('tinymce.ControlManager', { + ControlManager : function(ed, s) { + var t = this, i; + + s = s || {}; + t.editor = ed; + t.controls = {}; + t.onAdd = new tinymce.util.Dispatcher(t); + t.onPostRender = new tinymce.util.Dispatcher(t); + t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; + + t.onPostRender.add(function() { + each(t.controls, function(c) { + c.postRender(); + }); + }); + }, + + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + createControl : function(n) { + var c, t = this, ed = t.editor; + + each(ed.plugins, function(p) { + if (p.createControl) { + c = p.createControl(n, t); + + if (c) + return false; + } + }); + + switch (n) { + case "|": + case "separator": + return t.createSeparator(); + } + + if (!c && ed.buttons && (c = ed.buttons[n])) + return t.createButton(n, c); + + return t.add(c); + }, + + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; + + s = extend({ + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus + }, s); + + s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; + if (v = ed.getParam('skin_variant')) + s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + + id = t.prefix + id; + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); + c.onAddItem.add(function(c, o) { + var s = o.settings; + + s.title = ed.getLang(s.title, s.title); + + if (!s.onclick) { + s.onclick = function(v) { + if (s.cmd) + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + }); + + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + + function useNativeListForAccessibility(ed) { + return ed.settings.use_accessible_selects && !tinymce.isGecko + } + + if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) + c = new tinymce.ui.NativeListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s, ed); + } + + t.controls[id] = c; + + // Fix focus problem in Safari + if (tinymce.isWebKit) { + c.onPostRender.add(function(c, n) { + // Store bookmark on mousedown + Event.add(n, 'mousedown', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + + // Restore on focus, since it might be lost + Event.add(n, 'focus', function() { + ed.selection.moveToBookmark(ed.bookmark); + ed.bookmark = null; + }); + }); + } + + if (c.hideMenu) + ed.onMouseDown.add(c.hideMenu, c); + + return t.add(c); + }, + + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.label = ed.translate(s.label); + s.scope = s.scope || ed; + + if (!s.onclick && !s.menu_button) { + s.onclick = function() { + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + unavailable_prefix : ed.getLang('unavailable', ''), + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (s.menu_button) { + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s, ed); + } + + return t.add(c); + }, + + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s, ed)); + ed.onMouseDown.add(c.hideMenu, c); + + return c; + }, + + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls, bm; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + if (tinymce.isIE) + bm = ed.selection.getBookmark(1); + + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + 'menu_class' : ed.getParam('skin') + 'Skin', + scope : s.scope, + more_colors_title : ed.getLang('more_colors') + }, s); + + id = t.prefix + id; + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createToolbar : function(id, s, cc) { + var c, t = this, cls; + + id = t.prefix + id; + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + }); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + tinymce.create('tinymce.WindowManager', { + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); +}(tinymce)); +(function(tinymce) { + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValidChild, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + undefined; + + function isArray(obj) { + return obj instanceof Array; + }; + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); + }; + + // Public functions + + function get(name) { + return name ? formats[name] : formats; + }; + + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undefined) + format.deep = !format.selector; + + // Default to true + if (format.split === undefined) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undefined && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node; + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1 || container.nodeValue === "") { + container = container.nodeType == 1 ? container.childNodes[offset] : container; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } + } + } + } + + return rng; + }; + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.current(); node; node = walker.prev()) { + if (node.childNodes.length > 1 || node == start) { + return node; + } + } + }; + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset == 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ + var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; + + // find the index of the first child list. + each(node.childNodes, function(n, index) { + if (n.nodeName === "UL" || n.nodeName === "OL") { + listIndex = index; + list = n; + return false; + } + }); + + // get the index of the bookmarks + each(node.childNodes, function(n, index) { + if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { + if (n.id == bookmark.id + "_start") { + startIndex = index; + } else if (n.id == bookmark.id + "_end") { + endIndex = index; + } + } + }); + + // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally + if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { + each(tinymce.grep(node.childNodes), process); + return 0; + } else { + currentWrapElm = wrapElm.cloneNode(FALSE); + + // create a list of the nodes on the same side of the list as the selection + each(tinymce.grep(node.childNodes), function(n, index) { + if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { + nodes.push(n); + n.parentNode.removeChild(n); + } + }); + + // insert the wrapping element either before or after the list. + if (startIndex < listIndex) { + node.insertBefore(currentWrapElm, list); + } else if (startIndex > listIndex) { + node.insertBefore(currentWrapElm, list.nextSibling); + } + + // add the new nodes to the list. + newWrappers.push(currentWrapElm); + + each(nodes, function(node) { + currentWrapElm.appendChild(node); + }); + + return currentWrapElm; + } + }; + + function applyRngStyle(rng, bookmark, node_specific) { + var newWrappers = [], wrapName, wrapElm; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + function process(node) { + var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && node.id !== '_mce_caret') { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = wrapElm.cloneNode(FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else if (nodeName == 'li' && bookmark) { + // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. + currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + + // Cleanup + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = child.cloneNode(FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + selection.moveToBookmark(bookmark); + selection.setRng(moveStart(selection.getRng(TRUE))); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng; + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + + // Merges the styles for each node + function process(node) { + var children, i, l; + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + + // Process the children + if (format.deep) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = parent.cloneNode(FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } + }); + }); + }; + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + + // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width + if (tinymce.isWebKit) { + ed.execCommand('mceCleanup'); + } + }; + + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, item_name); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undefined) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }); + + return matchedFormatNames; + }; + + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply + }); + + // Private functions + + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + function expandRng(rng, format, remove) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, child, sibling, siblingName; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + for (;;) { + // Stop expanding on block elements or root depending on format + if (parent == root || (!format[0].block_expand && isBlock(parent))) + return parent; + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + parent = parent.parentNode; + } + + return container; + }; + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) + endOffset = endContainer.length; + } + + if (format[0].inline) { + if (rng.collapsed) { + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof(offset) == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + }; + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return {container : container, offset : pos}; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while (node = walker[start ? 'prev' : 'next']()) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return {container : node, offset : pos}; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return {container: lastTextNode, offset: offset}; + } + } + + // Expand left to closest word boundery + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundery + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + // TODO: Figure out why this is in here + //endContainer = leaf.node.previousSibling; + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

    *texttext*

    ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) + return parents[i]; + } + } + + return container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(true); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('data-mce-' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + if (format.block) { + if (!forcedRootBlock) { + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; + }; + + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undefined) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + function isTextBlock(name) { + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); + }; + + function getContainer(rng, start) { + var container, offset, lastIdx, walker; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + // If start text node is excluded then walk to the next node + if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { + container = new TreeWalker(container, ed.getBody()).next() || container; + } + + // If end text node is excluded then walk to the previous node + if (container.nodeType === 3 && !start && offset == 0) { + container = new TreeWalker(container, ed.getBody()).prev() || container; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; + + // Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container + invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR; + + // Creates a caret container bogus element + function createCaretContainer(fill) { + var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); + + if (fill) { + caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar)); + } + + return caretContainer; + }; + + function isCaretContainerEmpty(node, nodes) { + while (node) { + if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) { + return false; + } + + // Collect nodes + if (nodes && node.nodeType === 1) { + nodes.push(node); + } + + node = node.firstChild; + } + + return true; + }; + + // Returns any parent caret container element + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Removes the caret container for the specified node or all on the current document + function removeCaretContainer(node, move_caret) { + var child, rng; + + if (!node) { + node = getParentCaretContainer(selection.getStart()); + + if (!node) { + while (node = dom.get(caretContainerId)) { + removeCaretContainer(node, false); + } + } + } else { + rng = selection.getRng(true); + + if (isCaretContainerEmpty(node)) { + if (move_caret !== false) { + rng.setStartBefore(node); + rng.setEndBefore(node); + } + + dom.remove(node); + } else { + child = findFirstTextNode(node); + child = child.deleteData(0, 1); + dom.remove(node, 1); + } + + selection.setRng(rng); + } + }; + + // Applies formatting to the caret postion + function applyCaretFormat() { + var rng, caretContainer, textNode, offset, bookmark, container, text; + + rng = selection.getRng(true); + offset = rng.startOffset; + container = rng.startContainer; + text = container.nodeValue; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer) { + textNode = findFirstTextNode(caretContainer); + } + + // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character + if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name)); + rng = rangeUtils.split(rng); + + // Apply the format to the range + apply(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + if (!caretContainer || textNode.nodeValue !== invisibleChar) { + caretContainer = createCaretContainer(true); + textNode = caretContainer.firstChild; + + rng.insertNode(caretContainer); + offset = 1; + + apply(name, vars, caretContainer); + } else { + apply(name, vars, caretContainer); + } + + // Move selection to text node + selection.setCursorLocation(textNode, offset); + } + }; + + function removeCaretFormat() { + var rng = selection.getRng(true), container, offset, bookmark, + hasContentAfter, node, formatNode, parents = [], i, caretContainer; + + container = rng.startContainer; + offset = rng.startOffset; + node = container; + + if (container.nodeType == 3) { + if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) { + hasContentAfter = true; + } + + node = node.parentNode; + } + + while (node) { + if (matchNode(node, name, vars)) { + formatNode = node; + break; + } + + if (node.nextSibling) { + hasContentAfter = true; + } + + parents.push(node); + node = node.parentNode; + } + + // Node doesn't have the specified format + if (!formatNode) { + return; + } + + // Is there contents after the caret then remove the format on the element + if (hasContentAfter) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name), true); + rng = rangeUtils.split(rng); + + // Remove the format from the range + remove(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + caretContainer = createCaretContainer(); + + node = caretContainer; + for (i = parents.length - 1; i >= 0; i--) { + node.appendChild(parents[i].cloneNode(false)); + node = node.firstChild; + } + + // Insert invisible character into inner most format element + node.appendChild(dom.doc.createTextNode(invisibleChar)); + node = node.firstChild; + + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + + // Move selection to text node + selection.setCursorLocation(node, 1); + } + }; + + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + ed.onBeforeGetContent.addToTop(function() { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }); + + // Remove caret container on mouse up and on key up + tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { + ed[name].addToTop(function() { + removeCaretContainer(); + }); + }); + + // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys + ed.onKeyDown.addToTop(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == 8 || keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + }); + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + }; + }; +})(tinymce); + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_legacy_values); + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); + diff --git a/js/tiny_mce/tiny_mce_popup.js b/js/tiny_mce/tiny_mce_popup.js index 3ef3acb1..f859d24e 100644 --- a/js/tiny_mce/tiny_mce_popup.js +++ b/js/tiny_mce/tiny_mce_popup.js @@ -2,4 +2,4 @@ // Uncomment and change this document.domain value if you are loading the script cross subdomains // document.domain = 'moxiecode.com'; -var tinymce=null,tinyMCEPopup,tinyMCE;tinyMCEPopup={init:function(){var b=this,a,c;a=b.getWin();tinymce=a.tinymce;tinyMCE=a.tinyMCE;b.editor=tinymce.EditorManager.activeEditor;b.params=b.editor.windowManager.params;b.features=b.editor.windowManager.features;b.dom=b.editor.windowManager.createInstance("tinymce.dom.DOMUtils",document);if(b.features.popup_css!==false){b.dom.loadCSS(b.features.popup_css||b.editor.settings.popup_css)}b.listeners=[];b.onInit={add:function(e,d){b.listeners.push({func:e,scope:d})}};b.isWindow=!b.getWindowArg("mce_inline");b.id=b.getWindowArg("mce_window_id");b.editor.windowManager.onOpen.dispatch(b.editor.windowManager,window)},getWin:function(){return(!window.frameElement&&window.dialogArguments)||opener||parent||top},getWindowArg:function(c,b){var a=this.params[c];return tinymce.is(a)?a:b},getParam:function(b,a){return this.editor.getParam(b,a)},getLang:function(b,a){return this.editor.getLang(b,a)},execCommand:function(d,c,e,b){b=b||{};b.skip_focus=1;this.restoreSelection();return this.editor.execCommand(d,c,e,b)},resizeToInnerSize:function(){var a=this;setTimeout(function(){var b=a.dom.getViewPort(window);a.editor.windowManager.resizeBy(a.getWindowArg("mce_width")-b.w,a.getWindowArg("mce_height")-b.h,a.id||window)},0)},executeOnLoad:function(s){this.onInit.add(function(){eval(s)})},storeSelection:function(){this.editor.windowManager.bookmark=tinyMCEPopup.editor.selection.getBookmark(1)},restoreSelection:function(){var a=tinyMCEPopup;if(!a.isWindow&&tinymce.isIE){a.editor.selection.moveToBookmark(a.editor.windowManager.bookmark)}},requireLangPack:function(){var b=this,a=b.getWindowArg("plugin_url")||b.getWindowArg("theme_url");if(a&&b.editor.settings.language&&b.features.translate_i18n!==false){a+="/langs/"+b.editor.settings.language+"_dlg.js";if(!tinymce.ScriptLoader.isDone(a)){document.write(''; - - bi = s.body_id || 'tinymce'; - if (bi.indexOf('=') != -1) { - bi = t.getParam('body_id', '', 'hash'); - bi = bi[t.id] || bi; - } - - bc = s.body_class || ''; - if (bc.indexOf('=') != -1) { - bc = t.getParam('body_class', '', 'hash'); - bc = bc[t.id] || ''; - } - - t.iframeHTML += ''; - - // Domain relaxing enabled, then set document domain - if (tinymce.relaxedDomain) { - // We need to write the contents here in IE since multiple writes messes up refresh button and back button - if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; - else if (tinymce.isOpera) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; - } - - // Create iframe - n = DOM.add(o.iframeContainer, 'iframe', { - id : t.id + "_ifr", - src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 - frameBorder : '0', - style : { - width : '100%', - height : h - } - }); - - t.contentAreaContainer = o.iframeContainer; - DOM.get(o.editorContainer).style.display = t.orgDisplay; - DOM.get(t.id).style.display = 'none'; - - if (!isIE || !tinymce.relaxedDomain) - t.setupIframe(); - - e = n = o = null; // Cleanup - }, - - setupIframe : function() { - var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; - - // Setup iframe body - if (!isIE || !tinymce.relaxedDomain) { - d.open(); - d.write(t.iframeHTML); - d.close(); - } - - // Design mode needs to be added here Ctrl+A will fail otherwise - if (!isIE) { - try { - if (!s.readonly) - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - - // IE needs to use contentEditable or it will display non secure items for HTTPS - if (isIE) { - // It will not steal focus if we hide it while setting contentEditable - b = t.getBody(); - DOM.hide(b); - - if (!s.readonly) - b.contentEditable = true; - - DOM.show(b); - } - - t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { - keep_values : true, - url_converter : t.convertURL, - url_converter_scope : t, - hex_colors : s.force_hex_style_colors, - class_filter : s.class_filter, - update_styles : 1, - fix_ie_paragraphs : 1, - valid_styles : s.valid_styles - }); - - t.schema = new tinymce.dom.Schema(); - - t.serializer = new tinymce.dom.Serializer(extend(s, { - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - dom : t.dom, - schema : t.schema - })); - - t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); - - t.formatter = new tinymce.Formatter(this); - - // Register default formats - t.formatter.register({ - alignleft : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, - {selector : 'img,table', styles : {'float' : 'left'}} - ], - - aligncenter : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, - {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, - {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} - ], - - alignright : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, - {selector : 'img,table', styles : {'float' : 'right'}} - ], - - alignfull : [ - {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} - ], - - bold : [ - {inline : 'strong'}, - {inline : 'span', styles : {fontWeight : 'bold'}}, - {inline : 'b'} - ], - - italic : [ - {inline : 'em'}, - {inline : 'span', styles : {fontStyle : 'italic'}}, - {inline : 'i'} - ], - - underline : [ - {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, - {inline : 'u'} - ], - - strikethrough : [ - {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, - {inline : 'u'} - ], - - forecolor : {inline : 'span', styles : {color : '%value'}}, - hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, - fontname : {inline : 'span', styles : {fontFamily : '%value'}}, - fontsize : {inline : 'span', styles : {fontSize : '%value'}}, - blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, - - removeformat : [ - {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, - {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, - {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} - ] - }); - - // Register default block formats - each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { - t.formatter.register(name, {block : name, remove : 'all'}); - }); - - // Register user defined formats - t.formatter.register(t.settings.formats); - - t.undoManager = new tinymce.UndoManager(t); - - // Pass through - t.undoManager.onAdd.add(function(um, l) { - if (!l.initial) - return t.onChange.dispatch(t, l, um); - }); - - t.undoManager.onUndo.add(function(um, l) { - return t.onUndo.dispatch(t, l, um); - }); - - t.undoManager.onRedo.add(function(um, l) { - return t.onRedo.dispatch(t, l, um); - }); - - t.forceBlocks = new tinymce.ForceBlocks(t, { - forced_root_block : s.forced_root_block - }); - - t.editorCommands = new tinymce.EditorCommands(t); - - // Pass through - t.serializer.onPreProcess.add(function(se, o) { - return t.onPreProcess.dispatch(t, o, se); - }); - - t.serializer.onPostProcess.add(function(se, o) { - return t.onPostProcess.dispatch(t, o, se); - }); - - t.onPreInit.dispatch(t); - - if (!s.gecko_spellcheck) - t.getBody().spellcheck = 0; - - if (!s.readonly) - t._addEvents(); - - t.controlManager.onPostRender.dispatch(t, t.controlManager); - t.onPostRender.dispatch(t); - - if (s.directionality) - t.getBody().dir = s.directionality; - - if (s.nowrap) - t.getBody().style.whiteSpace = "nowrap"; - - if (s.custom_elements) { - function handleCustom(ed, o) { - each(explode(s.custom_elements), function(v) { - var n; - - if (v.indexOf('~') === 0) { - v = v.substring(1); - n = 'span'; - } else - n = 'div'; - - o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); - o.content = o.content.replace(new RegExp('', 'g'), ''); - }); - }; - - t.onBeforeSetContent.add(handleCustom); - t.onPostProcess.add(function(ed, o) { - if (o.set) - handleCustom(ed, o); - }); - } - - if (s.handle_node_change_callback) { - t.onNodeChange.add(function(ed, cm, n) { - t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); - }); - } - - if (s.save_callback) { - t.onSaveContent.add(function(ed, o) { - var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); - - if (h) - o.content = h; - }); - } - - if (s.onchange_callback) { - t.onChange.add(function(ed, l) { - t.execCallback('onchange_callback', t, l); - }); - } - - if (s.convert_newlines_to_brs) { - t.onBeforeSetContent.add(function(ed, o) { - if (o.initial) - o.content = o.content.replace(/\r?\n/g, '
    '); - }); - } - - if (s.fix_nesting && isIE) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t._fixNesting(o.content); - }); - } - - if (s.preformatted) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^\s*/, ''); - o.content = o.content.replace(/<\/pre>\s*$/, ''); - - if (o.set) - o.content = '
    ' + o.content + '
    '; - }); - } - - if (s.verify_css_classes) { - t.serializer.attribValueFilter = function(n, v) { - var s, cl; - - if (n == 'class') { - // Build regexp for classes - if (!t.classesRE) { - cl = t.dom.getClasses(); - - if (cl.length > 0) { - s = ''; - - each (cl, function(o) { - s += (s ? '|' : '') + o['class']; - }); - - t.classesRE = new RegExp('(' + s + ')', 'gi'); - } - } - - return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; - } - - return v; - }; - } - - if (s.cleanup_callback) { - t.onBeforeSetContent.add(function(ed, o) { - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - }); - - t.onPreProcess.add(function(ed, o) { - if (o.set) - t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); - - if (o.get) - t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); - }); - - t.onPostProcess.add(function(ed, o) { - if (o.set) - o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); - - if (o.get) - o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); - }); - } - - if (s.save_callback) { - t.onGetContent.add(function(ed, o) { - if (o.save) - o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); - }); - } - - if (s.handle_event_callback) { - t.onEvent.add(function(ed, e, o) { - if (t.execCallback('handle_event_callback', e, ed, o) === false) - Event.cancel(e); - }); - } - - // Add visual aids when new contents is added - t.onSetContent.add(function() { - t.addVisual(t.getBody()); - }); - - // Remove empty contents - if (s.padd_empty_editor) { - t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); - }); - } - - if (isGecko) { - // Fix gecko link bug, when a link is placed at the end of block elements there is - // no way to move the caret behind the link. This fix adds a bogus br element after the link - function fixLinks(ed, o) { - each(ed.dom.select('a'), function(n) { - var pn = n.parentNode; - - if (ed.dom.isBlock(pn) && pn.lastChild === n) - ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); - }); - }; - - t.onExecCommand.add(function(ed, cmd) { - if (cmd === 'CreateLink') - fixLinks(ed); - }); - - t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); - - if (!s.readonly) { - try { - // Design mode must be set here once again to fix a bug where - // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again - d.designMode = 'Off'; - d.designMode = 'On'; - } catch (ex) { - // Will fail on Gecko if the editor is placed in an hidden container element - // The design mode will be set ones the editor is focused - } - } - } - - // A small timeout was needed since firefox will remove. Bug: #1838304 - setTimeout(function () { - if (t.removed) - return; - - t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); - t.startContent = t.getContent({format : 'raw'}); - t.initialized = true; - - t.onInit.dispatch(t); - t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); - t.execCallback('init_instance_callback', t); - t.focus(true); - t.nodeChanged({initial : 1}); - - // Load specified content CSS last - if (s.content_css) { - tinymce.each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } - - // Handle auto focus - if (s.auto_focus) { - setTimeout(function () { - var ed = tinymce.get(s.auto_focus); - - ed.selection.select(ed.getBody(), 1); - ed.selection.collapse(1); - ed.getWin().focus(); - }, 100); - } - }, 1); - - e = null; - }, - - - focus : function(sf) { - var oed, t = this, ce = t.settings.content_editable; - - if (!sf) { - // Is not content editable - if (!ce) - t.getWin().focus(); - - } - - if (tinymce.activeEditor != t) { - if ((oed = tinymce.activeEditor) != null) - oed.onDeactivate.dispatch(oed, t); - - t.onActivate.dispatch(t, oed); - } - - tinymce._setActive(t); - }, - - execCallback : function(n) { - var t = this, f = t.settings[n], s; - - if (!f) - return; - - // Look through lookup - if (t.callbackLookup && (s = t.callbackLookup[n])) { - f = s.func; - s = s.scope; - } - - if (is(f, 'string')) { - s = f.replace(/\.\w+$/, ''); - s = s ? tinymce.resolve(s) : 0; - f = tinymce.resolve(f); - t.callbackLookup = t.callbackLookup || {}; - t.callbackLookup[n] = {func : f, scope : s}; - } - - return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); - }, - - translate : function(s) { - var c = this.settings.language || 'en', i18n = tinymce.i18n; - - if (!s) - return ''; - - return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { - return i18n[c + '.' + b] || '{#' + b + '}'; - }); - }, - - getLang : function(n, dv) { - return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); - }, - - getParam : function(n, dv, ty) { - var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; - - if (ty === 'hash') { - o = {}; - - if (is(v, 'string')) { - each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { - v = v.split('='); - - if (v.length > 1) - o[tr(v[0])] = tr(v[1]); - else - o[tr(v[0])] = tr(v); - }); - } else - o = v; - - return o; - } - - return v; - }, - - nodeChanged : function(o) { - var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); - - // Fix for bug #1896577 it seems that this can not be fired while the editor is loading - if (t.initialized) { - o = o || {}; - n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state - - // Get parents and add them to object - o.parents = []; - t.dom.getParent(n, function(node) { - if (node.nodeName == 'BODY') - return true; - - o.parents.push(node); - }); - - t.onNodeChange.dispatch( - t, - o ? o.controlManager || t.controlManager : t.controlManager, - n, - s.isCollapsed(), - o - ); - } - }, - - addButton : function(n, s) { - var t = this; - - t.buttons = t.buttons || {}; - t.buttons[n] = s; - }, - - addCommand : function(n, f, s) { - this.execCommands[n] = {func : f, scope : s || this}; - }, - - addQueryStateHandler : function(n, f, s) { - this.queryStateCommands[n] = {func : f, scope : s || this}; - }, - - addQueryValueHandler : function(n, f, s) { - this.queryValueCommands[n] = {func : f, scope : s || this}; - }, - - addShortcut : function(pa, desc, cmd_func, sc) { - var t = this, c; - - if (!t.settings.custom_shortcuts) - return false; - - t.shortcuts = t.shortcuts || {}; - - if (is(cmd_func, 'string')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c, false, null); - }; - } - - if (is(cmd_func, 'object')) { - c = cmd_func; - - cmd_func = function() { - t.execCommand(c[0], c[1], c[2]); - }; - } - - each(explode(pa), function(pa) { - var o = { - func : cmd_func, - scope : sc || this, - desc : desc, - alt : false, - ctrl : false, - shift : false - }; - - each(explode(pa, '+'), function(v) { - switch (v) { - case 'alt': - case 'ctrl': - case 'shift': - o[v] = true; - break; - - default: - o.charCode = v.charCodeAt(0); - o.keyCode = v.toUpperCase().charCodeAt(0); - } - }); - - t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; - }); - - return true; - }, - - execCommand : function(cmd, ui, val, a) { - var t = this, s = 0, o, st; - - if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) - t.focus(); - - o = {}; - t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); - if (o.terminate) - return false; - - // Command callback - if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Registred commands - if (o = t.execCommands[cmd]) { - st = o.func.call(o.scope, ui, val); - - // Fall through on true - if (st !== true) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return st; - } - } - - // Plugin commands - each(t.plugins, function(p) { - if (p.execCommand && p.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - s = 1; - return false; - } - }); - - if (s) - return true; - - // Theme commands - if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Execute global commands - if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Editor commands - if (t.editorCommands.execCommand(cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - - // Browser commands - t.getDoc().execCommand(cmd, ui, val); - t.onExecCommand.dispatch(t, cmd, ui, val, a); - }, - - queryCommandState : function(cmd) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryStateCommands[cmd]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandState(cmd); - if (o !== -1) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandState(cmd); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - queryCommandValue : function(c) { - var t = this, o, s; - - // Is hidden then return undefined - if (t._isHidden()) - return; - - // Registred commands - if (o = t.queryValueCommands[c]) { - s = o.func.call(o.scope); - - // Fall though on true - if (s !== true) - return s; - } - - // Registred commands - o = t.editorCommands.queryCommandValue(c); - if (is(o)) - return o; - - // Browser commands - try { - return this.getDoc().queryCommandValue(c); - } catch (ex) { - // Fails sometimes see bug: 1896577 - } - }, - - show : function() { - var t = this; - - DOM.show(t.getContainer()); - DOM.hide(t.id); - t.load(); - }, - - hide : function() { - var t = this, d = t.getDoc(); - - // Fixed bug where IE has a blinking cursor left from the editor - if (isIE && d) - d.execCommand('SelectAll'); - - // We must save before we hide so Safari doesn't crash - t.save(); - DOM.hide(t.getContainer()); - DOM.setStyle(t.id, 'display', t.orgDisplay); - }, - - isHidden : function() { - return !DOM.isHidden(this.id); - }, - - setProgressState : function(b, ti, o) { - this.onSetProgressState.dispatch(this, b, ti, o); - - return b; - }, - - load : function(o) { - var t = this, e = t.getElement(), h; - - if (e) { - o = o || {}; - o.load = true; - - // Double encode existing entities in the value - h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); - o.element = e; - - if (!o.no_events) - t.onLoadContent.dispatch(t, o); - - o.element = e = null; - - return h; - } - }, - - save : function(o) { - var t = this, e = t.getElement(), h, f; - - if (!e || !t.initialized) - return; - - o = o || {}; - o.save = true; - - // Add undo level will trigger onchange event - if (!o.no_events) { - t.undoManager.typing = 0; - t.undoManager.add(); - } - - o.element = e; - h = o.content = t.getContent(o); - - if (!o.no_events) - t.onSaveContent.dispatch(t, o); - - h = o.content; - - if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { - e.innerHTML = h; - - // Update hidden form element - if (f = DOM.getParent(t.id, 'form')) { - each(f.elements, function(e) { - if (e.name == t.id) { - e.value = h; - return false; - } - }); - } - } else - e.value = h; - - o.element = e = null; - - return h; - }, - - setContent : function(h, o) { - var t = this; - - o = o || {}; - o.format = o.format || 'html'; - o.set = true; - o.content = h; - - if (!o.no_events) - t.onBeforeSetContent.dispatch(t, o); - - // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content - // It will also be impossible to place the caret in the editor unless there is a BR element present - if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { - o.content = t.dom.setHTML(t.getBody(), '
    '); - o.format = 'raw'; - } - - o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); - } - - if (!o.no_events) - t.onSetContent.dispatch(t, o); - - return o.content; - }, - - getContent : function(o) { - var t = this, h; - - o = o || {}; - o.format = o.format || 'html'; - o.get = true; - - if (!o.no_events) - t.onBeforeGetContent.dispatch(t, o); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - h = t.serializer.serialize(t.getBody(), o); - } else - h = t.getBody().innerHTML; - - h = h.replace(/^\s*|\s*$/g, ''); - o.content = h; - - if (!o.no_events) - t.onGetContent.dispatch(t, o); - - return o.content; - }, - - isDirty : function() { - var t = this; - - return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; - }, - - getContainer : function() { - var t = this; - - if (!t.container) - t.container = DOM.get(t.editorContainer || t.id + '_parent'); - - return t.container; - }, - - getContentAreaContainer : function() { - return this.contentAreaContainer; - }, - - getElement : function() { - return DOM.get(this.settings.content_element || this.id); - }, - - getWin : function() { - var t = this, e; - - if (!t.contentWindow) { - e = DOM.get(t.id + "_ifr"); - - if (e) - t.contentWindow = e.contentWindow; - } - - return t.contentWindow; - }, - - getDoc : function() { - var t = this, w; - - if (!t.contentDocument) { - w = t.getWin(); - - if (w) - t.contentDocument = w.document; - } - - return t.contentDocument; - }, - - getBody : function() { - return this.bodyElement || this.getDoc().body; - }, - - convertURL : function(u, n, e) { - var t = this, s = t.settings; - - // Use callback instead - if (s.urlconverter_callback) - return t.execCallback('urlconverter_callback', u, e, true, n); - - // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs - if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) - return u; - - // Convert to relative - if (s.relative_urls) - return t.documentBaseURI.toRelative(u); - - // Convert to absolute - u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); - - return u; - }, - - addVisual : function(e) { - var t = this, s = t.settings; - - e = e || t.getBody(); - - if (!is(t.hasVisual)) - t.hasVisual = s.visual; - - each(t.dom.select('table,a', e), function(e) { - var v; - - switch (e.nodeName) { - case 'TABLE': - v = t.dom.getAttrib(e, 'border'); - - if (!v || v == '0') { - if (t.hasVisual) - t.dom.addClass(e, s.visual_table_class); - else - t.dom.removeClass(e, s.visual_table_class); - } - - return; - - case 'A': - v = t.dom.getAttrib(e, 'name'); - - if (v) { - if (t.hasVisual) - t.dom.addClass(e, 'mceItemAnchor'); - else - t.dom.removeClass(e, 'mceItemAnchor'); - } - - return; - } - }); - - t.onVisualAid.dispatch(t, e, t.hasVisual); - }, - - remove : function() { - var t = this, e = t.getContainer(); - - t.removed = 1; // Cancels post remove event execution - t.hide(); - - t.execCallback('remove_instance_callback', t); - t.onRemove.dispatch(t); - - // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command - t.onExecCommand.listeners = []; - - tinymce.remove(t); - DOM.remove(e); - }, - - destroy : function(s) { - var t = this; - - // One time is enough - if (t.destroyed) - return; - - if (!s) { - tinymce.removeUnload(t.destroy); - tinyMCE.onBeforeUnload.remove(t._beforeUnload); - - // Manual destroy - if (t.theme && t.theme.destroy) - t.theme.destroy(); - - // Destroy controls, selection and dom - t.controlManager.destroy(); - t.selection.destroy(); - t.dom.destroy(); - - // Remove all events - - // Don't clear the window or document if content editable - // is enabled since other instances might still be present - if (!t.settings.content_editable) { - Event.clear(t.getWin()); - Event.clear(t.getDoc()); - } - - Event.clear(t.getBody()); - Event.clear(t.formElement); - } - - if (t.formElement) { - t.formElement.submit = t.formElement._mceOldSubmit; - t.formElement._mceOldSubmit = null; - } - - t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; - - if (t.selection) - t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; - - t.destroyed = 1; - }, - - // Internal functions - - _addEvents : function() { - // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset - var t = this, i, s = t.settings, lo = { - mouseup : 'onMouseUp', - mousedown : 'onMouseDown', - click : 'onClick', - keyup : 'onKeyUp', - keydown : 'onKeyDown', - keypress : 'onKeyPress', - submit : 'onSubmit', - reset : 'onReset', - contextmenu : 'onContextMenu', - dblclick : 'onDblClick', - paste : 'onPaste' // Doesn't work in all browsers yet - }; - - function eventHandler(e, o) { - var ty = e.type; - - // Don't fire events when it's removed - if (t.removed) - return; - - // Generic event handler - if (t.onEvent.dispatch(t, e, o) !== false) { - // Specific event handler - t[lo[e.fakeType || e.type]].dispatch(t, e, o); - } - }; - - // Add DOM events - each(lo, function(v, k) { - switch (k) { - case 'contextmenu': - if (tinymce.isOpera) { - // Fake contextmenu on Opera - t.dom.bind(t.getBody(), 'mousedown', function(e) { - if (e.ctrlKey) { - e.fakeType = 'contextmenu'; - eventHandler(e); - } - }); - } else - t.dom.bind(t.getBody(), k, eventHandler); - break; - - case 'paste': - t.dom.bind(t.getBody(), k, function(e) { - eventHandler(e); - }); - break; - - case 'submit': - case 'reset': - t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); - break; - - default: - t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); - } - }); - - t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { - t.focus(true); - }); - - - // Fixes bug where a specified document_base_uri could result in broken images - // This will also fix drag drop of images in Gecko - if (tinymce.isGecko) { - // Convert all images to absolute URLs -/* t.onSetContent.add(function(ed, o) { - each(ed.dom.select('img'), function(e) { - var v; - - if (v = e.getAttribute('_mce_src')) - e.src = t.documentBaseURI.toAbsolute(v); - }) - });*/ - - t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { - var v; - - e = e.target; - - if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) - e.src = t.documentBaseURI.toAbsolute(v); - }); - } - - // Set various midas options in Gecko - if (isGecko) { - function setOpts() { - var t = this, d = t.getDoc(), s = t.settings; - - if (isGecko && !s.readonly) { - if (t._isHidden()) { - try { - if (!s.content_editable) - d.designMode = 'On'; - } catch (ex) { - // Fails if it's hidden - } - } - - try { - // Try new Gecko method - d.execCommand("styleWithCSS", 0, false); - } catch (ex) { - // Use old method - if (!t._isHidden()) - try {d.execCommand("useCSS", 0, true);} catch (ex) {} - } - - if (!s.table_inline_editing) - try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} - - if (!s.object_resizing) - try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} - } - }; - - t.onBeforeExecCommand.add(setOpts); - t.onMouseDown.add(setOpts); - } - - // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 - // WebKit can't even do simple things like selecting an image - // This also fixes so it's possible to select mceItemAnchors - if (tinymce.isWebKit) { - t.onClick.add(function(ed, e) { - e = e.target; - - // Needs tobe the setBaseAndExtend or it will fail to select floated images - if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor'))) - t.selection.getSel().setBaseAndExtent(e, 0, e, 1); - }); - } - - // Add node change handlers - t.onMouseUp.add(t.nodeChanged); - t.onClick.add(t.nodeChanged); - t.onKeyUp.add(function(ed, e) { - var c = e.keyCode; - - if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) - t.nodeChanged(); - }); - - // Add reset handler - t.onReset.add(function() { - t.setContent(t.startContent, {format : 'raw'}); - }); - - // Add shortcuts - if (s.custom_shortcuts) { - if (s.custom_undo_redo_keyboard_shortcuts) { - t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); - t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); - } - - // Add default shortcuts for gecko - if (isGecko) { - t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); - t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); - t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); - } - - // BlockFormat shortcuts keys - for (i=1; i<=6; i++) - t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); - - t.addShortcut('ctrl+7', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+8', '', ['FormatBlock', false, '

    ']); - t.addShortcut('ctrl+9', '', ['FormatBlock', false, '
    ']); - - function find(e) { - var v = null; - - if (!e.altKey && !e.ctrlKey && !e.metaKey) - return v; - - each(t.shortcuts, function(o) { - if (tinymce.isMac && o.ctrl != e.metaKey) - return; - else if (!tinymce.isMac && o.ctrl != e.ctrlKey) - return; - - if (o.alt != e.altKey) - return; - - if (o.shift != e.shiftKey) - return; - - if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { - v = o; - return false; - } - }); - - return v; - }; - - t.onKeyUp.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyPress.add(function(ed, e) { - var o = find(e); - - if (o) - return Event.cancel(e); - }); - - t.onKeyDown.add(function(ed, e) { - var o = find(e); - - if (o) { - o.func.call(o.scope); - return Event.cancel(e); - } - }); - } - - if (tinymce.isIE) { - // Fix so resize will only update the width and height attributes not the styles of an image - // It will also block mceItemNoResize items - t.dom.bind(t.getDoc(), 'controlselect', function(e) { - var re = t.resizeInfo, cb; - - e = e.target; - - // Don't do this action for non image elements - if (e.nodeName !== 'IMG') - return; - - if (re) - t.dom.unbind(re.node, re.ev, re.cb); - - if (!t.dom.hasClass(e, 'mceItemNoResize')) { - ev = 'resizeend'; - cb = t.dom.bind(e, ev, function(e) { - var v; - - e = e.target; - - if (v = t.dom.getStyle(e, 'width')) { - t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'width', ''); - } - - if (v = t.dom.getStyle(e, 'height')) { - t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'height', ''); - } - }); - } else { - ev = 'resizestart'; - cb = t.dom.bind(e, 'resizestart', Event.cancel, Event); - } - - re = t.resizeInfo = { - node : e, - ev : ev, - cb : cb - }; - }); - - t.onKeyDown.add(function(ed, e) { - switch (e.keyCode) { - case 8: - // Fix IE control + backspace browser bug - if (t.selection.getRng().item) { - ed.dom.remove(t.selection.getRng().item(0)); - return Event.cancel(e); - } - } - }); - - /*if (t.dom.boxModel) { - t.getBody().style.height = '100%'; - - Event.add(t.getWin(), 'resize', function(e) { - var docElm = t.getDoc().documentElement; - - docElm.style.height = (docElm.offsetHeight - 10) + 'px'; - }); - }*/ - } - - if (tinymce.isOpera) { - t.onClick.add(function(ed, e) { - Event.prevent(e); - }); - } - - // Add custom undo/redo handlers - if (s.custom_undo_redo) { - function addUndo() { - t.undoManager.typing = 0; - t.undoManager.add(); - }; - - t.dom.bind(t.getDoc(), 'focusout', function(e) { - if (!t.removed && t.undoManager.typing) - addUndo(); - }); - - t.onKeyUp.add(function(ed, e) { - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) - addUndo(); - }); - - t.onKeyDown.add(function(ed, e) { - // Is caracter positon keys - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { - if (t.undoManager.typing) - addUndo(); - - return; - } - - if (!t.undoManager.typing) { - t.undoManager.add(); - t.undoManager.typing = 1; - } - }); - - t.onMouseDown.add(function() { - if (t.undoManager.typing) - addUndo(); - }); - } - }, - - _isHidden : function() { - var s; - - if (!isGecko) - return 0; - - // Weird, wheres that cursor selection? - s = this.selection.getSel(); - return (!s || !s.rangeCount || s.rangeCount == 0); - }, - - // Fix for bug #1867292 - _fixNesting : function(s) { - var d = [], i; - - s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { - var e; - - // Handle end element - if (b === '/') { - if (!d.length) - return ''; - - if (c !== d[d.length - 1].tag) { - for (i=d.length - 1; i>=0; i--) { - if (d[i].tag === c) { - d[i].close = 1; - break; - } - } - - return ''; - } else { - d.pop(); - - if (d.length && d[d.length - 1].close) { - a = a + ''; - d.pop(); - } - } - } else { - // Ignore these - if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) - return a; - - // Ignore closed ones - if (/\/>$/.test(a)) - return a; - - d.push({tag : c}); // Push start element - } - - return a; - }); - - // End all open tags - for (i=d.length - 1; i>=0; i--) - s += ''; - - return s; - } - }); -})(tinymce); - -(function(tinymce) { - // Added for compression purposes - var each = tinymce.each, undefined, TRUE = true, FALSE = false; - - tinymce.EditorCommands = function(editor) { - var dom = editor.dom, - selection = editor.selection, - commands = {state: {}, exec : {}, value : {}}, - settings = editor.settings, - bookmark; - - function execCommand(command, ui, value) { - var func; - - command = command.toLowerCase(); - if (func = commands.exec[command]) { - func(command, ui, value); - return TRUE; - } - - return FALSE; - }; - - function queryCommandState(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.state[command]) - return func(command); - - return -1; - }; - - function queryCommandValue(command) { - var func; - - command = command.toLowerCase(); - if (func = commands.value[command]) - return func(command); - - return FALSE; - }; - - function addCommands(command_list, type) { - type = type || 'exec'; - - each(command_list, function(callback, command) { - each(command.toLowerCase().split(','), function(command) { - commands[type][command] = callback; - }); - }); - }; - - // Expose public methods - tinymce.extend(this, { - execCommand : execCommand, - queryCommandState : queryCommandState, - queryCommandValue : queryCommandValue, - addCommands : addCommands - }); - - // Private methods - - function execNativeCommand(command, ui, value) { - if (ui === undefined) - ui = FALSE; - - if (value === undefined) - value = null; - - return editor.getDoc().execCommand(command, ui, value); - }; - - function isFormatMatch(name) { - return editor.formatter.match(name); - }; - - function toggleFormat(name, value) { - editor.formatter.toggle(name, value ? {value : value} : undefined); - }; - - function storeSelection(type) { - bookmark = selection.getBookmark(type); - }; - - function restoreSelection() { - selection.moveToBookmark(bookmark); - }; - - // Add execCommand overrides - addCommands({ - // Ignore these, added for compatibility - 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, - - // Add undo manager logic - 'mceEndUndoLevel,mceAddUndoLevel' : function() { - editor.undoManager.add(); - }, - - 'Cut,Copy,Paste' : function(command) { - var doc = editor.getDoc(), failed; - - // Try executing the native command - try { - execNativeCommand(command); - } catch (ex) { - // Command failed - failed = TRUE; - } - - // Present alert message about clipboard access not being available - if (failed || !doc.queryCommandEnabled(command)) { - if (tinymce.isGecko) { - editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { - if (state) - open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); - }); - } else - editor.windowManager.alert(editor.getLang('clipboard_no_support')); - } - }, - - // Override unlink command - unlink : function(command) { - if (selection.isCollapsed()) - selection.select(selection.getNode()); - - execNativeCommand(command); - selection.collapse(FALSE); - }, - - // Override justify commands to use the text formatter engine - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - var align = command.substring(7); - - // Remove all other alignments first - each('left,center,right,full'.split(','), function(name) { - if (align != name) - editor.formatter.remove('align' + name); - }); - - toggleFormat('align' + align); - }, - - // Override list commands to fix WebKit bug - 'InsertUnorderedList,InsertOrderedList' : function(command) { - var listElm, listParent; - - execNativeCommand(command); - - // WebKit produces lists within block elements so we need to split them - // we will replace the native list creation logic to custom logic later on - // TODO: Remove this when the list creation logic is removed - listElm = dom.getParent(selection.getNode(), 'ol,ul'); - if (listElm) { - listParent = listElm.parentNode; - - // If list is within a text block then split that block - if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { - storeSelection(); - dom.split(listParent, listElm); - restoreSelection(); - } - } - }, - - // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough' : function(command) { - toggleFormat(command); - }, - - // Override commands to use the text formatter engine - 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { - toggleFormat(command, value); - }, - - FontSize : function(command, ui, value) { - var fontClasses, fontSizes; - - // Convert font size 1-7 to styles - if (value >= 1 && value <= 7) { - fontSizes = tinymce.explode(settings.font_size_style_values); - fontClasses = tinymce.explode(settings.font_size_classes); - - if (fontClasses) - value = fontClasses[value - 1] || value; - else - value = fontSizes[value - 1] || value; - } - - toggleFormat(command, value); - }, - - RemoveFormat : function(command) { - editor.formatter.remove(command); - }, - - mceBlockQuote : function(command) { - toggleFormat('blockquote'); - }, - - FormatBlock : function(command, ui, value) { - return toggleFormat(value); - }, - - mceCleanup : function() { - storeSelection(); - editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); - restoreSelection(); - }, - - mceRemoveNode : function(command, ui, value) { - var node = value || selection.getNode(); - - // Make sure that the body node isn't removed - if (node != ed.getBody()) { - storeSelection(); - editor.dom.remove(node, TRUE); - restoreSelection(); - } - }, - - mceSelectNodeDepth : function(command, ui, value) { - var counter = 0; - - dom.getParent(selection.getNode(), function(node) { - if (node.nodeType == 1 && counter++ == value) { - selection.select(node); - return FALSE; - } - }, editor.getBody()); - }, - - mceSelectNode : function(command, ui, value) { - selection.select(value); - }, - - mceInsertContent : function(command, ui, value) { - selection.setContent(value); - }, - - mceInsertRawHTML : function(command, ui, value) { - selection.setContent('tiny_mce_marker'); - editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value)); - }, - - mceSetContent : function(command, ui, value) { - editor.setContent(value); - }, - - 'Indent,Outdent' : function(command) { - var intentValue, indentUnit, value; - - // Setup indent level - intentValue = settings.indentation; - indentUnit = /[a-z%]+$/i.exec(intentValue); - intentValue = parseInt(intentValue); - - if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { - each(selection.getSelectedBlocks(), function(element) { - if (command == 'outdent') { - value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); - dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); - } else - dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); - }); - } else - execNativeCommand(command); - }, - - mceRepaint : function() { - var bookmark; - - if (tinymce.isGecko) { - try { - storeSelection(TRUE); - - if (selection.getSel()) - selection.getSel().selectAllChildren(editor.getBody()); - - selection.collapse(TRUE); - restoreSelection(); - } catch (ex) { - // Ignore - } - } - }, - - mceToggleFormat : function(command, ui, value) { - editor.formatter.toggle(value); - }, - - InsertHorizontalRule : function() { - selection.setContent('
    '); - }, - - mceToggleVisualAid : function() { - editor.hasVisual = !editor.hasVisual; - editor.addVisual(); - }, - - mceReplaceContent : function(command, ui, value) { - selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); - }, - - mceInsertLink : function(command, ui, value) { - var link = dom.getParent(selection.getNode(), 'a'); - - if (tinymce.is(value, 'string')) - value = {href : value}; - - if (!link) { - execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); - each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { - dom.setAttribs(link, value); - }); - } else { - if (value.href) - dom.setAttribs(link, value); - else - ed.dom.remove(link, TRUE); - } - } - }); - - // Add queryCommandState overrides - addCommands({ - // Override justify commands - 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { - return isFormatMatch('align' + command.substring(7)); - }, - - 'Bold,Italic,Underline,Strikethrough' : function(command) { - return isFormatMatch(command); - }, - - mceBlockQuote : function() { - return isFormatMatch('blockquote'); - }, - - Outdent : function() { - var node; - - if (settings.inline_styles) { - if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - - if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) - return TRUE; - } - - return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); - }, - - 'InsertUnorderedList,InsertOrderedList' : function(command) { - return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); - } - }, 'state'); - - // Add queryCommandValue overrides - addCommands({ - 'FontSize,FontName' : function(command) { - var value = 0, parent; - - if (parent = dom.getParent(selection.getNode(), 'span')) { - if (command == 'fontsize') - value = parent.style.fontSize; - else - value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); - } - - return value; - } - }, 'value'); - - // Add undo manager logic - if (settings.custom_undo_redo) { - addCommands({ - Undo : function() { - editor.undoManager.undo(); - }, - - Redo : function() { - editor.undoManager.redo(); - } - }); - } - }; -})(tinymce); -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher; - - tinymce.UndoManager = function(editor) { - var self, index = 0, data = []; - - function getContent() { - return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); - }; - - return self = { - typing : 0, - - onAdd : new Dispatcher(self), - onUndo : new Dispatcher(self), - onRedo : new Dispatcher(self), - - add : function(level) { - var i, settings = editor.settings, lastLevel; - - level = level || {}; - level.content = getContent(); - - // Add undo level if needed - lastLevel = data[index]; - if (lastLevel && lastLevel.content == level.content) { - if (index > 0 || data.length == 1) - return null; - } - - // Time to compress - if (settings.custom_undo_redo_levels) { - if (data.length > settings.custom_undo_redo_levels) { - for (i = 0; i < data.length - 1; i++) - data[i] = data[i + 1]; - - data.length--; - index = data.length; - } - } - - // Get a non intrusive normalized bookmark - level.bookmark = editor.selection.getBookmark(2, true); - - // Crop array if needed - if (index < data.length - 1) { - // Treat first level as initial - if (index == 0) - data = []; - else - data.length = index + 1; - } - - data.push(level); - index = data.length - 1; - - self.onAdd.dispatch(self, level); - editor.isNotDirty = 0; - - return level; - }, - - undo : function() { - var level, i; - - if (self.typing) { - self.add(); - self.typing = 0; - } - - if (index > 0) { - level = data[--index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onUndo.dispatch(self, level); - } - - return level; - }, - - redo : function() { - var level; - - if (index < data.length - 1) { - level = data[++index]; - - editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); - - self.onRedo.dispatch(self, level); - } - - return level; - }, - - clear : function() { - data = []; - index = self.typing = 0; - }, - - hasUndo : function() { - return index > 0 || self.typing; - }, - - hasRedo : function() { - return index < data.length - 1; - } - }; - }; -})(tinymce); - -(function(tinymce) { - // Shorten names - var Event = tinymce.dom.Event, - isIE = tinymce.isIE, - isGecko = tinymce.isGecko, - isOpera = tinymce.isOpera, - each = tinymce.each, - extend = tinymce.extend, - TRUE = true, - FALSE = false; - - // Checks if the selection/caret is at the end of the specified block element - function isAtEnd(rng, par) { - var rng2 = par.ownerDocument.createRange(); - - rng2.setStart(rng.endContainer, rng.endOffset); - rng2.setEndAfter(par); - - // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element - return rng2.cloneContents().textContent.length == 0; - }; - - function isEmpty(n) { - n = n.innerHTML; - - n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars - n = n.replace(/<[^>]+>/g, ''); // Remove all tags - - return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; - }; - - function splitList(selection, dom, li) { - var listBlock, block; - - if (isEmpty(li)) { - listBlock = dom.getParent(li, 'ul,ol'); - - if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { - dom.split(listBlock, li); - block = dom.create('p', 0, '
    '); - dom.replace(block, li); - selection.select(block, 1); - } - - return FALSE; - } - - return TRUE; - }; - - tinymce.create('tinymce.ForceBlocks', { - ForceBlocks : function(ed) { - var t = this, s = ed.settings, elm; - - t.editor = ed; - t.dom = ed.dom; - elm = (s.forced_root_block || 'p').toLowerCase(); - s.element = elm.toUpperCase(); - - ed.onPreInit.add(t.setup, t); - - t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

    <\\\/p>||

    \\s+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

    [\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi'); - t.reBR2Nbsp = new RegExp(']+)>\\s*
    \\s*<\\\/p>|

    \\s*
    \\s*<\\\/p>'.replace(/p/g, elm), 'gi'); - - function padd(ed, o) { - if (isOpera) - o.content = o.content.replace(t.reOpera, ''); - - o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0'); - - if (!isIE && !isOpera && o.set) { - // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
    '); - o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
    '); - } else - o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0'); - }; - - ed.onBeforeSetContent.add(padd); - ed.onPostProcess.add(padd); - - if (s.forced_root_block) { - ed.onInit.add(t.forceRoots, t); - ed.onSetContent.add(t.forceRoots, t); - ed.onBeforeGetContent.add(t.forceRoots, t); - } - }, - - setup : function() { - var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection; - - // Force root blocks when typing and when getting output - if (s.forced_root_block) { - ed.onBeforeExecCommand.add(t.forceRoots, t); - ed.onKeyUp.add(t.forceRoots, t); - ed.onPreProcess.add(t.forceRoots, t); - } - - if (s.force_br_newlines) { - // Force IE to produce BRs on enter - if (isIE) { - ed.onKeyPress.add(function(ed, e) { - var n; - - if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { - selection.setContent('
    ', {format : 'raw'}); - n = dom.get('__'); - n.removeAttribute('id'); - selection.select(n); - selection.collapse(); - return Event.cancel(e); - } - }); - } - } - - if (!isIE && s.force_p_newlines) { - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) - Event.cancel(e); - }); - - if (isGecko) { - ed.onKeyDown.add(function(ed, e) { - if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) - t.backspaceDelete(e, e.keyCode == 8); - }); - } - } - - // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 - if (tinymce.isWebKit) { - function insertBr(ed) { - var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; - - // Insert BR element - rng.insertNode(br = dom.create('br')); - - // Place caret after BR - rng.setStartAfter(br); - rng.setEndAfter(br); - selection.setRng(rng); - - // Could not place caret after BR then insert an nbsp entity and move the caret - if (selection.getSel().focusNode == br.previousSibling) { - selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); - selection.collapse(TRUE); - } - - // Create a temporary DIV after the BR and get the position as it - // seems like getPos() returns 0 for text nodes and BR elements. - dom.insertAfter(div, br); - divYPos = dom.getPos(div).y; - dom.remove(div); - - // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 - if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. - ed.getWin().scrollTo(0, divYPos); - }; - - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) { - insertBr(ed); - Event.cancel(e); - } - }); - } - - // Padd empty inline elements within block elements - // For example:

    becomes

     

    - ed.onPreProcess.add(function(ed, o) { - each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { - if (isEmpty(p)) { - each(dom.select('span,em,strong,b,i', o.node), function(n) { - if (!n.hasChildNodes()) { - n.appendChild(ed.getDoc().createTextNode('\u00a0')); - return FALSE; // Break the loop one padding is enough - } - }); - } - }); - }); - - // IE specific fixes - if (isIE) { - // Replaces IE:s auto generated paragraphs with the specified element name - if (s.element != 'P') { - ed.onKeyPress.add(function(ed, e) { - t.lastElm = selection.getNode().nodeName; - }); - - ed.onKeyUp.add(function(ed, e) { - var bl, n = selection.getNode(), b = ed.getBody(); - - if (b.childNodes.length === 1 && n.nodeName == 'P') { - n = dom.rename(n, s.element); - selection.select(n); - selection.collapse(); - ed.nodeChanged(); - } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { - bl = dom.getParent(n, 'p'); - - if (bl) { - dom.rename(bl, s.element); - ed.nodeChanged(); - } - } - }); - } - } - }, - - find : function(n, t, s) { - var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1; - - while (n = w.nextNode()) { - c++; - - // Index by node - if (t == 0 && n == s) - return c; - - // Node by index - if (t == 1 && c == s) - return n; - } - - return -1; - }, - - forceRoots : function(ed, e) { - var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF; - var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid; - - // Fix for bug #1863847 - //if (e && e.keyCode == 13) - // return TRUE; - - // Wrap non blocks into blocks - for (i = nl.length - 1; i >= 0; i--) { - nx = nl[i]; - - // Ignore internal elements - if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { - bl = null; - continue; - } - - // Is text or non block element - if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) { - if (!bl) { - // Create new block but ignore whitespace - if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { - // Store selection - if (si == -2 && r) { - if (!isIE) { - // If selection is element then mark it - if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { - // Save the id of the selected element - eid = n.getAttribute("id"); - n.setAttribute("id", "__mce"); - } else { - // If element is inside body, might not be the case in contentEdiable mode - if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) { - so = r.startOffset; - eo = r.endOffset; - si = t.find(b, 0, r.startContainer); - ei = t.find(b, 0, r.endContainer); - } - } - } else { - tr = d.body.createTextRange(); - tr.moveToElementText(b); - tr.collapse(1); - bp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(1); - sp = tr.move('character', c) * -1; - - tr = r.duplicate(); - tr.collapse(0); - le = (tr.move('character', c) * -1) - sp; - - si = sp - bp; - ei = le; - } - } - - // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE - // See: http://support.microsoft.com/kb/829907 - bl = ed.dom.create(ed.settings.forced_root_block); - nx.parentNode.replaceChild(bl, nx); - bl.appendChild(nx); - } - } else { - if (bl.hasChildNodes()) - bl.insertBefore(nx, bl.firstChild); - else - bl.appendChild(nx); - } - } else - bl = null; // Time to create new block - } - - // Restore selection - if (si != -2) { - if (!isIE) { - bl = b.getElementsByTagName(ed.settings.element)[0]; - r = d.createRange(); - - // Select last location or generated block - if (si != -1) - r.setStart(t.find(b, 1, si), so); - else - r.setStart(bl, 0); - - // Select last location or generated block - if (ei != -1) - r.setEnd(t.find(b, 1, ei), eo); - else - r.setEnd(bl, 0); - - if (s) { - s.removeAllRanges(); - s.addRange(r); - } - } else { - try { - r = s.createRange(); - r.moveToElementText(b); - r.collapse(1); - r.moveStart('character', si); - r.moveEnd('character', ei); - r.select(); - } catch (ex) { - // Ignore - } - } - } else if (!isIE && (n = ed.dom.get('__mce'))) { - // Restore the id of the selected element - if (eid) - n.setAttribute('id', eid); - else - n.removeAttribute('id'); - - // Move caret before selected element - r = d.createRange(); - r.setStartBefore(n); - r.setEndBefore(n); - se.setRng(r); - } - }, - - getParentBlock : function(n) { - var d = this.dom; - - return d.getParent(n, d.isBlock); - }, - - insertPara : function(e) { - var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; - var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; - - // If root blocks are forced then use Operas default behavior since it's really good -// Removed due to bug: #1853816 -// if (se.forced_root_block && isOpera) -// return TRUE; - - // Setup before range - rb = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - rb.setStart(s.anchorNode, s.anchorOffset); - rb.collapse(TRUE); - - // Setup after range - ra = d.createRange(); - - // If is before the first block element and in body, then move it into first block element - ra.setStart(s.focusNode, s.focusOffset); - ra.collapse(TRUE); - - // Setup start/end points - dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; - sn = dir ? s.anchorNode : s.focusNode; - so = dir ? s.anchorOffset : s.focusOffset; - en = dir ? s.focusNode : s.anchorNode; - eo = dir ? s.focusOffset : s.anchorOffset; - - // If selection is in empty table cell - if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { - if (sn.firstChild.nodeName == 'BR') - dom.remove(sn.firstChild); // Remove BR - - // Create two new block elements - if (sn.childNodes.length == 0) { - ed.dom.add(sn, se.element, null, '
    '); - aft = ed.dom.add(sn, se.element, null, '
    '); - } else { - n = sn.innerHTML; - sn.innerHTML = ''; - ed.dom.add(sn, se.element, null, n); - aft = ed.dom.add(sn, se.element, null, '
    '); - } - - // Move caret into the last one - r = d.createRange(); - r.selectNodeContents(aft); - r.collapse(1); - ed.selection.setRng(r); - - return FALSE; - } - - // If the caret is in an invalid location in FF we need to move it into the first block - if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { - sn = en = sn.firstChild; - so = eo = 0; - rb = d.createRange(); - rb.setStart(sn, 0); - ra = d.createRange(); - ra.setStart(en, 0); - } - - // Never use body as start or end node - sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - sn = sn.nodeName == "BODY" ? sn.firstChild : sn; - en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes - en = en.nodeName == "BODY" ? en.firstChild : en; - - // Get start and end blocks - sb = t.getParentBlock(sn); - eb = t.getParentBlock(en); - bn = sb ? sb.nodeName : se.element; // Get block name to create - - // Return inside list use default browser behavior - if (n = t.dom.getParent(sb, 'li,pre')) { - if (n.nodeName == 'LI') - return splitList(ed.selection, t.dom, n); - - return TRUE; - } - - // If caption or absolute layers then always generate new blocks within - if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - sb = null; - } - - // If caption or absolute layers then always generate new blocks within - if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { - bn = se.element; - eb = null; - } - - // Use P instead - if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { - bn = se.element; - sb = eb = null; - } - - // Setup new before and after blocks - bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); - aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); - - // Remove id from after clone - aft.removeAttribute('id'); - - // Is header and cursor is at the end, then force paragraph under - if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) - aft = ed.dom.create(se.element); - - // Find start chop node - n = sc = sn; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - sc = n; - } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); - - // Find end chop node - n = ec = en; - do { - if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) - break; - - ec = n; - } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); - - // Place first chop part into before block element - if (sc.nodeName == bn) - rb.setStart(sc, 0); - else - rb.setStartBefore(sc); - - rb.setEnd(sn, so); - bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Place secnd chop part within new block element - try { - ra.setEndAfter(ec); - } catch(ex) { - //console.debug(s.focusNode, s.focusOffset); - } - - ra.setStart(en, eo); - aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari - - // Create range around everything - r = d.createRange(); - if (!sc.previousSibling && sc.parentNode.nodeName == bn) { - r.setStartBefore(sc.parentNode); - } else { - if (rb.startContainer.nodeName == bn && rb.startOffset == 0) - r.setStartBefore(rb.startContainer); - else - r.setStart(rb.startContainer, rb.startOffset); - } - - if (!ec.nextSibling && ec.parentNode.nodeName == bn) - r.setEndAfter(ec.parentNode); - else - r.setEnd(ra.endContainer, ra.endOffset); - - // Delete and replace it with new block elements - r.deleteContents(); - - if (isOpera) - ed.getWin().scrollTo(0, vp.y); - - // Never wrap blocks in blocks - if (bef.firstChild && bef.firstChild.nodeName == bn) - bef.innerHTML = bef.firstChild.innerHTML; - - if (aft.firstChild && aft.firstChild.nodeName == bn) - aft.innerHTML = aft.firstChild.innerHTML; - - // Padd empty blocks - if (isEmpty(bef)) - bef.innerHTML = '
    '; - - function appendStyles(e, en) { - var nl = [], nn, n, i; - - e.innerHTML = ''; - - // Make clones of style elements - if (se.keep_styles) { - n = en; - do { - // We only want style specific elements - if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { - nn = n.cloneNode(FALSE); - dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique - nl.push(nn); - } - } while (n = n.parentNode); - } - - // Append style elements to aft - if (nl.length > 0) { - for (i = nl.length - 1, nn = e; i >= 0; i--) - nn = nn.appendChild(nl[i]); - - // Padd most inner style element - nl[0].innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - return nl[0]; // Move caret to most inner element - } else - e.innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there - }; - - // Fill empty afterblook with current style - if (isEmpty(aft)) - car = appendStyles(aft, en); - - // Opera needs this one backwards for older versions - if (isOpera && parseFloat(opera.version()) < 9.5) { - r.insertNode(bef); - r.insertNode(aft); - } else { - r.insertNode(aft); - r.insertNode(bef); - } - - // Normalize - aft.normalize(); - bef.normalize(); - - function first(n) { - return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n; - }; - - // Move cursor and scroll into view - r = d.createRange(); - r.selectNodeContents(isGecko ? first(car || aft) : car || aft); - r.collapse(1); - s.removeAllRanges(); - s.addRange(r); - - // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs - y = ed.dom.getPos(aft).y; - ch = aft.clientHeight; - - // Is element within viewport - if (y < vp.y || y + ch > vp.y + vp.h) { - ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks - //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); - } - - return FALSE; - }, - - backspaceDelete : function(e, bs) { - var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn; - - // The caret sometimes gets stuck in Gecko if you delete empty paragraphs - // This workaround removes the element by hand and moves the caret to the previous element - if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { - if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { - // Find previous block element - n = sc; - while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; - - if (n) { - if (sc != b.firstChild) { - // Find last text node - w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); - while (tn = w.nextNode()) - n = tn; - - // Place caret at the end of last text node - r = ed.getDoc().createRange(); - r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); - r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); - se.setRng(r); - - // Remove the target container - ed.dom.remove(sc); - } - - return Event.cancel(e); - } - } - } - - // Gecko generates BR elements here and there, we don't like those so lets remove them - function handler(e) { - var pr; - - e = e.target; - - // A new BR was created in a block element, remove it - if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) { - pr = e.previousSibling; - - Event.remove(b, 'DOMNodeInserted', handler); - - // Is there whitespace at the end of the node before then we might need the pesky BR - // to place the caret at a correct location see bug: #2013943 - if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue)) - return; - - // Only remove BR elements that got inserted in the middle of the text - if (e.previousSibling || e.nextSibling) - ed.dom.remove(e); - } - }; - - // Listen for new nodes - Event._add(b, 'DOMNodeInserted', handler); - - // Remove listener - window.setTimeout(function() { - Event._remove(b, 'DOMNodeInserted', handler); - }, 1); - } - }); -})(tinymce); - -(function(tinymce) { - // Shorten names - var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; - - tinymce.create('tinymce.ControlManager', { - ControlManager : function(ed, s) { - var t = this, i; - - s = s || {}; - t.editor = ed; - t.controls = {}; - t.onAdd = new tinymce.util.Dispatcher(t); - t.onPostRender = new tinymce.util.Dispatcher(t); - t.prefix = s.prefix || ed.id + '_'; - t._cls = {}; - - t.onPostRender.add(function() { - each(t.controls, function(c) { - c.postRender(); - }); - }); - }, - - get : function(id) { - return this.controls[this.prefix + id] || this.controls[id]; - }, - - setActive : function(id, s) { - var c = null; - - if (c = this.get(id)) - c.setActive(s); - - return c; - }, - - setDisabled : function(id, s) { - var c = null; - - if (c = this.get(id)) - c.setDisabled(s); - - return c; - }, - - add : function(c) { - var t = this; - - if (c) { - t.controls[c.id] = c; - t.onAdd.dispatch(c, t); - } - - return c; - }, - - createControl : function(n) { - var c, t = this, ed = t.editor; - - each(ed.plugins, function(p) { - if (p.createControl) { - c = p.createControl(n, t); - - if (c) - return false; - } - }); - - switch (n) { - case "|": - case "separator": - return t.createSeparator(); - } - - if (!c && ed.buttons && (c = ed.buttons[n])) - return t.createButton(n, c); - - return t.add(c); - }, - - createDropMenu : function(id, s, cc) { - var t = this, ed = t.editor, c, bm, v, cls; - - s = extend({ - 'class' : 'mceDropDown', - constrain : ed.settings.constrain_menus - }, s); - - s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; - if (v = ed.getParam('skin_variant')) - s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); - - id = t.prefix + id; - cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; - c = t.controls[id] = new cls(id, s); - c.onAddItem.add(function(c, o) { - var s = o.settings; - - s.title = ed.getLang(s.title, s.title); - - if (!s.onclick) { - s.onclick = function(v) { - if (s.cmd) - ed.execCommand(s.cmd, s.ui || false, s.value); - }; - } - }); - - ed.onRemove.add(function() { - c.destroy(); - }); - - // Fix for bug #1897785, #1898007 - if (tinymce.isIE) { - c.onShowMenu.add(function() { - // IE 8 needs focus in order to store away a range with the current collapsed caret location - ed.focus(); - - bm = ed.selection.getBookmark(1); - }); - - c.onHideMenu.add(function() { - if (bm) { - ed.selection.moveToBookmark(bm); - bm = 0; - } - }); - } - - return t.add(c); - }, - - createListBox : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - - if (ed.settings.use_native_selects) - c = new tinymce.ui.NativeListBox(id, s); - else { - cls = cc || t._cls.listbox || tinymce.ui.ListBox; - c = new cls(id, s); - } - - t.controls[id] = c; - - // Fix focus problem in Safari - if (tinymce.isWebKit) { - c.onPostRender.add(function(c, n) { - // Store bookmark on mousedown - Event.add(n, 'mousedown', function() { - ed.bookmark = ed.selection.getBookmark(1); - }); - - // Restore on focus, since it might be lost - Event.add(n, 'focus', function() { - ed.selection.moveToBookmark(ed.bookmark); - ed.bookmark = null; - }); - }); - } - - if (c.hideMenu) - ed.onMouseDown.add(c.hideMenu, c); - - return t.add(c); - }, - - createButton : function(id, s, cc) { - var t = this, ed = t.editor, o, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.label = ed.translate(s.label); - s.scope = s.scope || ed; - - if (!s.onclick && !s.menu_button) { - s.onclick = function() { - ed.execCommand(s.cmd, s.ui || false, s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - unavailable_prefix : ed.getLang('unavailable', ''), - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - - if (s.menu_button) { - cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; - c = new cls(id, s); - ed.onMouseDown.add(c.hideMenu, c); - } else { - cls = t._cls.button || tinymce.ui.Button; - c = new cls(id, s); - } - - return t.add(c); - }, - - createMenuButton : function(id, s, cc) { - s = s || {}; - s.menu_button = 1; - - return this.createButton(id, s, cc); - }, - - createSplitButton : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onclick) { - s.onclick = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - scope : s.scope, - control_manager : t - }, s); - - id = t.prefix + id; - cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; - c = t.add(new cls(id, s)); - ed.onMouseDown.add(c.hideMenu, c); - - return c; - }, - - createColorSplitButton : function(id, s, cc) { - var t = this, ed = t.editor, cmd, c, cls, bm; - - if (t.get(id)) - return null; - - s.title = ed.translate(s.title); - s.scope = s.scope || ed; - - if (!s.onclick) { - s.onclick = function(v) { - if (tinymce.isIE) - bm = ed.selection.getBookmark(1); - - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - if (!s.onselect) { - s.onselect = function(v) { - ed.execCommand(s.cmd, s.ui || false, v || s.value); - }; - } - - s = extend({ - title : s.title, - 'class' : 'mce_' + id, - 'menu_class' : ed.getParam('skin') + 'Skin', - scope : s.scope, - more_colors_title : ed.getLang('more_colors') - }, s); - - id = t.prefix + id; - cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; - c = new cls(id, s); - ed.onMouseDown.add(c.hideMenu, c); - - // Remove the menu element when the editor is removed - ed.onRemove.add(function() { - c.destroy(); - }); - - // Fix for bug #1897785, #1898007 - if (tinymce.isIE) { - c.onShowMenu.add(function() { - // IE 8 needs focus in order to store away a range with the current collapsed caret location - ed.focus(); - bm = ed.selection.getBookmark(1); - }); - - c.onHideMenu.add(function() { - if (bm) { - ed.selection.moveToBookmark(bm); - bm = 0; - } - }); - } - - return t.add(c); - }, - - createToolbar : function(id, s, cc) { - var c, t = this, cls; - - id = t.prefix + id; - cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; - c = new cls(id, s); - - if (t.get(id)) - return null; - - return t.add(c); - }, - - createSeparator : function(cc) { - var cls = cc || this._cls.separator || tinymce.ui.Separator; - - return new cls(); - }, - - setControlType : function(n, c) { - return this._cls[n.toLowerCase()] = c; - }, - - destroy : function() { - each(this.controls, function(c) { - c.destroy(); - }); - - this.controls = null; - } - }); -})(tinymce); - -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; - - tinymce.create('tinymce.WindowManager', { - WindowManager : function(ed) { - var t = this; - - t.editor = ed; - t.onOpen = new Dispatcher(t); - t.onClose = new Dispatcher(t); - t.params = {}; - t.features = {}; - }, - - open : function(s, p) { - var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; - - // Default some options - s = s || {}; - p = p || {}; - sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window - sh = isOpera ? vp.h : screen.height; - s.name = s.name || 'mc_' + new Date().getTime(); - s.width = parseInt(s.width || 320); - s.height = parseInt(s.height || 240); - s.resizable = true; - s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); - s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); - p.inline = false; - p.mce_width = s.width; - p.mce_height = s.height; - p.mce_auto_focus = s.auto_focus; - - if (mo) { - if (isIE) { - s.center = true; - s.help = false; - s.dialogWidth = s.width + 'px'; - s.dialogHeight = s.height + 'px'; - s.scroll = s.scrollbars || false; - } - } - - // Build features string - each(s, function(v, k) { - if (tinymce.is(v, 'boolean')) - v = v ? 'yes' : 'no'; - - if (!/^(name|url)$/.test(k)) { - if (isIE && mo) - f += (f ? ';' : '') + k + ':' + v; - else - f += (f ? ',' : '') + k + '=' + v; - } - }); - - t.features = s; - t.params = p; - t.onOpen.dispatch(t, s, p); - - u = s.url || s.file; - u = tinymce._addVer(u); - - try { - if (isIE && mo) { - w = 1; - window.showModalDialog(u, window, f); - } else - w = window.open(u, s.name, f); - } catch (ex) { - // Ignore - } - - if (!w) - alert(t.editor.getLang('popup_blocked')); - }, - - close : function(w) { - w.close(); - this.onClose.dispatch(this); - }, - - createInstance : function(cl, a, b, c, d, e) { - var f = tinymce.resolve(cl); - - return new f(a, b, c, d, e); - }, - - confirm : function(t, cb, s, w) { - w = w || window; - - cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); - }, - - alert : function(tx, cb, s, w) { - var t = this; - - w = w || window; - w.alert(t._decode(t.editor.getLang(tx, tx))); - - if (cb) - cb.call(s || t); - }, - - resizeBy : function(dw, dh, win) { - win.resizeBy(dw, dh); - }, - - // Internal functions - - _decode : function(s) { - return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); - } - }); -}(tinymce)); -(function(tinymce) { - function CommandManager() { - var execCommands = {}, queryStateCommands = {}, queryValueCommands = {}; - - function add(collection, cmd, func, scope) { - if (typeof(cmd) == 'string') - cmd = [cmd]; - - tinymce.each(cmd, function(cmd) { - collection[cmd.toLowerCase()] = {func : func, scope : scope}; - }); - }; - - tinymce.extend(this, { - add : function(cmd, func, scope) { - add(execCommands, cmd, func, scope); - }, - - addQueryStateHandler : function(cmd, func, scope) { - add(queryStateCommands, cmd, func, scope); - }, - - addQueryValueHandler : function(cmd, func, scope) { - add(queryValueCommands, cmd, func, scope); - }, - - execCommand : function(scope, cmd, ui, value, args) { - if (cmd = execCommands[cmd.toLowerCase()]) { - if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false) - return true; - } - }, - - queryCommandValue : function() { - if (cmd = queryValueCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - }, - - queryCommandState : function() { - if (cmd = queryStateCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - } - }); - }; - - tinymce.GlobalCommands = new CommandManager(); -})(tinymce); -(function(tinymce) { - tinymce.Formatter = function(ed) { - var formats = {}, - each = tinymce.each, - dom = ed.dom, - selection = ed.selection, - TreeWalker = tinymce.dom.TreeWalker, - rangeUtils = new tinymce.dom.RangeUtils(dom), - isValid = ed.schema.isValid, - isBlock = dom.isBlock, - forcedRootBlock = ed.settings.forced_root_block, - nodeIndex = dom.nodeIndex, - INVISIBLE_CHAR = '\uFEFF', - MCE_ATTR_RE = /^(src|href|style)$/, - FALSE = false, - TRUE = true, - undefined, - pendingFormats = {apply : [], remove : []}; - - function isArray(obj) { - return obj instanceof Array; - }; - - function getParents(node, selector) { - return dom.getParents(node, selector, dom.getRoot()); - }; - - function isCaretNode(node) { - return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); - }; - - // Public functions - - function get(name) { - return name ? formats[name] : formats; - }; - - function register(name, format) { - if (name) { - if (typeof(name) !== 'string') { - each(name, function(format, name) { - register(name, format); - }); - } else { - // Force format into array and add it to internal collection - format = format.length ? format : [format]; - - each(format, function(format) { - // Set deep to false by default on selector formats this to avoid removing - // alignment on images inside paragraphs when alignment is changed on paragraphs - if (format.deep === undefined) - format.deep = !format.selector; - - // Default to true - if (format.split === undefined) - format.split = !format.selector || format.inline; - - // Default to true - if (format.remove === undefined && format.selector && !format.inline) - format.remove = 'none'; - - // Mark format as a mixed format inline + block level - if (format.selector && format.inline) { - format.mixed = true; - format.block_expand = true; - } - - // Split classes if needed - if (typeof(format.classes) === 'string') - format.classes = format.classes.split(/\s+/); - }); - - formats[name] = format; - } - } - }; - - function apply(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, rng, i; - - function moveStart(rng) { - var container = rng.startContainer, - offset = rng.startOffset, - walker, node; - - // Move startContainer/startOffset in to a suitable node - if (container.nodeType == 1 || container.nodeValue === "") { - walker = new TreeWalker(container.childNodes[offset]); - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { - rng.setStart(node, 0); - break; - } - } - } - - return rng; - }; - - function setElementFormat(elm, fmt) { - fmt = fmt || format; - - if (elm) { - each(fmt.styles, function(value, name) { - dom.setStyle(elm, name, replaceVars(value, vars)); - }); - - each(fmt.attributes, function(value, name) { - dom.setAttrib(elm, name, replaceVars(value, vars)); - }); - - each(fmt.classes, function(value) { - value = replaceVars(value, vars); - - if (!dom.hasClass(elm, value)) - dom.addClass(elm, value); - }); - } - }; - - function applyRngStyle(rng) { - var newWrappers = [], wrapName, wrapElm; - - // Setup wrapper element - wrapName = format.inline || format.block; - wrapElm = dom.create(wrapName); - setElementFormat(wrapElm); - - rangeUtils.walk(rng, function(nodes) { - var currentWrapElm; - - function process(node) { - var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; - - // Stop wrapping on br elements - if (isEq(nodeName, 'br')) { - currentWrapElm = 0; - - // Remove any br elements when we wrap things - if (format.block) - dom.remove(node); - - return; - } - - // If node is wrapper type - if (format.wrapper && matchNode(node, name, vars)) { - currentWrapElm = 0; - return; - } - - // Can we rename the block - if (format.block && !format.wrapper && isTextBlock(nodeName)) { - node = dom.rename(node, wrapName); - setElementFormat(node); - newWrappers.push(node); - currentWrapElm = 0; - return; - } - - // Handle selector patterns - if (format.selector) { - // Look for matching formats - each(formatList, function(format) { - if (dom.is(node, format.selector) && !isCaretNode(node)) { - setElementFormat(node, format); - found = true; - } - }); - - // Contine processing if a selector match wasn't found and a inline element is defined - if (!format.inline || found) { - currentWrapElm = 0; - return; - } - } - - // Is it valid to wrap this item - if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { - // Start wrapping - if (!currentWrapElm) { - // Wrap the node - currentWrapElm = wrapElm.cloneNode(FALSE); - node.parentNode.insertBefore(currentWrapElm, node); - newWrappers.push(currentWrapElm); - } - - currentWrapElm.appendChild(node); - } else { - // Start a new wrapper for possible children - currentWrapElm = 0; - - each(tinymce.grep(node.childNodes), process); - - // End the last wrapper - currentWrapElm = 0; - } - }; - - // Process siblings from range - each(nodes, process); - }); - - // Cleanup - each(newWrappers, function(node) { - var childCount; - - function getChildCount(node) { - var count = 0; - - each(node.childNodes, function(node) { - if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) - count++; - }); - - return count; - }; - - function mergeStyles(node) { - var child, clone; - - each(node.childNodes, function(node) { - if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { - child = node; - return FALSE; // break loop - } - }); - - // If child was found and of the same type as the current node - if (child && matchName(child, format)) { - clone = child.cloneNode(FALSE); - setElementFormat(clone); - - dom.replace(clone, node, TRUE); - dom.remove(child, 1); - } - - return clone || node; - }; - - childCount = getChildCount(node); - - // Remove empty nodes - if (childCount === 0) { - dom.remove(node, 1); - return; - } - - if (format.inline || format.wrapper) { - // Merges the current node with it's children of similar type to reduce the number of elements - if (!format.exact && childCount === 1) - node = mergeStyles(node); - - // Remove/merge children - each(formatList, function(format) { - // Merge all children of similar type will move styles from child to parent - // this: text - // will become: text - each(dom.select(format.inline, node), function(child) { - removeFormat(format, vars, child, format.exact ? child : null); - }); - }); - - // Look for parent with similar style format - dom.getParent(node.parentNode, function(parent) { - if (matchNode(parent, name, vars)) { - dom.remove(node, 1); - node = 0; - return TRUE; - } - }); - - // Merge next and previous siblings if they are similar texttext becomes texttext - if (node) { - node = mergeSiblings(getNonWhiteSpaceSibling(node), node); - node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); - } - } - }); - }; - - if (format) { - if (node) { - rng = dom.createRng(); - - rng.setStartBefore(node); - rng.setEndAfter(node); - - applyRngStyle(rng); - } else { - if (!selection.isCollapsed() || !format.inline) { - // Apply formatting to selection - bookmark = selection.getBookmark(); - applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); - - selection.moveToBookmark(bookmark); - selection.setRng(moveStart(selection.getRng(TRUE))); - ed.nodeChanged(); - } else - performCaretAction('apply', name, vars); - } - } - }; - - function remove(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, i, rng; - - // Merges the styles for each node - function process(node) { - var children, i, l; - - // Grab the children first since the nodelist might be changed - children = tinymce.grep(node.childNodes); - - // Process current node - for (i = 0, l = formatList.length; i < l; i++) { - if (removeFormat(formatList[i], vars, node, node)) - break; - } - - // Process the children - if (format.deep) { - for (i = 0, l = children.length; i < l; i++) - process(children[i]); - } - }; - - function findFormatRoot(container) { - var formatRoot; - - // Find format root - each(getParents(container.parentNode).reverse(), function(parent) { - var format; - - // Find format root element - if (!formatRoot && parent.id != '_start' && parent.id != '_end') { - // Is the node matching the format we are looking for - format = matchNode(parent, name, vars); - if (format && format.split !== false) - formatRoot = parent; - } - }); - - return formatRoot; - }; - - function wrapAndSplit(format_root, container, target, split) { - var parent, clone, lastClone, firstClone, i, formatRootParent; - - // Format root found then clone formats and split it - if (format_root) { - formatRootParent = format_root.parentNode; - - for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { - clone = parent.cloneNode(FALSE); - - for (i = 0; i < formatList.length; i++) { - if (removeFormat(formatList[i], vars, clone, clone)) { - clone = 0; - break; - } - } - - // Build wrapper node - if (clone) { - if (lastClone) - clone.appendChild(lastClone); - - if (!firstClone) - firstClone = clone; - - lastClone = clone; - } - } - - // Never split block elements if the format is mixed - if (split && (!format.mixed || !isBlock(format_root))) - container = dom.split(format_root, container); - - // Wrap container in cloned formats - if (lastClone) { - target.parentNode.insertBefore(lastClone, target); - firstClone.appendChild(target); - } - } - - return container; - }; - - function splitToFormatRoot(container) { - return wrapAndSplit(findFormatRoot(container), container, container, true); - }; - - function unwrap(start) { - var node = dom.get(start ? '_start' : '_end'), - out = node[start ? 'firstChild' : 'lastChild']; - - dom.remove(node, 1); - - return out; - }; - - function removeRngStyle(rng) { - var startContainer, endContainer; - - rng = expandRng(rng, formatList, TRUE); - - if (format.split) { - startContainer = getContainer(rng, TRUE); - endContainer = getContainer(rng); - - if (startContainer != endContainer) { - // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); - - // Split start/end - splitToFormatRoot(startContainer); - splitToFormatRoot(endContainer); - - // Unwrap start/end to get real elements again - startContainer = unwrap(TRUE); - endContainer = unwrap(); - } else - startContainer = endContainer = splitToFormatRoot(startContainer); - - // Update range positions since they might have changed after the split operations - rng.startContainer = startContainer.parentNode; - rng.startOffset = nodeIndex(startContainer); - rng.endContainer = endContainer.parentNode; - rng.endOffset = nodeIndex(endContainer) + 1; - } - - // Remove items between start/end - rangeUtils.walk(rng, function(nodes) { - each(nodes, function(node) { - process(node); - }); - }); - }; - - // Handle node - if (node) { - rng = dom.createRng(); - rng.setStartBefore(node); - rng.setEndAfter(node); - removeRngStyle(rng); - return; - } - - if (!selection.isCollapsed() || !format.inline) { - bookmark = selection.getBookmark(); - removeRngStyle(selection.getRng(TRUE)); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } else - performCaretAction('remove', name, vars); - }; - - function toggle(name, vars, node) { - if (match(name, vars, node)) - remove(name, vars, node); - else - apply(name, vars, node); - }; - - function matchNode(node, name, vars) { - var formatList = get(name), format, i, classes; - - function matchItems(node, format, item_name) { - var key, value, items = format[item_name], i; - - // Check all items - if (items) { - // Non indexed object - if (items.length === undefined) { - for (key in items) { - if (items.hasOwnProperty(key)) { - if (item_name === 'attributes') - value = dom.getAttrib(node, key); - else - value = getStyle(node, key); - - if (!isEq(value, replaceVars(items[key], vars))) - return; - } - } - } else { - // Only one match needed for indexed arrays - for (i = 0; i < items.length; i++) { - if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) - return format; - } - } - } - - return format; - }; - - if (formatList && node) { - // Check each format in list - for (i = 0; i < formatList.length; i++) { - format = formatList[i]; - - // Name name, attributes, styles and classes - if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { - // Match classes - if (classes = format.classes) { - for (i = 0; i < classes.length; i++) { - if (!dom.hasClass(node, classes[i])) - return; - } - } - - return format; - } - } - } - }; - - function match(name, vars, node) { - var startNode, i; - - function matchParents(node) { - // Find first node with similar format settings - node = dom.getParent(node, function(node) { - return !!matchNode(node, name, vars); - }); - - // Do an exact check on the similar format element - return matchNode(node, name, vars); - }; - - // Check specified node - if (node) - return matchParents(node); - - // Check pending formats - if (selection.isCollapsed()) { - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - if (pendingFormats.apply[i].name == name) - return true; - } - - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - if (pendingFormats.remove[i].name == name) - return false; - } - - return matchParents(selection.getNode()); - } - - // Check selected node - node = selection.getNode(); - if (matchParents(node)) - return TRUE; - - // Check start node if it's different - startNode = selection.getStart(); - if (startNode != node) { - if (matchParents(startNode)) - return TRUE; - } - - return FALSE; - }; - - function matchAll(names, vars) { - var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; - - // If the selection is collapsed then check pending formats - if (selection.isCollapsed()) { - for (ni = 0; ni < names.length; ni++) { - // If the name is to be removed, then stop it from being added - for (i = pendingFormats.remove.length - 1; i >= 0; i--) { - name = names[ni]; - - if (pendingFormats.remove[i].name == name) { - checkedMap[name] = true; - break; - } - } - } - - // If the format is to be applied - for (i = pendingFormats.apply.length - 1; i >= 0; i--) { - for (ni = 0; ni < names.length; ni++) { - name = names[ni]; - - if (!checkedMap[name] && pendingFormats.apply[i].name == name) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - } - } - - // Check start of selection for formats - startElement = selection.getStart(); - dom.getParent(startElement, function(node) { - var i, name; - - for (i = 0; i < names.length; i++) { - name = names[i]; - - if (!checkedMap[name] && matchNode(node, name, vars)) { - checkedMap[name] = true; - matchedFormatNames.push(name); - } - } - }); - - return matchedFormatNames; - }; - - function canApply(name) { - var formatList = get(name), startNode, parents, i, x, selector; - - if (formatList) { - startNode = selection.getStart(); - parents = getParents(startNode); - - for (x = formatList.length - 1; x >= 0; x--) { - selector = formatList[x].selector; - - // Format is not selector based, then always return TRUE - if (!selector) - return TRUE; - - for (i = parents.length - 1; i >= 0; i--) { - if (dom.is(parents[i], selector)) - return TRUE; - } - } - } - - return FALSE; - }; - - // Expose to public - tinymce.extend(this, { - get : get, - register : register, - apply : apply, - remove : remove, - toggle : toggle, - match : match, - matchAll : matchAll, - matchNode : matchNode, - canApply : canApply - }); - - // Private functions - - function matchName(node, format) { - // Check for inline match - if (isEq(node, format.inline)) - return TRUE; - - // Check for block match - if (isEq(node, format.block)) - return TRUE; - - // Check for selector match - if (format.selector) - return dom.is(node, format.selector); - }; - - function isEq(str1, str2) { - str1 = str1 || ''; - str2 = str2 || ''; - - str1 = '' + (str1.nodeName || str1); - str2 = '' + (str2.nodeName || str2); - - return str1.toLowerCase() == str2.toLowerCase(); - }; - - function getStyle(node, name) { - var styleVal = dom.getStyle(node, name); - - // Force the format to hex - if (name == 'color' || name == 'backgroundColor') - styleVal = dom.toHex(styleVal); - - // Opera will return bold as 700 - if (name == 'fontWeight' && styleVal == 700) - styleVal = 'bold'; - - return '' + styleVal; - }; - - function replaceVars(value, vars) { - if (typeof(value) != "string") - value = value(vars); - else if (vars) { - value = value.replace(/%(\w+)/g, function(str, name) { - return vars[name] || str; - }); - } - - return value; - }; - - function isWhiteSpaceNode(node) { - return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue); - }; - - function wrap(node, name, attrs) { - var wrapper = dom.create(name, attrs); - - node.parentNode.insertBefore(wrapper, node); - wrapper.appendChild(node); - - return wrapper; - }; - - function expandRng(rng, format, remove) { - var startContainer = rng.startContainer, - startOffset = rng.startOffset, - endContainer = rng.endContainer, - endOffset = rng.endOffset, sibling, lastIdx; - - // This function walks up the tree if there is no siblings before/after the node - function findParentContainer(container, child_name, sibling_name, root) { - var parent, child; - - root = root || dom.getRoot(); - - for (;;) { - // Check if we can move up are we at root level or body level - parent = container.parentNode; - - // Stop expanding on block elements or root depending on format - if (parent == root || (!format[0].block_expand && isBlock(parent))) - return container; - - for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return container; - - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return container; - } - - container = container.parentNode; - } - - return container; - }; - - // If index based start position then resolve it - if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { - lastIdx = startContainer.childNodes.length - 1; - startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; - - if (startContainer.nodeType == 3) - startOffset = 0; - } - - // If index based end position then resolve it - if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { - lastIdx = endContainer.childNodes.length - 1; - endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; - - if (endContainer.nodeType == 3) - endOffset = endContainer.nodeValue.length; - } - - // Exclude bookmark nodes if possible - if (isBookmarkNode(startContainer.parentNode)) - startContainer = startContainer.parentNode; - - if (isBookmarkNode(startContainer)) - startContainer = startContainer.nextSibling || startContainer; - - if (isBookmarkNode(endContainer.parentNode)) - endContainer = endContainer.parentNode; - - if (isBookmarkNode(endContainer)) - endContainer = endContainer.previousSibling || endContainer; - - // Move start/end point up the tree if the leaves are sharp and if we are in different containers - // Example * becomes !: !

    *texttext*

    ! - // This will reduce the number of wrapper elements that needs to be created - // Move start point up the tree - if (format[0].inline || format[0].block_expand) { - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - - // Expand start/end container to matching selector - if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { - function findSelectorEndPoint(container, sibling_name) { - var parents, i, y; - - if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) - container = container[sibling_name]; - - parents = getParents(container); - for (i = 0; i < parents.length; i++) { - for (y = 0; y < format.length; y++) { - if (dom.is(parents[i], format[y].selector)) - return parents[i]; - } - } - - return container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); - endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); - } - - // Expand start/end container to matching block element or text node - if (format[0].block || format[0].selector) { - function findBlockEndPoint(container, sibling_name, sibling_name2) { - var node; - - // Expand to block of similar type - if (!format[0].wrapper) - node = dom.getParent(container, format[0].block); - - // Expand to first wrappable block element or any block element - if (!node) - node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); - - // Exclude inner lists from wrapping - if (node && format[0].wrapper) - node = getParents(node, 'ul,ol').reverse()[0] || node; - - // Didn't find a block element look for first/last wrappable element - if (!node) { - node = container; - - while (node[sibling_name] && !isBlock(node[sibling_name])) { - node = node[sibling_name]; - - // Break on BR but include it will be removed later on - // we can't remove it now since we need to check if it can be wrapped - if (isEq(node, 'br')) - break; - } - } - - return node || container; - }; - - // Find new startContainer/endContainer if there is better one - startContainer = findBlockEndPoint(startContainer, 'previousSibling'); - endContainer = findBlockEndPoint(endContainer, 'nextSibling'); - - // Non block element then try to expand up the leaf - if (format[0].block) { - if (!isBlock(startContainer)) - startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling'); - - if (!isBlock(endContainer)) - endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling'); - } - } - - // Setup index for startContainer - if (startContainer.nodeType == 1) { - startOffset = nodeIndex(startContainer); - startContainer = startContainer.parentNode; - } - - // Setup index for endContainer - if (endContainer.nodeType == 1) { - endOffset = nodeIndex(endContainer) + 1; - endContainer = endContainer.parentNode; - } - - // Return new range like object - return { - startContainer : startContainer, - startOffset : startOffset, - endContainer : endContainer, - endOffset : endOffset - }; - } - - function removeFormat(format, vars, node, compare_node) { - var i, attrs, stylesModified; - - // Check if node matches format - if (!matchName(node, format)) - return FALSE; - - // Should we compare with format attribs and styles - if (format.remove != 'all') { - // Remove styles - each(format.styles, function(value, name) { - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(getStyle(compare_node, name), value)) - dom.setStyle(node, name, ''); - - stylesModified = 1; - }); - - // Remove style attribute if it's empty - if (stylesModified && dom.getAttrib(node, 'style') == '') { - node.removeAttribute('style'); - node.removeAttribute('_mce_style'); - } - - // Remove attributes - each(format.attributes, function(value, name) { - var valueOut; - - value = replaceVars(value, vars); - - // Indexed array - if (typeof(name) === 'number') { - name = value; - compare_node = 0; - } - - if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { - // Keep internal classes - if (name == 'class') { - value = dom.getAttrib(node, name); - if (value) { - // Build new class value where everything is removed except the internal prefixed classes - valueOut = ''; - each(value.split(/\s+/), function(cls) { - if (/mce\w+/.test(cls)) - valueOut += (valueOut ? ' ' : '') + cls; - }); - - // We got some internal classes left - if (valueOut) { - dom.setAttrib(node, name, valueOut); - return; - } - } - } - - // IE6 has a bug where the attribute doesn't get removed correctly - if (name == "class") - node.removeAttribute('className'); - - // Remove mce prefixed attributes - if (MCE_ATTR_RE.test(name)) - node.removeAttribute('_mce_' + name); - - node.removeAttribute(name); - } - }); - - // Remove classes - each(format.classes, function(value) { - value = replaceVars(value, vars); - - if (!compare_node || dom.hasClass(compare_node, value)) - dom.removeClass(node, value); - }); - - // Check for non internal attributes - attrs = dom.getAttribs(node); - for (i = 0; i < attrs.length; i++) { - if (attrs[i].nodeName.indexOf('_') !== 0) - return FALSE; - } - } - - // Remove the inline child if it's empty for example or - if (format.remove != 'none') { - removeNode(node, format); - return TRUE; - } - }; - - function removeNode(node, format) { - var parentNode = node.parentNode, rootBlockElm; - - if (format.block) { - if (!forcedRootBlock) { - function find(node, next, inc) { - node = getNonWhiteSpaceSibling(node, next, inc); - - return !node || (node.nodeName == 'BR' || isBlock(node)); - }; - - // Append BR elements if needed before we remove the block - if (isBlock(node) && !isBlock(parentNode)) { - if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) - node.insertBefore(dom.create('br'), node.firstChild); - - if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) - node.appendChild(dom.create('br')); - } - } else { - // Wrap the block in a forcedRootBlock if we are at the root of document - if (parentNode == dom.getRoot()) { - if (!format.list_block || !isEq(node, format.list_block)) { - each(tinymce.grep(node.childNodes), function(node) { - if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { - if (!rootBlockElm) - rootBlockElm = wrap(node, forcedRootBlock); - else - rootBlockElm.appendChild(node); - } else - rootBlockElm = 0; - }); - } - } - } - } - - // Never remove nodes that isn't the specified inline element if a selector is specified too - if (format.selector && format.inline && !isEq(format.inline, node)) - return; - - dom.remove(node, 1); - }; - - function getNonWhiteSpaceSibling(node, next, inc) { - if (node) { - next = next ? 'nextSibling' : 'previousSibling'; - - for (node = inc ? node : node[next]; node; node = node[next]) { - if (node.nodeType == 1 || !isWhiteSpaceNode(node)) - return node; - } - } - }; - - function isBookmarkNode(node) { - return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; - }; - - function mergeSiblings(prev, next) { - var marker, sibling, tmpSibling; - - function compareElements(node1, node2) { - // Not the same name - if (node1.nodeName != node2.nodeName) - return FALSE; - - function getAttribs(node) { - var attribs = {}; - - each(dom.getAttribs(node), function(attr) { - var name = attr.nodeName.toLowerCase(); - - // Don't compare internal attributes or style - if (name.indexOf('_') !== 0 && name !== 'style') - attribs[name] = dom.getAttrib(node, name); - }); - - return attribs; - }; - - function compareObjects(obj1, obj2) { - var value, name; - - for (name in obj1) { - // Obj1 has item obj2 doesn't have - if (obj1.hasOwnProperty(name)) { - value = obj2[name]; - - // Obj2 doesn't have obj1 item - if (value === undefined) - return FALSE; - - // Obj2 item has a different value - if (obj1[name] != value) - return FALSE; - - // Delete similar value - delete obj2[name]; - } - } - - // Check if obj 2 has something obj 1 doesn't have - for (name in obj2) { - // Obj2 has item obj1 doesn't have - if (obj2.hasOwnProperty(name)) - return FALSE; - } - - return TRUE; - }; - - // Attribs are not the same - if (!compareObjects(getAttribs(node1), getAttribs(node2))) - return FALSE; - - // Styles are not the same - if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) - return FALSE; - - return TRUE; - }; - - // Check if next/prev exists and that they are elements - if (prev && next) { - function findElementSibling(node, sibling_name) { - for (sibling = node; sibling; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) - return node; - - if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) - return sibling; - } - - return node; - }; - - // If previous sibling is empty then jump over it - prev = findElementSibling(prev, 'previousSibling'); - next = findElementSibling(next, 'nextSibling'); - - // Compare next and previous nodes - if (compareElements(prev, next)) { - // Append nodes between - for (sibling = prev.nextSibling; sibling && sibling != next;) { - tmpSibling = sibling; - sibling = sibling.nextSibling; - prev.appendChild(tmpSibling); - } - - // Remove next node - dom.remove(next); - - // Move children into prev node - each(tinymce.grep(next.childNodes), function(node) { - prev.appendChild(node); - }); - - return prev; - } - } - - return next; - }; - - function isTextBlock(name) { - return /^(h[1-6]|p|div|pre|address)$/.test(name); - }; - - function getContainer(rng, start) { - var container, offset, lastIdx; - - container = rng[start ? 'startContainer' : 'endContainer']; - offset = rng[start ? 'startOffset' : 'endOffset']; - - if (container.nodeType == 1) { - lastIdx = container.childNodes.length - 1; - - if (!start && offset) - offset--; - - container = container.childNodes[offset > lastIdx ? lastIdx : offset]; - } - - return container; - }; - - function performCaretAction(type, name, vars) { - var i, currentPendingFormats = pendingFormats[type], - otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply']; - - function hasPending() { - return pendingFormats.apply.length || pendingFormats.remove.length; - }; - - function resetPending() { - pendingFormats.apply = []; - pendingFormats.remove = []; - }; - - function perform(caret_node) { - // Apply pending formats - each(pendingFormats.apply.reverse(), function(item) { - apply(item.name, item.vars, caret_node); - }); - - // Remove pending formats - each(pendingFormats.remove.reverse(), function(item) { - remove(item.name, item.vars, caret_node); - }); - - dom.remove(caret_node, 1); - resetPending(); - }; - - // Check if it already exists then ignore it - for (i = currentPendingFormats.length - 1; i >= 0; i--) { - if (currentPendingFormats[i].name == name) - return; - } - - currentPendingFormats.push({name : name, vars : vars}); - - // Check if it's in the other type, then remove it - for (i = otherPendingFormats.length - 1; i >= 0; i--) { - if (otherPendingFormats[i].name == name) - otherPendingFormats.splice(i, 1); - } - - // Pending apply or remove formats - if (hasPending()) { - ed.getDoc().execCommand('FontName', false, 'mceinline'); - - // IE will convert the current word - each(dom.select('font,span'), function(node) { - var bookmark; - - if (isCaretNode(node)) { - bookmark = selection.getBookmark(); - perform(node); - selection.moveToBookmark(bookmark); - ed.nodeChanged(); - } - }); - - // Only register listeners once if we need to - if (!pendingFormats.isListening && hasPending()) { - pendingFormats.isListening = true; - - each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) { - ed[event].addToTop(function(ed, e) { - if (hasPending()) { - each(dom.select('font,span'), function(node) { - var bookmark, textNode, rng; - - // Look for marker - if (isCaretNode(node)) { - textNode = node.firstChild; - - perform(node); - - rng = dom.createRng(); - rng.setStart(textNode, textNode.nodeValue.length); - rng.setEnd(textNode, textNode.nodeValue.length); - selection.setRng(rng); - ed.nodeChanged(); - } - }); - - // Always unbind and clear pending styles on keyup - if (e.type == 'keyup' || e.type == 'mouseup') - resetPending(); - } - }); - }); - } - } - }; - }; -})(tinymce); - -tinymce.onAddEditor.add(function(tinymce, ed) { - var filters, fontSizes, dom, settings = ed.settings; - - if (settings.inline_styles) { - fontSizes = tinymce.explode(settings.font_size_style_values); - - function replaceWithSpan(node, styles) { - dom.replace(dom.create('span', { - style : styles - }), node, 1); - }; - - filters = { - font : function(dom, node) { - replaceWithSpan(node, { - backgroundColor : node.style.backgroundColor, - color : node.color, - fontFamily : node.face, - fontSize : fontSizes[parseInt(node.size) - 1] - }); - }, - - u : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'underline' - }); - }, - - strike : function(dom, node) { - replaceWithSpan(node, { - textDecoration : 'line-through' - }); - } - }; - - function convert(editor, params) { - dom = editor.dom; - - if (settings.convert_fonts_to_spans) { - tinymce.each(dom.select('font,u,strike', params.node), function(node) { - filters[node.nodeName.toLowerCase()](ed.dom, node); - }); - } - }; - - ed.onPreProcess.add(convert); - - ed.onInit.add(function() { - ed.selection.onSetContent.add(convert); - }); - } -}); - +(function(win) { + var whiteSpaceRe = /^\s*|\s*$/g, + undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; + + var tinymce = { + majorVersion : '3', + + minorVersion : '4.7', + + releaseDate : '2011-11-03', + + _init : function() { + var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; + + t.isOpera = win.opera && opera.buildNumber; + + t.isWebKit = /WebKit/.test(ua); + + t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); + + t.isIE6 = t.isIE && /MSIE [56]/.test(ua); + + t.isIE7 = t.isIE && /MSIE [7]/.test(ua); + + t.isIE8 = t.isIE && /MSIE [8]/.test(ua); + + t.isIE9 = t.isIE && /MSIE [9]/.test(ua); + + t.isGecko = !t.isWebKit && /Gecko/.test(ua); + + t.isMac = ua.indexOf('Mac') != -1; + + t.isAir = /adobeair/i.test(ua); + + t.isIDevice = /(iPad|iPhone)/.test(ua); + + t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; + + // TinyMCE .NET webcontrol might be setting the values for TinyMCE + if (win.tinyMCEPreInit) { + t.suffix = tinyMCEPreInit.suffix; + t.baseURL = tinyMCEPreInit.base; + t.query = tinyMCEPreInit.query; + return; + } + + // Get suffix and base + t.suffix = ''; + + // If base element found, add that infront of baseURL + nl = d.getElementsByTagName('base'); + for (i=0; i : + s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); + cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name + + // Create namespace for new class + ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); + + // Class already exists + if (ns[cn]) + return; + + // Make pure static class + if (s[2] == 'static') { + ns[cn] = p; + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn]); + + return; + } + + // Create default constructor + if (!p[cn]) { + p[cn] = function() {}; + de = 1; + } + + // Add constructor and methods + ns[cn] = p[cn]; + t.extend(ns[cn].prototype, p); + + // Extend + if (s[5]) { + sp = t.resolve(s[5]).prototype; + scn = s[5].match(/\.(\w+)$/i)[1]; // Class name + + // Extend constructor + c = ns[cn]; + if (de) { + // Add passthrough constructor + ns[cn] = function() { + return sp[scn].apply(this, arguments); + }; + } else { + // Add inherit constructor + ns[cn] = function() { + this.parent = sp[scn]; + return c.apply(this, arguments); + }; + } + ns[cn].prototype[cn] = ns[cn]; + + // Add super methods + t.each(sp, function(f, n) { + ns[cn].prototype[n] = sp[n]; + }); + + // Add overridden methods + t.each(p, function(f, n) { + // Extend methods if needed + if (sp[n]) { + ns[cn].prototype[n] = function() { + this.parent = sp[n]; + return f.apply(this, arguments); + }; + } else { + if (n != cn) + ns[cn].prototype[n] = f; + } + }); + } + + // Add static methods + t.each(p['static'], function(f, n) { + ns[cn][n] = f; + }); + + if (this.onCreate) + this.onCreate(s[2], s[3], ns[cn].prototype); + }, + + walk : function(o, f, n, s) { + s = s || this; + + if (o) { + if (n) + o = o[n]; + + tinymce.each(o, function(o, i) { + if (f.call(s, o, i, n) === false) + return false; + + tinymce.walk(o, f, n, s); + }); + } + }, + + createNS : function(n, o) { + var i, v; + + o = o || win; + + n = n.split('.'); + for (i=0; i= items.length) { + for (i = 0, l = base.length; i < l; i++) { + if (i >= items.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (base.length < items.length) { + for (i = 0, l = items.length; i < l; i++) { + if (i >= base.length || base[i] != items[i]) { + bp = i + 1; + break; + } + } + } + + if (bp == 1) + return path; + + for (i = 0, l = base.length - (bp - 1); i < l; i++) + out += "../"; + + for (i = bp - 1, l = items.length; i < l; i++) { + if (i != bp - 1) + out += "/" + items[i]; + else + out += items[i]; + } + + return out; + }, + + toAbsPath : function(base, path) { + var i, nb = 0, o = [], tr, outPath; + + // Split paths + tr = /\/$/.test(path) ? '/' : ''; + base = base.split('/'); + path = path.split('/'); + + // Remove empty chunks + each(base, function(k) { + if (k) + o.push(k); + }); + + base = o; + + // Merge relURLParts chunks + for (i = path.length - 1, o = []; i >= 0; i--) { + // Ignore empty or . + if (path[i].length == 0 || path[i] == ".") + continue; + + // Is parent + if (path[i] == '..') { + nb++; + continue; + } + + // Move up + if (nb > 0) { + nb--; + continue; + } + + o.push(path[i]); + } + + i = base.length - nb; + + // If /a/b/c or / + if (i <= 0) + outPath = o.reverse().join('/'); + else + outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); + + // Add front / if it's needed + if (outPath.indexOf('/') !== 0) + outPath = '/' + outPath; + + // Add traling / if it's needed + if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) + outPath += tr; + + return outPath; + }, + + getURI : function(nh) { + var s, t = this; + + // Rebuild source + if (!t.source || nh) { + s = ''; + + if (!nh) { + if (t.protocol) + s += t.protocol + '://'; + + if (t.userInfo) + s += t.userInfo + '@'; + + if (t.host) + s += t.host; + + if (t.port) + s += ':' + t.port; + } + + if (t.path) + s += t.path; + + if (t.query) + s += '?' + t.query; + + if (t.anchor) + s += '#' + t.anchor; + + t.source = s; + } + + return t.source; + } + }); +})(); + +(function() { + var each = tinymce.each; + + tinymce.create('static tinymce.util.Cookie', { + getHash : function(n) { + var v = this.get(n), h; + + if (v) { + each(v.split('&'), function(v) { + v = v.split('='); + h = h || {}; + h[unescape(v[0])] = unescape(v[1]); + }); + } + + return h; + }, + + setHash : function(n, v, e, p, d, s) { + var o = ''; + + each(v, function(v, k) { + o += (!o ? '' : '&') + escape(k) + '=' + escape(v); + }); + + this.set(n, o, e, p, d, s); + }, + + get : function(n) { + var c = document.cookie, e, p = n + "=", b; + + // Strict mode + if (!c) + return; + + b = c.indexOf("; " + p); + + if (b == -1) { + b = c.indexOf(p); + + if (b != 0) + return null; + } else + b += 2; + + e = c.indexOf(";", b); + + if (e == -1) + e = c.length; + + return unescape(c.substring(b + p.length, e)); + }, + + set : function(n, v, e, p, d, s) { + document.cookie = n + "=" + escape(v) + + ((e) ? "; expires=" + e.toGMTString() : "") + + ((p) ? "; path=" + escape(p) : "") + + ((d) ? "; domain=" + d : "") + + ((s) ? "; secure" : ""); + }, + + remove : function(n, p) { + var d = new Date(); + + d.setTime(d.getTime() - 1000); + + this.set(n, '', d, p, d); + } + }); +})(); + +(function() { + function serialize(o, quote) { + var i, v, t; + + quote = quote || '"'; + + if (o == null) + return 'null'; + + t = typeof o; + + if (t == 'string') { + v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; + + return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { + // Make sure single quotes never get encoded inside double quotes for JSON compatibility + if (quote === '"' && a === "'") + return a; + + i = v.indexOf(b); + + if (i + 1) + return '\\' + v.charAt(i + 1); + + a = b.charCodeAt().toString(16); + + return '\\u' + '0000'.substring(a.length) + a; + }) + quote; + } + + if (t == 'object') { + if (o.hasOwnProperty && o instanceof Array) { + for (i=0, v = '['; i 0 ? ',' : '') + serialize(o[i], quote); + + return v + ']'; + } + + v = '{'; + + for (i in o) { + if (o.hasOwnProperty(i)) { + v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : ''; + } + } + + return v + '}'; + } + + return '' + o; + }; + + tinymce.util.JSON = { + serialize: serialize, + + parse: function(s) { + try { + return eval('(' + s + ')'); + } catch (ex) { + // Ignore + } + } + + }; +})(); + +tinymce.create('static tinymce.util.XHR', { + send : function(o) { + var x, t, w = window, c = 0; + + // Default settings + o.scope = o.scope || this; + o.success_scope = o.success_scope || o.scope; + o.error_scope = o.error_scope || o.scope; + o.async = o.async === false ? false : true; + o.data = o.data || ''; + + function get(s) { + x = 0; + + try { + x = new ActiveXObject(s); + } catch (ex) { + } + + return x; + }; + + x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); + + if (x) { + if (x.overrideMimeType) + x.overrideMimeType(o.content_type); + + x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); + + if (o.content_type) + x.setRequestHeader('Content-Type', o.content_type); + + x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + x.send(o.data); + + function ready() { + if (!o.async || x.readyState == 4 || c++ > 10000) { + if (o.success && c < 10000 && x.status == 200) + o.success.call(o.success_scope, '' + x.responseText, x, o); + else if (o.error) + o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); + + x = null; + } else + w.setTimeout(ready, 10); + }; + + // Syncronous request + if (!o.async) + return ready(); + + // Wait for response, onReadyStateChange can not be used since it leaks memory in IE + t = w.setTimeout(ready, 10); + } + } +}); + +(function() { + var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; + + tinymce.create('tinymce.util.JSONRequest', { + JSONRequest : function(s) { + this.settings = extend({ + }, s); + this.count = 0; + }, + + send : function(o) { + var ecb = o.error, scb = o.success; + + o = extend(this.settings, o); + + o.success = function(c, x) { + c = JSON.parse(c); + + if (typeof(c) == 'undefined') { + c = { + error : 'JSON Parse error.' + }; + } + + if (c.error) + ecb.call(o.error_scope || o.scope, c.error, x); + else + scb.call(o.success_scope || o.scope, c.result); + }; + + o.error = function(ty, x) { + if (ecb) + ecb.call(o.error_scope || o.scope, ty, x); + }; + + o.data = JSON.serialize({ + id : o.id || 'c' + (this.count++), + method : o.method, + params : o.params + }); + + // JSON content type for Ruby on rails. Bug: #1883287 + o.content_type = 'application/json'; + + XHR.send(o); + }, + + 'static' : { + sendRPC : function(o) { + return new tinymce.util.JSONRequest().send(o); + } + } + }); +}()); +(function(tinymce){ + tinymce.VK = { + DELETE: 46, + BACKSPACE: 8, + ENTER: 13, + TAB: 9, + SPACEBAR: 32, + UP: 38, + DOWN: 40 + } +})(tinymce); + +(function(tinymce) { + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE; + + function cleanupStylesWhenDeleting(ed) { + var dom = ed.dom, selection = ed.selection; + + ed.onKeyDown.add(function(ed, e) { + var rng, blockElm, node, clonedSpan, isDelete; + + isDelete = e.keyCode == DELETE; + if (isDelete || e.keyCode == BACKSPACE) { + e.preventDefault(); + rng = selection.getRng(); + + // Find root block + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + + // On delete clone the root span of the next block element + if (isDelete) + blockElm = dom.getNext(blockElm, dom.isBlock); + + // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace + if (blockElm) { + node = blockElm.firstChild; + + // Ignore empty text nodes + while (node && node.nodeType == 3 && node.nodeValue.length == 0) + node = node.nextSibling; + + if (node && node.nodeName === 'SPAN') { + clonedSpan = node.cloneNode(false); + } + } + + // Do the backspace/delete actiopn + ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Find all odd apple-style-spans + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { + var bm = selection.getBookmark(); + + if (clonedSpan) { + dom.replace(clonedSpan.cloneNode(false), span, true); + } else { + dom.remove(span, true); + } + + // Restore the selection + selection.moveToBookmark(bm); + }); + } + }); + }; + + function emptyEditorWhenDeleting(ed) { + ed.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == DELETE || keyCode == BACKSPACE) { + if (ed.dom.isEmpty(ed.getBody())) { + ed.setContent('', {format : 'raw'}); + ed.nodeChanged(); + return; + } + } + }); + }; + + function inputMethodFocus(ed) { + ed.dom.bind(ed.getDoc(), 'focusin', function() { + ed.selection.setRng(ed.selection.getRng()); + }); + }; + + function removeHrOnBackspace(ed) { + ed.onKeyDown.add(function(ed, e) { + if (e.keyCode === BACKSPACE) { + if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) { + var node = ed.selection.getNode(); + var previousSibling = node.previousSibling; + if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { + ed.dom.remove(previousSibling); + tinymce.dom.Event.cancel(e); + } + } + } + }) + } + + function focusBody(ed) { + // Fix for a focus bug in FF 3.x where the body element + // wouldn't get proper focus if the user clicked on the HTML element + if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 + ed.onMouseDown.add(function(ed, e) { + if (e.target.nodeName === "HTML") { + var body = ed.getBody(); + + // Blur the body it's focused but not correctly focused + body.blur(); + + // Refocus the body after a little while + setTimeout(function() { + body.focus(); + }, 0); + } + }); + } + }; + + function selectControlElements(ed) { + ed.onClick.add(function(ed, e) { + e = e.target; + + // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 + // WebKit can't even do simple things like selecting an image + // Needs tobe the setBaseAndExtend or it will fail to select floated images + if (/^(IMG|HR)$/.test(e.nodeName)) + ed.selection.getSel().setBaseAndExtent(e, 0, e, 1); + + if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor')) + ed.selection.select(e); + + ed.nodeChanged(); + }); + }; + + function selectionChangeNodeChanged(ed) { + var lastRng, selectionTimer; + + ed.dom.bind(ed.getDoc(), 'selectionchange', function() { + if (selectionTimer) { + clearTimeout(selectionTimer); + selectionTimer = 0; + } + + selectionTimer = window.setTimeout(function() { + var rng = ed.selection.getRng(); + + // Compare the ranges to see if it was a real change or not + if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { + ed.nodeChanged(); + lastRng = rng; + } + }, 50); + }); + } + + function ensureBodyHasRoleApplication(ed) { + document.body.setAttribute("role", "application"); + } + + tinymce.create('tinymce.util.Quirks', { + Quirks: function(ed) { + // WebKit + if (tinymce.isWebKit) { + cleanupStylesWhenDeleting(ed); + emptyEditorWhenDeleting(ed); + inputMethodFocus(ed); + selectControlElements(ed); + + // iOS + if (tinymce.isIDevice) { + selectionChangeNodeChanged(ed); + } + } + + // IE + if (tinymce.isIE) { + removeHrOnBackspace(ed); + emptyEditorWhenDeleting(ed); + ensureBodyHasRoleApplication(ed); + } + + // Gecko + if (tinymce.isGecko) { + removeHrOnBackspace(ed); + focusBody(ed); + } + } + }); +})(tinymce); + +(function(tinymce) { + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#x|#)?([\w]+);/g, + asciiMap = { + 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", + 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", + 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", + 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", + 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" + }; + + // Raw entities + baseEntities = { + '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; + + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" + }; + + // Decodes text by using the browser + function nativeDecode(text) { + var elm; + + elm = document.createElement("div"); + elm.innerHTML = text; + + return elm.textContent || elm.innerText || text; + }; + + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; + + if (items) { + items = items.split(','); + radix = radix || 10; + + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } + + return lookup; + } + }; + + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + namedEntities = buildEntitiesLookup( + '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro' + , 32); + + tinymce.html = tinymce.html || {}; + + tinymce.html.Entities = { + encodeRaw : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); + }, + + encodeNumeric : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) + return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; + + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, + + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; + + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); + }, + + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; + + entities = buildEntitiesLookup(entities) || namedEntities; + + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + }; + + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; + + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); + + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; + + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; + + return Entities.encodeNamed; + } + + // Numeric + if (name.numeric) + return Entities.encodeNumeric; + + // Raw encoder + return Entities.encodeRaw; + }, + + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value, numeric.length === 2 ? 16 : 10); + + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; + + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); + } + + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); + +tinymce.html.Styles = function(settings, schema) { + var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, + urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, + styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, + trimRightRegExp = /\s+$/, + urlColorRegExp = /rgb/, + undef, i, encodingLookup = {}, encodingItems; + + settings = settings || {}; + + encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '\uFEFF' + i; + encodingLookup['\uFEFF' + i] = encodingItems[i]; + } + + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); + + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; + + return '#' + hex(r) + hex(g) + hex(b); + }; + + return { + toHex : function(color) { + return color.replace(rgbRegExp, toHex); + }, + + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; + + function compress(prefix, suffix) { + var top, right, bottom, left; + + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; + + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; + + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; + + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; + + // Compress + styles[prefix + suffix] = left; + delete styles[prefix + '-top' + suffix]; + delete styles[prefix + '-right' + suffix]; + delete styles[prefix + '-bottom' + suffix]; + delete styles[prefix + '-left' + suffix]; + }; + + function canCompress(key) { + var value = styles[key], i; + + if (!value || value.indexOf(' ') < 0) + return; + + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; + } + + styles[key] = value[0]; + + return true; + }; + + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; + + if (!canCompress(b)) + return; + + if (!canCompress(c)) + return; + + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; + + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; + + return encodingLookup[str]; + }; + + // Decodes the specified string by replacing all _ with it's original value \" \' etc + // It will also decode the \" \' if keep_slashes is set to fale or omitted + function decode(str, keep_slashes) { + if (isEncoded) { + str = str.replace(/\uFEFF[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } + + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); + + return str; + } + + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); + }); + + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); + + if (name && value.length > 0) { + // Opera will produce 700 instead of bold in their style values + if (name === 'font-weight' && value === '700') + value = 'bold'; + else if (name === 'color' || name === 'background-color') // Lowercase colors like RED + value = value.toLowerCase(); + + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); + + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) { + str = str || str2; + + if (str) { + str = decode(str); + + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } + + url = decode(url || url2 || url3); + + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); + + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }); + + styles[name] = isEncoded ? decode(value, true) : value; + } + + styleRegExp.lastIndex = matches.index + matches[0].length; + } + + // Compress the styles to reduce it's size for example IE will expand styles + compress("border", ""); + compress("border", "-width"); + compress("border", "-color"); + compress("border", "-style"); + compress("padding", ""); + compress("margin", ""); + compress2('border', 'border-width', 'border-style', 'border-color'); + + // Remove pointless border, IE produces these + if (styles.border === 'medium none') + delete styles.border; + } + + return styles; + }, + + serialize : function(styles, element_name) { + var css = '', name, value; + + function serializeStyles(name) { + var styleList, i, l, value; + + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; + + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(element_name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; + + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + + return css; + } + }; +}; + +(function(tinymce) { + var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {}, + defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each; + + function split(str, delim) { + return str.split(delim || ','); + }; + + function unpack(lookup, data) { + var key, elements = {}; + + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; + + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } + + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); + + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) + } + }); + + return elements; + }; + + // Build a lookup table for block elements both lowercase and uppercase + blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + + 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + + 'noscript,menu,isindex,samp,header,footer,article,section,hgroup'; + blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase())); + + // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size + transitional = unpack({ + Z : 'H|K|N|O|P', + Y : 'X|form|R|Q', + ZG : 'E|span|width|align|char|charoff|valign', + X : 'p|T|div|U|W|isindex|fieldset|table', + ZF : 'E|align|char|charoff|valign', + W : 'pre|hr|blockquote|address|center|noframes', + ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', + ZD : '[E][S]', + U : 'ul|ol|dl|menu|dir', + ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', + T : 'h1|h2|h3|h4|h5|h6', + ZB : 'X|S|Q', + S : 'R|P', + ZA : 'a|G|J|M|O|P', + R : 'a|H|K|N|O', + Q : 'noscript|P', + P : 'ins|del|script', + O : 'input|select|textarea|label|button', + N : 'M|L', + M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', + L : 'sub|sup', + K : 'J|I', + J : 'tt|i|b|u|s|strike', + I : 'big|small|font|basefont', + H : 'G|F', + G : 'br|span|bdo', + F : 'object|applet|img|map|iframe', + E : 'A|B|C', + D : 'accesskey|tabindex|onfocus|onblur', + C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : 'lang|xml:lang|dir', + A : 'id|class|style|title' + }, 'script[id|charset|type|language|src|defer|xml:space][]' + + 'style[B|id|type|media|title|xml:space][]' + + 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + + 'param[id|name|value|valuetype|type][]' + + 'p[E|align][#|S]' + + 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + + 'br[A|clear][]' + + 'span[E][#|S]' + + 'bdo[A|C|B][#|S]' + + 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + + 'h1[E|align][#|S]' + + 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + + 'map[B|C|A|name][X|form|Q|area]' + + 'h2[E|align][#|S]' + + 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + + 'h3[E|align][#|S]' + + 'tt[E][#|S]' + + 'i[E][#|S]' + + 'b[E][#|S]' + + 'u[E][#|S]' + + 's[E][#|S]' + + 'strike[E][#|S]' + + 'big[E][#|S]' + + 'small[E][#|S]' + + 'font[A|B|size|color|face][#|S]' + + 'basefont[id|size|color|face][]' + + 'em[E][#|S]' + + 'strong[E][#|S]' + + 'dfn[E][#|S]' + + 'code[E][#|S]' + + 'q[E|cite][#|S]' + + 'samp[E][#|S]' + + 'kbd[E][#|S]' + + 'var[E][#|S]' + + 'cite[E][#|S]' + + 'abbr[E][#|S]' + + 'acronym[E][#|S]' + + 'sub[E][#|S]' + + 'sup[E][#|S]' + + 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + + 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + + 'optgroup[E|disabled|label][option]' + + 'option[E|selected|disabled|label|value][]' + + 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + + 'label[E|for|accesskey|onfocus|onblur][#|S]' + + 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + + 'h4[E|align][#|S]' + + 'ins[E|cite|datetime][#|Y]' + + 'h5[E|align][#|S]' + + 'del[E|cite|datetime][#|Y]' + + 'h6[E|align][#|S]' + + 'div[E|align][#|Y]' + + 'ul[E|type|compact][li]' + + 'li[E|type|value][#|Y]' + + 'ol[E|type|compact|start][li]' + + 'dl[E|compact][dt|dd]' + + 'dt[E][#|S]' + + 'dd[E][#|Y]' + + 'menu[E|compact][li]' + + 'dir[E|compact][li]' + + 'pre[E|width|xml:space][#|ZA]' + + 'hr[E|align|noshade|size|width][]' + + 'blockquote[E|cite][#|Y]' + + 'address[E][#|S|p]' + + 'center[E][#|Y]' + + 'noframes[E][#|Y]' + + 'isindex[A|B|prompt][]' + + 'fieldset[E][#|legend|Y]' + + 'legend[E|accesskey|align][#|S]' + + 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + + 'caption[E|align][#|S]' + + 'col[ZG][]' + + 'colgroup[ZG][col]' + + 'thead[ZF][tr]' + + 'tr[ZF|bgcolor][th|td]' + + 'th[E|ZE][#|Y]' + + 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + + 'noscript[E][#|Y]' + + 'td[E|ZE][#|Y]' + + 'tfoot[ZF][tr]' + + 'tbody[ZF][tr]' + + 'area[E|D|shape|coords|href|nohref|alt|target][]' + + 'base[id|href|target][]' + + 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' + ); + + boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls'); + shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source'); + nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap); + defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea'); + selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); + + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap; + + settings = settings || {}; + + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; + + // Build styles list + if (settings.valid_styles) { + validStyles = {}; + + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); + }); + } + + whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap; + + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; + + // Parses the specified valid_elements string and adds to the current rules + // This function is a bit hard to read since it's heavily optimized for speed + function addValidElements(valid_elements) { + var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, + elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, + attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, + hasPatternsRegExp = /[*?+]/; + + if (valid_elements) { + // Split valid elements into an array with rules + valid_elements = split(valid_elements); + + if (elements['@']) { + globalAttributes = elements['@'].attributes; + globalAttributesOrder = elements['@'].attributesOrder; + } + + // Loop all rules + for (ei = 0, el = valid_elements.length; ei < el; ei++) { + // Parse element rule + matches = elementRuleRegExp.exec(valid_elements[ei]); + if (matches) { + // Setup local names for matches + prefix = matches[1]; + elementName = matches[2]; + outputName = matches[3]; + attrData = matches[4]; + + // Create new attributes and attributesOrder + attributes = {}; + attributesOrder = []; + + // Create the new element + element = { + attributes : attributes, + attributesOrder : attributesOrder + }; + + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; + + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; + + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; + + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); + } + + // Attributes defined + if (attrData) { + attrData = split(attrData, '|'); + for (ai = 0, al = attrData.length; ai < al; ai++) { + matches = attrRuleRegExp.exec(attrData[ai]); + if (matches) { + attr = {}; + attrType = matches[1]; + attrName = matches[2].replace(/::/g, ':'); + prefix = matches[3]; + value = matches[4]; + + // Required + if (attrType === '!') { + element.attributesRequired = element.attributesRequired || []; + element.attributesRequired.push(attrName); + attr.required = true; + } + + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } + + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } + + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } + + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } + + // Check for attribute patterns + if (hasPatternsRegExp.test(attrName)) { + element.attributePatterns = element.attributePatterns || []; + attr.pattern = patternToRegExp(attrName); + element.attributePatterns.push(attr); + } else { + // Add attribute to order list if it doesn't already exist + if (!attributes[attrName]) + attributesOrder.push(attrName); + + attributes[attrName] = attr; + } + } + } + } + + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } + + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } + + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } + } + } + }; + + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; + + addValidElements(valid_elements); + + each(transitional, function(element, name) { + children[name] = element.children; + }); + }; + + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; + + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + inline = matches[1] === '~', + cloneName = inline ? 'span' : 'div', + name = matches[2]; + + children[name] = children[cloneName]; + customElementsMap[name] = cloneName; + + // If it's not marked as inline then add it to valid block elements + if (!inline) + blockElementsMap[name] = {}; + + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); + } + }; + + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; + + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; + + if (matches) { + prefix = matches[1]; + + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; + + parent = children[matches[2]]; + + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); + } + }; + + function getElementRule(name) { + var element = elements[name], i; + + // Exact match found + if (element) + return element; + + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; + } + }; + + if (!settings.valid_elements) { + // No valid elements defined then clone the elements from the transitional spec + each(transitional, function(element, name) { + elements[name] = { + attributes : element.attributes, + attributesOrder : element.attributesOrder + }; + + children[name] = element.children; + }); + + // Switch these + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); + + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; + + // Remove these if they are empty by default + each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) { + elements[name].removeEmpty = true; + }); + + // Padd these by default + each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { + elements[name].paddEmpty = true; + }); + } else + setValidElements(settings.valid_elements); + + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); + + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); + + // If the user didn't allow span only allow internal spans + if (!getElementRule('span')) + addValidElements('span[!data-mce-type|*]'); + + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } + + self.children = children; + + self.styles = validStyles; + + self.getBoolAttrs = function() { + return boolAttrMap; + }; + + self.getBlockElements = function() { + return blockElementsMap; + }; + + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; + + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; + + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; + + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; + + self.isValidChild = function(name, child) { + var parent = children[name]; + + return !!(parent && parent[child]); + }; + + self.getElementRule = getElementRule; + + self.getCustomElements = function() { + return customElementsMap; + }; + + self.addValidElements = addValidElements; + + self.setValidElements = setValidElements; + + self.addCustomElements = addCustomElements; + + self.addValidChildren = addValidChildren; + }; + + // Expose boolMap and blockElementMap as static properties for usage in DOMUtils + tinymce.html.Schema.boolAttrMap = boolAttrMap; + tinymce.html.Schema.blockElementsMap = blockElementsMap; +})(tinymce); + +(function(tinymce) { + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; + + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); + + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; + + // Add handler functions from settings and setup default handlers + tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { + if (name) + self[name] = settings[name] || noop; + }); + + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; + + function processEndTag(name) { + var pos, i; + + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } + + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; + + if (name.valid) + self.end(name.name); + } + + // Remove the open elements from the stack + stack.length = pos; + } + }; + + // Precompile RegExps and map objects + tokenRegExp = new RegExp('<(?:' + + '(?:!--([\\w\\W]*?)-->)|' + // Comment + '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA + '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE + '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI + '(?:\\/([^>]+)>)|' + // End element + '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/)>)' + // Start element + ')', 'g'); + + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; + specialElements = { + 'script' : /<\/script[^>]*>/gi, + 'style' : /<\/style[^>]*>/gi, + 'noscript' : /<\/noscript[^>]*>/gi + }; + + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + removeInternalElements = settings.remove_internals; + fixSelfClosing = settings.fix_self_closing; + isIE = tinymce.isIE; + invalidPrefixRegExp = /^:/; + + while (matches = tokenRegExp.exec(html)) { + // Text + if (index < matches.index) + self.text(decode(html.substr(index, matches.index - index))); + + if (value = matches[6]) { // End element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + processEndTag(value); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + isShortEnded = value in shortEndedElements; + + // Is self closing tag for example an
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } + + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element + + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; + + attrList = []; + attrList.map = {}; + + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; + + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute + + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; + + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } + + // No rule matched + if (i === -1) + attrRule = null; + } + + // No attribute rule found + if (!attrRule) + return; + + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } + + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } + + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; + + if (!(name in attrList.map)) { + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } + + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } + + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } + + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } + + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; + + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; + + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); + + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } + + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } + + index = matches.index + matches[0].length; + } + + // Text + if (index < html.length) + self.text(decode(html.substr(index))); + + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; + + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); + +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; + + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; + + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; + + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; + + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; + + if (sibling) + return sibling; + } + } + }; + + function Node(name, type) { + this.name = name; + this.type = type; + + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } + + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; + + if (node.parent) + node.remove(); + + self.insert(node, self); + self.remove(); + + return self; + }, + + attr : function(name, value) { + var self = this, attrs, i, undef; + + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); + + return self; + } + + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; + + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } + } + + return self; + } + + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; + } + } + } else + attrs.push({name: name, value: value}); + + attrs.map[name] = value; + + return self; + } else { + return attrs.map[name]; + } + } + }, + + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; + + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; + + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; + + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } + + clone.attributes = cloneAttrs; + } + + clone.value = self.value; + clone.shortEnded = self.shortEnded; + + return clone; + }, + + wrap : function(wrapper) { + var self = this; + + self.parent.insert(wrapper, self); + wrapper.append(self); + + return self; + }, + + unwrap : function() { + var self = this, node, next; + + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } + + self.remove(); + }, + + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; + + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; + + if (next) + next.prev = null; + } else { + prev.next = next; + } + + if (parent.lastChild === self) { + parent.lastChild = prev; + + if (prev) + prev.next = null; + } else { + next.prev = prev; + } + + self.parent = self.next = self.prev = null; + } + + return self; + }, + + append : function(node) { + var self = this, last; + + if (node.parent) + node.remove(); + + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; + + node.parent = self; + + return node; + }, + + insert : function(node, ref_node, before) { + var parent; + + if (node.parent) + node.remove(); + + parent = ref_node.parent || this; + + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; + + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; + + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } + + node.parent = parent; + + return node; + }, + + getAll : function(name) { + var self = this, node, collection = []; + + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); + } + + return collection; + }, + + empty : function() { + var self = this, nodes, i, node; + + // Remove all children + if (self.firstChild) { + nodes = []; + + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); + + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } + + self.firstChild = self.lastChild = null; + + return self; + }, + + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; + + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; + + // Keep empty elements like + if (elements[node.name]) + return false; + + // Keep elements with data attributes or name attribute like + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } + } + + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } + + return true; + }, + + walk : function(prev) { + return walk(this, null, prev); + } + }); + + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; + + // Create node + node = new Node(name, typeLookup[name] || 1); + + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } + + return node; + } + }); + + tinymce.html.Node = Node; +})(tinymce); + +(function(tinymce) { + var Node = tinymce.html.Node; + + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); + + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); + + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; + + // Already removed + if (!node.parent) + continue; + + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); + + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); + + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); + + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; + + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } + + currentNode = tempNode; + } + + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } + + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } + + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } + + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } + + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; + + self.filterNode = function(node) { + var i, name, list; + + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; + + if (name in node.attributes.map) { + list = matchedAttributes[name]; + + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } + + return node; + }; + + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; + + if (!list) + nodeFilters[name] = list = []; + + list.push(callback); + }); + }; + + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } + + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; + + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], + endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } + + node = next; + }; + }; + + function createNode(name, type) { + var node = new Node(name, type), list; + + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + return node; + }; + + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; + + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; + + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results + + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, + + text: function(text, raw) { + var textNode; + + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); + + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } + + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, + + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, + + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, + + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, + + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; + + node.append(newNode); + + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); + + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; + + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; + + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } + + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + } + }, + + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!whiteSpaceElements[node.name]) { + // Trim whitespace at beginning of block + for (textNode = node.firstChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } + + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } + + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } + + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like + if (!node.attributes.map.name) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } + + node = node.parent; + } + } + }, schema); + + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; + } + + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); + + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } + + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; + + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } + + return rootNode; + }; + + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = schema.getBlockElements(), + nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the right of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } + + prev = prev.prev; + } + + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } + } + }); + } + } +})(tinymce); + +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; + + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; + + return { + start: function(name, attrs, empty) { + var i, l, attr, value; + + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } + } + + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; + + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + end: function(name) { + var value; + + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ + + html.push(''); + + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, + + cdata: function(text) { + html.push(''); + }, + + comment: function(text) { + html.push(''); + }, + + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); + + if (indent) + html.push('\n'); + }, + + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, + + reset: function() { + html.length = 0; + }, + + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; + +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; + + self.serialize = function(node) { + var handlers, validate; + + validate = settings.validate; + + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, + + // #comment + 8: function(node) { + writer.comment(node.value); + }, + + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, + + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; + + writer.reset(); + + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; + + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; + + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; + + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; + + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + attrs = sortedAttrs; + } + + writer.start(node.name, attrs, isEmpty); + + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + + writer.end(name); + } + } else + handler(node); + } + + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); + + return writer.getContent(); + }; + } +})(tinymce); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + blockElementsMap = tinymce.html.Schema.blockElementsMap, + whiteSpaceRegExp = /^[ \t\r\n]*$/; + + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, + + DOMUtils : function(d, s) { + var t = this, globalStyle, name; + + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); + + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); + + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } + } + + if (isIE && s.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + d.createElement(name); + }); + + // Create all custom elements + for (name in s.schema.getCustomElements()) { + d.createElement(name); + } + } + + tinymce.addUnload(t.destroy, t); + }, + + getRoot : function() { + var t = this, s = t.settings; + + return (s && t.get(s.root_element)) || t.doc.body; + }, + + getViewPort : function(w) { + var d, b; + + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; + + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, + + getRect : function(e) { + var p, t = this, sr; + + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); + + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, + + getSize : function(e) { + var t = this, w, h; + + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); + + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; + + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; + + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, + + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, + + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; + + n = t.get(n); + c = c === undefined; + + if (se.strict_root) + r = r || t.getRoot(); + + // Wrap node name as func + if (is(f, 'string')) { + na = f; + + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } + + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; + + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } + + n = n.parentNode; + } + + return c ? o : null; + }, + + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; + } + + return e; + }, + + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, + + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, + + + select : function(pa, s) { + var t = this; + + return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); + }, + + is : function(n, selector) { + var i; + + // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance + if (n.length === undefined) { + // Simple all selector + if (selector === '*') + return n.nodeType == 1; + + // Simple selector just elements + if (simpleSelectorRe.test(selector)) { + selector = selector.toLowerCase().split(/,/); + n = n.nodeName.toLowerCase(); + + for (i = selector.length - 1; i >= 0; i--) { + if (selector[i] == n) + return true; + } + + return false; + } + } + + return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + }, + + + add : function(p, n, a, h, c) { + var t = this; + + return this.run(p, function(p) { + var e, k; + + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); + + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } + + return !c ? p.appendChild(e) : e; + }); + }, + + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, + + createHTML : function(n, a, h) { + var o = '', t = this, k; + + o += '<' + n; + + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } + + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; + + return o + ' />'; + }, + + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } + } + + return parent.removeChild(node); + }); + }, + + setStyle : function(n, na, v) { + var t = this; + + return t.run(n, function(e) { + var s, i; + + s = e.style; + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; + + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } + + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; + + case 'float': + isIE ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } + + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, + + getStyle : function(n, na, c) { + n = this.get(n); + + if (!n) + return; + + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } + } + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; + + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; + + return n.style ? n.style[na] : undefined; + }, + + setStyles : function(e, o) { + var t = this, s = t.settings, ol; + + ol = s.update_styles; + s.update_styles = 0; + + each(o, function(v, n) { + t.setStyle(e, n, v); + }); + + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, + + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, + + setAttrib : function(e, n, v) { + var t = this; + + // Whats the point + if (!e || !n) + return; + + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); + + return this.run(e, function(e) { + var s = t.settings; + if (v !== null) { + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); + + return; + } + + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } + + e.style.cssText = v; + break; + + case "class": + e.className = v || ''; // Fix IE null bug + break; + + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + + t.setAttrib(e, 'data-mce-' + n, v, 2); + } + + break; + + case "shape": + e.setAttribute('data-mce-style', v); + break; + } + } + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + }); + }, + + setAttribs : function(e, o) { + var t = this; + + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, + + getAttrib : function(e, n, dv) { + var v, t = this, undef; + + e = t.get(e); + + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; + + if (!is(dv)) + dv = ''; + + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); + + if (v) + return v; + } + + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } + + if (!v) + v = e.getAttribute(n, 2); + + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; + + return v ? n : ''; + } + + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); + + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); + } + } + + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; + + break; + + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; + + break; + + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; + + break; + + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; + + break; + + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; + + break; + + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; + + return dv; + + case 'shape': + v = v.toLowerCase(); + break; + + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); + } + } + + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; + } + + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } + + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } + + return {x : x, y : y}; + }, + + parseStyle : function(st) { + return this.styles.parse(st); + }, + + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, + + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = t.select('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; + } + + head.appendChild(link); + }); + }, + + addClass : function(e, c) { + return this.run(e, function(e) { + var o; + + if (!c) + return 0; + + if (this.hasClass(e, c)) + return e.className; + + o = this.removeClass(e, c); + + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, + + removeClass : function(e, c) { + var t = this, re; + + return t.run(e, function(e) { + var v; + + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); + + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); + } + + return v; + } + + return e.className; + }); + }, + + hasClass : function(n, c) { + n = this.get(n); + + if (!n || !c) + return false; + + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, + + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, + + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, + + isHidden : function(e) { + e = this.get(e); + + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, + + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, + + setHTML : function(element, html) { + var self = this; + + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + element = self.create('div'); + element.innerHTML = '
    ' + html; + + // Add all children from div to target + each (element.childNodes, function(node, i) { + // Skip br element + if (i) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; + + return html; + }); + }, + + getOuterHTML : function(elm) { + var doc, self = this; + + elm = self.get(elm); + + if (!elm) + return null; + + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; + + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); + + return doc.innerHTML; + }, + + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + }; + + return this.run(e, function(e) { + e = t.get(e); + + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; + + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); + } + }); + }, + + decode : Entities.decode, + + encode : Entities.encodeAllRaw, + + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); + + return this.run(node, function(node) { + var parent, nextSibling; + + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); + + return node; + }); + }, + + isBlock : function(node) { + var type = node.nodeType; + + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); + + return !!blockElementsMap[node]; + }, + + replace : function(n, o, k) { + var t = this; + + if (is(o, 'array')) + n = n.cloneNode(true); + + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + rename : function(elm, name) { + var t = this, newElm; + + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); + + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); + + // Replace block + t.replace(newElm, elm, 1); + } + + return newElm || elm; + }, + + findCommonAncestor : function(a, b) { + var ps = a, pe; + + while (ps) { + pe = b; + + while (pe && ps != pe) + pe = pe.parentNode; + + if (ps == pe) + break; + + ps = ps.parentNode; + } + + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; + + return ps; + }, + + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + + function hex(s) { + s = parseInt(s).toString(16); + + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; + + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + + return s; + } + + return s; + }, + + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + + if (t.classes) + return t.classes; + + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); + + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); + + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; + + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + + // Filter classes + if (f && !(v = f(v, ov))) + return; + + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; + + // Import + case 3: + addClasses(r.styleSheet); + break; + } + }); + }; + + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } + + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); + } + }); + + return o; + } + + return f.call(s, e); + }, + + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; + } + + return n.attributes; + }, + + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, parentNode; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    + parentNode = node.parentNode; + if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) { + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } + } + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); + } + + return true; + }, + + destroy : function(s) { + var t = this; + + if (t.events) + t.events.destroy(); + + t.win = t.doc = t.root = t.events = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); + }, + + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; + + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + idx++; + lastNodeType = nodeType; + } + } + + return idx; + }, + + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; + } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; + } + }, + + bind : function(target, name, func, scope) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.add(target, name, func, scope || this); + }, + + unbind : function(target, name, func) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.remove(target, name, func); + }, + + + _findSib : function(node, selector, name) { + var t = this, f = selector; + + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _isRes : function(c) { + // Is live resizble element + return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); + } + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); + } + */ + + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; + + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } + + return s; + } + */ + }); + + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); + +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; + + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, + + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange + }); + + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; + + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; + + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; + + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; + + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; + + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; + } + + t.collapsed = TRUE; + }; + + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; + + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; + + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; + + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; + + function deleteContents() { + _traverse(DELETE); + }; + + function extractContents() { + return _traverse(EXTRACT); + }; + + function cloneContents() { + return _traverse(CLONE); + }; + + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; + + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; + + function surroundContents(n) { + var f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; + + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; + + // Private methods + + function _getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child) + return child; + + return container; + }; + + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; + + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal + + if (offsetA < offsetB) + return -1; // before + + return 1; // after + } + + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; + + if (c) { + offsetC = 0; + n = containerA.firstChild; + + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + + if (offsetA <= offsetC) + return -1; // before + + return 1; // after + } + + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } + + if (c) { + offsetC = 0; + n = containerB.firstChild; + + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } + + if (offsetC < offsetB) + return -1; // before + + return 1; // after + } + + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; + + if (!childA) + childA = cmnRoot; + + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; + + if (!childB) + childB = cmnRoot; + + if (childA == childB) + return 0; // equal + + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before + + if (n == childB) + return 1; // after + + n = n.nextSibling; + } + }; + + function _setEndPoint(st, n, o) { + var ec, sc; + + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; + } + + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); + + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; + + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); + + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } + + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } + + return _traverseCommonAncestors(startNode, endNode, how); + }; + + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; + + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } + + if (how == DELETE) + return; + + frag.appendChild(doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + } + + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; + } + + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + }; + + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = n.cloneNode(FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return n.cloneNode(FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? n.cloneNode(TRUE) : n; + + n.parentNode.removeChild(n); + }; + }; + + ns.Range = Range; +})(tinymce.dom); + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng); + + // Fix for edge case:
    ..
    ab|c
    + if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) { + checkRng = rng.duplicate(); + checkRng.collapse(start); + + offset = -1; + while (parent == checkRng.parentElement()) { + if (checkRng.move('character', -1) == 0) + break; + + offset++; + } + } + + offset = offset || checkRng.text.replace('\r\n', ' ').length; + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng); + + // Get the length of the text to find where the endpoint is relative to it's container + offset = checkRng.text.replace('\r\n', ' ').length; + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var node, parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root && parent.nodeType !== 9) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else { + // Empty node selection for example
    |
    + marker = doc.createTextNode('\uFEFF'); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { + if (startOffset == endOffset - 1) { + try { + ctrlRng = body.createControlRange(); + ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.select(); + return; + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); + + +/* + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), + soFar = selector, ret, cur, pop, i; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return (/h\d/i).test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return (/input|select|textarea|button/i).test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
    "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +Sizzle.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +window.tinymce.dom.Sizzle = Sizzle; + +})(); + + +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + + tinymce.create('tinymce.dom.EventUtils', { + EventUtils : function() { + this.inits = []; + this.events = []; + }, + + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; + + if (n instanceof Array) { + r = []; + + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + o = DOM.get(o); + + if (!o) + return; + + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; + + e = e || window.event; + + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; + + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } + + if (!s) + return f(e); + + return f.call(s, e); + }; + + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; + } + + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); + + return cb; + } + + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); + + t._add(o, n, cb); + + return f; + }, + + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); + + return r; + } + + o = DOM.get(o); + + each(a, function(e, i) { + if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { + a.splice(i, 1); + t._remove(o, n, e.cfunc); + s = true; + return false; + } + }); + + return s; + }, + + clear : function(o) { + var t = this, a = t.events, i, e; + + if (o) { + o = DOM.get(o); + + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; + + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } + }, + + cancel : function(e) { + if (!e) + return false; + + this.stop(e); + + return this.prevent(e); + }, + + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; + + return false; + }, + + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + + return false; + }, + + destroy : function() { + var t = this; + + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); + + t.events = []; + t = null; + }, + + _add : function(o, n, f) { + if (o.attachEvent) + o.attachEvent('on' + n, f); + else if (o.addEventListener) + o.addEventListener(n, f, false); + else + o['on' + n] = f; + }, + + _remove : function(o, n, f) { + if (o) { + try { + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; + } catch (ex) { + // Might fail with permission denined on IE so we just ignore that + } + } + }, + + _pageInit : function(win) { + var t = this; + + // Keep it from running more than once + if (t.domLoaded) + return; + + t.domLoaded = true; + + each(t.inits, function(c) { + c(); + }); + + t.inits = []; + }, + + _wait : function(win) { + var t = this, doc = win.document; + + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } + + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); + + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; + + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + t._pageInit(win); + })(); + } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } + + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, + + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, + + stopPropagation : function() { + this.cancelBubble = true; + } + } + }); + + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); + + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; + + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); + + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/) + , function(k) { + t[k] = function() { + var a = [id], i; + + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); + + a = dom[k].apply(dom, a); + t.update(k); + + return a; + }; + }); + + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, + + getSize : function() { + var n = dom.get(t.id); + + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = t.getXY(); + + t.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = t.getSize(); + + t.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var b; + + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; + + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; + + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } + + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); + + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); + } + } + }); + }; +})(tinymce); + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + + // Add events + each([ + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); + + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); + + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); + + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, + + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); + }, + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); + + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + + if (r.cloneContents) { + n = r.cloneContents(); + + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); + + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; + + if (/\s+$/.test(e.innerHTML)) + wa = ' '; + + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; + }, + + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; + + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; + + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; + + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); + + if (doc.body.childNodes.length == 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); + + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } + } + + // Move to caret marker + caretNode = self.dom.get('__caret'); + + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); + + // Remove the caret position + self.dom.remove('__caret'); + + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } + + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); + } else + rng.pasteHTML(content); + } + + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, + + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; + + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); + + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); + + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; + } + } + + return startElement; + } else { + startElement = rng.startContainer; + + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; + + return startElement; + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e, eo; + + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild || e; + + return e; + } else { + e = r.endContainer; + eo = r.endOffset; + + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; + + if (e && e.nodeType == 3) + return e.parentNode; + + return e; + } + }, + + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + + function findIndex(name, element) { + var index = 0; + + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); + + return index; + }; + + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } + + point.push(offset); + } else { + childNodes = container.childNodes; + + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } + + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } + + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); + + return point; + }; + + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (t.tridentSel) + return t.tridentSel.getBookmark(type); + + return getLocation(); + } + + // Handle simple range + if (type) + return {rng : t.getRng()}; + + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; + + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); + + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) == 0) + rng2.move('character', -1); + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; + + // W3C method + rng2 = rng.cloneRange(); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } + + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); + + return {id : id}; + }, + + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; + + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) + return; + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); + + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } + + return true; + }; + + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); + + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; + + function addBogus(node) { + // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly + if (dom.isBlock(node) && !node.innerHTML) + node.innerHTML = !isIE ? '
    ' : ' '; + + return node; + }; + + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); + + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); + } + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) + t.setRng(bookmark.rng); + } + }, + + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; + + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + setPoint(node, 1); + setPoint(node); + } + + t.setRng(rng); + } + + return node; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; + + return !s || r.collapsed; + }, + + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; + + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } + + rng.collapse(!!to_start); + self.setRng(rng); + }, + + getSel : function() { + var t = this, w = this.win; + + return w.getSelection ? w.getSelection() : w.document.selection; + }, + + getRng : function(w3c) { + var t = this, s, r, elm, doc = t.win.document; + + // Found tridentSel object then we need to use that one + if (w3c && t.tridentSel) + return t.tridentSel.getRangeAt(0); + + try { + if (s = t.getSel()) + r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } + + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + r = doc.createRange(); + r.setStartBefore(elm); + r.setEndAfter(elm); + } + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!r) + r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; + } + } + + return r; + }, + + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); + + if (s) { + t.explicitRange = r; + + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } + + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } + + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } + }, + + setNode : function(n) { + var t = this; + + t.setContent(t.dom.getOuterHTML(n)); + + return n; + }, + + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; + + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); + + if (rng.setStart) { + elm = rng.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } + + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + } + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } + + if (start && start === end) + return start; + } + } + + if (elm && elm.nodeType == 3) + return elm.parentNode; + + return elm; + } + + return rng.item ? rng.item(0) : rng.parentElement(); + }, + + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; + + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); + + if (sb) + bl.push(sb); + + if (sb && eb && sb != eb) { + n = sb; + + var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot()); + while ((n = walker.next()) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } + + if (eb && sb != eb) + bl.push(eb); + + return bl; + }, + + normalize : function() { + var self = this, rng, normalized; + + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; + + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node; + + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; + + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = container.body; + offset = 0; + } + + // If the container is body try move it into the closest text node or position + // TODO: Add more logic here to handle element selection cases + if (container === body) { + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes()) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new tinymce.dom.TreeWalker(container, body); + do { + // Found a text node use that position + if (node.nodeType === 3) { + offset = start ? 0 : node.nodeValue.length - 1; + container = node; + break; + } + + // Found a BR element that we can place the caret before + if (node.nodeName === 'BR') { + offset = dom.nodeIndex(node); + container = node.parentNode; + break; + } + } while (node = (start ? walker.next() : walker.prev())); + + normalized = true; + } + } + } + + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; + + rng = self.getRng(); + + // Normalize the end points + normalizeEndPoint(true); + + if (rng.collapsed) + normalizeEndPoint(); + + // Set the selection if it was normalized + if (normalized) { + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng); + } + }, + + destroy : function(s) { + var t = this; + + t.win = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + }; + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); + + pointRng.select(); + } + } else + endSelection(); + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + }; + + // Detect when user selects outside BODY + dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { + if (e.target.nodeName === 'HTML') { + if (started) + endSelection(); + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Serializer = function(settings, dom, schema) { + var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; + + // Support the old apply_source_formatting option + if (!settings.apply_source_formatting) + settings.indent = false; + + settings.remove_trailing_brs = true; + + // Default DOM and Schema if they are undefined + dom = dom || tinymce.DOM; + schema = schema || new tinymce.html.Schema(settings); + settings.entity_encoding = settings.entity_encoding || 'named'; + + onPreProcess = new tinymce.util.Dispatcher(self); + + onPostProcess = new tinymce.util.Dispatcher(self); + + htmlParser = new tinymce.html.DomParser(settings, schema); + + // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed + htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; + + while (i--) { + node = nodes[i]; + + value = node.attributes.map[internalName]; + if (value !== undef) { + // Set external name to internal value and remove internal + node.attr(name, value.length > 0 ? value : null); + node.attr(internalName, null); + } else { + // No internal attribute found then convert the value we have in the DOM + value = node.attributes.map[name]; + + if (name === "style") + value = dom.serializeStyle(dom.parseStyle(value), node.name); + else if (urlConverter) + value = urlConverter.call(urlConverterScope, value, name, node.name); + + node.attr(name, value.length > 0 ? value : null); + } + } + }); + + // Remove internal classes mceItem<..> + htmlParser.addAttributeFilter('class', function(nodes, name) { + var i = nodes.length, node, value; + + while (i--) { + node = nodes[i]; + value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, ''); + node.attr('class', value.length > 0 ? value : null); + } + }); + + // Remove bookmark elements + htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) + node.remove(); + } + }); + + // Force script into CDATA sections and remove the mce- prefix also add comments around styles + htmlParser.addNodeFilter('script,style', function(nodes, name) { + var i = nodes.length, node, value; + + function trim(value) { + return value.replace(/()/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); + }; + + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; + + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); + + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); + + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); + + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); + + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; + + while (i--) { + node = nodes[i]; + parentNode = node.parent; + + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } + } + }); + } + + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + // Return public methods + return { + schema : schema, + + addNodeFilter : htmlParser.addNodeFilter, + + addAttributeFilter : htmlParser.addAttributeFilter, + + onPreProcess : onPreProcess, + + onPostProcess : onPostProcess, + + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; + + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); + + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; + + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } + + args = args || {}; + args.format = args.format || 'html'; + + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } + + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); + + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args) + ); + + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF|\u200B/g, ''); + + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); + + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; + + args.node = null; + + return args.content; + }, + + addRules : function(rules) { + schema.addValidElements(rules); + }, + + setRules : function(rules) { + schema.setValidElements(rules); + } + }; + }; +})(tinymce); +(function(tinymce) { + tinymce.dom.ScriptLoader = function(settings) { + var QUEUED = 0, + LOADING = 1, + LOADED = 2, + states = {}, + queue = [], + scriptLoadedCallbacks = {}, + queueLoadedCallbacks = [], + loading = 0, + undefined; + + function loadScript(url, callback) { + var t = this, dom = tinymce.DOM, elm, uri, loc, id; + + // Execute callback when script is loaded + function done() { + dom.remove(id); + + if (elm) + elm.onreadystatechange = elm.onload = elm = null; + + callback(); + }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; + + id = dom.uniqueId(); + + if (tinymce.isIE6) { + uri = new tinymce.util.URI(url); + loc = location; + + // If script is from same domain and we + // use IE 6 then use XHR since it's more reliable + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { + tinymce.util.XHR.send({ + url : tinymce._addVer(uri.getURI()), + success : function(content) { + // Create new temp script element + var script = dom.create('script', { + type : 'text/javascript' + }); + + // Evaluate script in global scope + script.text = content; + document.getElementsByTagName('head')[0].appendChild(script); + dom.remove(script); + + done(); + }, + + error : error + }); + + return; + } + } + + // Create new script element + elm = dom.create('script', { + id : id, + type : 'text/javascript', + src : tinymce._addVer(url) + }); + + // Add onload listener for non IE browsers since IE9 + // fires onload event before the script is parsed and executed + if (!tinymce.isIE) + elm.onload = done; + + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; + + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } + + // Most browsers support this feature so we report errors + // for those at least to help users track their missing plugins etc + // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option + /*elm.onerror = function() { + alert('Failed to load: ' + url); + };*/ + + // Add script to document + (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); + }; + + this.isDone = function(url) { + return states[url] == LOADED; + }; + + this.markDone = function(url) { + states[url] = LOADED; + }; + + this.add = this.load = function(url, callback, scope) { + var item, state = states[url]; + + // Add url to load queue + if (state == undefined) { + queue.push(url); + states[url] = QUEUED; + } + + if (callback) { + // Store away callback for later execution + if (!scriptLoadedCallbacks[url]) + scriptLoadedCallbacks[url] = []; + + scriptLoadedCallbacks[url].push({ + func : callback, + scope : scope || this + }); + } + }; + + this.loadQueue = function(callback, scope) { + this.loadScripts(queue, callback, scope); + }; + + this.loadScripts = function(scripts, callback, scope) { + var loadScripts; + + function execScriptLoadedCallbacks(url) { + // Execute URL callback functions + tinymce.each(scriptLoadedCallbacks[url], function(callback) { + callback.func.call(callback.scope); + }); + + scriptLoadedCallbacks[url] = undefined; + }; + + queueLoadedCallbacks.push({ + func : callback, + scope : scope || this + }); + + loadScripts = function() { + var loadingScripts = tinymce.grep(scripts); + + // Current scripts has been handled + scripts.length = 0; + + // Load scripts that needs to be loaded + tinymce.each(loadingScripts, function(url) { + // Script is already loaded then execute script callbacks directly + if (states[url] == LOADED) { + execScriptLoadedCallbacks(url); + return; + } + + // Is script not loading then start loading it + if (states[url] != LOADING) { + states[url] = LOADING; + loading++; + + loadScript(url, function() { + states[url] = LOADED; + loading--; + + execScriptLoadedCallbacks(url); + + // Load more scripts if they where added by the recently loaded script + loadScripts(); + }); + } + }); + + // No scripts are currently loading then execute all pending queue loaded callbacks + if (!loading) { + tinymce.each(queueLoadedCallbacks, function(callback) { + callback.func.call(callback.scope); + }); + + queueLoadedCallbacks.length = 0; + } + }; + + loadScripts(); + }; + }; + + // Global script loader + tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); +})(tinymce); + +tinymce.dom.TreeWalker = function(start_node, root_node) { + var node = start_node; + + function findSibling(node, start_name, sibling_name, shallow) { + var sibling, parent; + + if (node) { + // Walk into nodes if it has a start + if (!shallow && node[start_name]) + return node[start_name]; + + // Return the sibling if it has one + if (node != root_node) { + sibling = node[sibling_name]; + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { + sibling = parent[sibling_name]; + if (sibling) + return sibling; + } + } + } + }; + + this.current = function() { + return node; + }; + + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; + + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); + }; +}; + +(function(tinymce) { + tinymce.dom.RangeUtils = function(dom) { + var INVISIBLE_CHAR = '\uFEFF'; + + this.walk = function(rng, callback) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, + ancestor, startPoint, + endPoint, node, parent, siblings, nodes; + + // Handle table cell selection the table plugin enables + // you to fake select table cells and perform formatting actions on them + nodes = dom.select('td.mceSelected,th.mceSelected'); + if (nodes.length > 0) { + tinymce.each(nodes, function(node) { + callback([node]); + }); + + return; + } + + function exclude(nodes) { + var node; + + // First node is excluded + node = nodes[0]; + if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { + nodes.splice(0, 1); + } + + // Last node is excluded + node = nodes[nodes.length - 1]; + if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { + nodes.splice(nodes.length - 1, 1); + } + + return nodes; + }; + + function collectSiblings(node, name, end_node) { + var siblings = []; + + for (; node && node != end_node; node = node[name]) + siblings.push(node); + + return siblings; + }; + + function findEndPoint(node, root) { + do { + if (node.parentNode == root) + return node; + + node = node.parentNode; + } while(node); + }; + + function walkBoundary(start_node, end_node, next) { + var siblingName = next ? 'nextSibling' : 'previousSibling'; + + for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { + parent = node.parentNode; + siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); + + if (siblings.length) { + if (!next) + siblings.reverse(); + + callback(exclude(siblings)); + } + } + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) + startContainer = startContainer.childNodes[startOffset]; + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; + + // Same container + if (startContainer == endContainer) + return callback(exclude([startContainer])); + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(startContainer, endContainer); + + // Process left side + for (node = startContainer; node; node = node.parentNode) { + if (node === endContainer) + return walkBoundary(startContainer, ancestor, true); + + if (node === ancestor) + break; + } + + // Process right side + for (node = endContainer; node; node = node.parentNode) { + if (node === startContainer) + return walkBoundary(endContainer, ancestor); + + if (node === ancestor) + break; + } + + // Find start/end point + startPoint = findEndPoint(startContainer, ancestor) || startContainer; + endPoint = findEndPoint(endContainer, ancestor) || endContainer; + + // Walk left leaf + walkBoundary(startContainer, startPoint, true); + + // Walk the middle from start to end point + siblings = collectSiblings( + startPoint == startContainer ? startPoint : startPoint.nextSibling, + 'nextSibling', + endPoint == endContainer ? endPoint.nextSibling : endPoint + ); + + if (siblings.length) + callback(exclude(siblings)); + + // Walk right leaf + walkBoundary(endContainer, endPoint); + }; + + this.split = function(rng) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + function splitText(node, offset) { + return node.splitText(offset); + }; + + // Handle single text node + if (startContainer == endContainer && startContainer.nodeType == 3) { + if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { + endContainer = splitText(startContainer, startOffset); + startContainer = endContainer.previousSibling; + + if (endOffset > startOffset) { + endOffset = endOffset - startOffset; + startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + startOffset = 0; + } else { + endOffset = 0; + } + } + } else { + // Split startContainer text node if needed + if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { + startContainer = splitText(startContainer, startOffset); + startOffset = 0; + } + + // Split endContainer text node if needed + if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { + endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + } + } + + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + }; + + }; + + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; +})(tinymce); + +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.KeyboardNavigation', { + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + t.destroy = function() { + each(items, function(item) { + dom.unbind(dom.get(item.id), 'focus', itemFocussed); + dom.unbind(dom.get(item.id), 'blur', itemBlurred); + }); + + dom.unbind(dom.get(root), 'focus', rootFocussed); + dom.unbind(dom.get(root), 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + if (excludeFromTabOrder) { + dom.bind(item.id, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + dom.setAttrib(item.id, 'tabindex', tabindex); + dom.bind(dom.get(item.id), 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + dom.bind(dom.get(root), 'focus', rootFocussed); + dom.bind(dom.get(root), 'keydown', rootKeydown); + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten class names + var DOM = tinymce.DOM, is = tinymce.is; + + tinymce.create('tinymce.ui.Control', { + Control : function(id, s, editor) { + this.id = id; + this.settings = s = s || {}; + this.rendered = false; + this.onRender = new tinymce.util.Dispatcher(this); + this.classPrefix = ''; + this.scope = s.scope || this; + this.disabled = 0; + this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); + }, + + setDisabled : function(s) { + if (s != this.disabled) { + this.setAriaProperty('disabled', s); + + this.setState('Disabled', s); + this.setState('Enabled', !s); + this.disabled = s; + } + }, + + isDisabled : function() { + return this.disabled; + }, + + setActive : function(s) { + if (s != this.active) { + this.setState('Active', s); + this.active = s; + this.setAriaProperty('pressed', s); + } + }, + + isActive : function() { + return this.active; + }, + + setState : function(c, s) { + var n = DOM.get(this.id); + + c = this.classPrefix + c; + + if (s) + DOM.addClass(n, c); + else + DOM.removeClass(n, c); + }, + + isRendered : function() { + return this.rendered; + }, + + renderHTML : function() { + }, + + renderTo : function(n) { + DOM.setHTML(n, this.renderHTML()); + }, + + postRender : function() { + var t = this, b; + + // Set pending states + if (is(t.disabled)) { + b = t.disabled; + t.disabled = -1; + t.setDisabled(b); + } + + if (is(t.active)) { + b = t.active; + t.active = -1; + t.setActive(b); + } + }, + + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + + destroy : function() { + tinymce.dom.Event.clear(this.id); + } + }); +})(tinymce); +tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { + Container : function(id, s, editor) { + this.parent(id, s, editor); + + this.controls = []; + + this.lookup = {}; + }, + + add : function(c) { + this.lookup[c.id] = c; + this.controls.push(c); + + return c; + }, + + get : function(n) { + return this.lookup[n]; + } +}); + + +tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + this.setDisabled(true); + }, + + renderHTML : function() { + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); + } +}); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { + MenuItem : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceMenuItem'; + }, + + setSelected : function(s) { + this.setState('Selected', s); + this.setAriaProperty('checked', !!s); + this.selected = s; + }, + + isSelected : function() { + return this.selected; + }, + + postRender : function() { + var t = this; + + t.parent(); + + // Set pending state + if (is(t.selected)) + t.setSelected(t.selected); + } + }); +})(tinymce); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { + Menu : function(id, s) { + var t = this; + + t.parent(id, s); + t.items = {}; + t.collapsed = false; + t.menuCount = 0; + t.onAddItem = new tinymce.util.Dispatcher(this); + }, + + expand : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.expand) + o.expand(); + }, 'items', t); + } + + t.collapsed = false; + }, + + collapse : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.collapse) + o.collapse(); + }, 'items', t); + } + + t.collapsed = true; + }, + + isCollapsed : function() { + return this.collapsed; + }, + + add : function(o) { + if (!o.settings) + o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); + + this.onAddItem.dispatch(this, o); + + return this.items[o.id] = o; + }, + + addSeparator : function() { + return this.add({separator : true}); + }, + + addMenu : function(o) { + if (!o.collapse) + o = this.createMenu(o); + + this.menuCount++; + + return this.add(o); + }, + + hasMenus : function() { + return this.menuCount !== 0; + }, + + remove : function(o) { + delete this.items[o.id]; + }, + + removeAll : function() { + var t = this; + + walk(t, function(o) { + if (o.removeAll) + o.removeAll(); + else + o.remove(); + + o.destroy(); + }, 'items', t); + + t.items = {}; + }, + + createMenu : function(o) { + var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); + + m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); + + return m; + } + }); +})(tinymce); +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; + + tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { + DropMenu : function(id, s) { + s = s || {}; + s.container = s.container || DOM.doc.body; + s.offset_x = s.offset_x || 0; + s.offset_y = s.offset_y || 0; + s.vp_offset_x = s.vp_offset_x || 0; + s.vp_offset_y = s.vp_offset_y || 0; + + if (is(s.icons) && !s.icons) + s['class'] += ' mceNoIcons'; + + this.parent(id, s); + this.onShowMenu = new tinymce.util.Dispatcher(this); + this.onHideMenu = new tinymce.util.Dispatcher(this); + this.classPrefix = 'mceMenu'; + }, + + createMenu : function(s) { + var t = this, cs = t.settings, m; + + s.container = s.container || cs.container; + s.parent = t; + s.constrain = s.constrain || cs.constrain; + s['class'] = s['class'] || cs['class']; + s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; + s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; + m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); + + m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); + + return m; + }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, + + update : function() { + var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; + + tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth; + th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight; + + if (!DOM.boxModel) + t.element.setStyles({width : tw + 2, height : th + 2}); + else + t.element.setStyles({width : tw, height : th}); + + if (s.max_width) + DOM.setStyle(co, 'width', tw); + + if (s.max_height) { + DOM.setStyle(co, 'height', th); + + if (tb.clientHeight < s.max_height) + DOM.setStyle(co, 'overflow', 'hidden'); + } + }, + + showMenu : function(x, y, px) { + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; + + t.collapse(1); + + if (t.isMenuVisible) + return; + + if (!t.rendered) { + co = DOM.add(t.settings.container, t.renderNode()); + + each(t.items, function(o) { + o.postRender(); + }); + + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + } else + co = DOM.get('menu_' + t.id); + + // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug + if (!tinymce.isOpera) + DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); + + DOM.show(co); + t.update(); + + x += s.offset_x || 0; + y += s.offset_y || 0; + vp.w -= 4; + vp.h -= 4; + + // Move inside viewport if not submenu + if (s.constrain) { + w = co.clientWidth - ot; + h = co.clientHeight - ot; + mx = vp.x + vp.w; + my = vp.y + vp.h; + + if ((x + s.vp_offset_x + w) > mx) + x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); + + if ((y + s.vp_offset_y + h) > my) + y = Math.max(0, (my - s.vp_offset_y) - h); + } + + DOM.setStyles(co, {left : x , top : y}); + t.element.update(); + + t.isMenuVisible = 1; + t.mouseClickFunc = Event.add(co, 'click', function(e) { + var m; + + e = e.target; + + if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { + m = t.items[e.id]; + + if (m.isDisabled()) + return; + + dm = t; + + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + + if (m.settings.onclick) + m.settings.onclick(e); + + return Event.cancel(e); // Cancel to fix onbeforeunload problem + } + }); + + if (t.hasMenus()) { + t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { + var m, r, mi; + + e = e.target; + if (e && (e = DOM.getParent(e, 'tr'))) { + m = t.items[e.id]; + + if (t.lastMenu) + t.lastMenu.collapse(1); + + if (m.isDisabled()) + return; + + if (e && DOM.hasClass(e, cp + 'ItemSub')) { + //p = DOM.getPos(s.container); + r = DOM.getRect(e); + m.showMenu((r.x + r.w - ot), r.y - ot, r.x); + t.lastMenu = m; + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); + } + } + }); + } + + Event.add(co, 'keydown', t._keyHandler, t); + + t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + t._setupKeyboardNav(); + } + }, + + hideMenu : function(c) { + var t = this, co = DOM.get('menu_' + t.id), e; + + if (!t.isMenuVisible) + return; + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + DOM.hide(co); + t.isMenuVisible = 0; + + if (!c) + t.collapse(1); + + if (t.element) + t.element.hide(); + + if (e = DOM.get(t.id)) + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); + + t.onHideMenu.dispatch(t); + }, + + add : function(o) { + var t = this, co; + + o = t.parent(o); + + if (t.isRendered && (co = DOM.get('menu_' + t.id))) + t._add(DOM.select('tbody', co)[0], o); + + return o; + }, + + collapse : function(d) { + this.parent(d); + this.hideMenu(1); + }, + + remove : function(o) { + DOM.remove(o.id); + this.destroy(); + + return this.parent(o); + }, + + destroy : function() { + var t = this, co = DOM.get('menu_' + t.id); + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + + if (t.element) + t.element.remove(); + + DOM.remove(co); + }, + + renderNode : function() { + var t = this, s = t.settings, n, tb, co, w; + + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + + if (s.menu_line) + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); + +// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); + tb = DOM.add(n, 'tbody'); + + each(t.items, function(o) { + t._add(tb, o); + }); + + t.rendered = true; + + return w; + }, + + // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.select('#menu_' + t.id)[0]; + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, + + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; + } + }, + + _add : function(tb, o) { + var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; + + if (s.separator) { + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); + + if (n = ro.previousSibling) + DOM.addClass(n, 'mceLast'); + + return; + } + + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } + + DOM.addClass(it, s['class']); +// n = DOM.add(n, 'span', {'class' : 'item'}); + + ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); + + if (s.icon_src) + DOM.add(ic, 'img', {src : s.icon_src}); + + n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); + + if (o.settings.style) + DOM.setAttrib(n, 'style', o.settings.style); + + if (tb.childNodes.length == 1) + DOM.addClass(ro, 'mceFirst'); + + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) + DOM.addClass(ro, 'mceFirst'); + + if (o.collapse) + DOM.addClass(ro, cp + 'ItemSub'); + + if (n = ro.previousSibling) + DOM.removeClass(n, 'mceLast'); + + DOM.addClass(ro, 'mceLast'); + } + }); +})(tinymce); +(function(tinymce) { + var DOM = tinymce.DOM; + + tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { + Button : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceButton'; + }, + + renderHTML : function() { + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + l; + else + h += '' + (l ? '' + l + '' : ''); + + h += ''; + h += ''; + return h; + }, + + postRender : function() { + var t = this, s = t.settings; + + tinymce.dom.Event.add(t.id, 'click', function(e) { + if (!t.isDisabled()) + return s.onclick.call(s.scope, e); + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { + ListBox : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.items = []; + + t.onChange = new Dispatcher(t); + + t.onPostRender = new Dispatcher(t); + + t.onAdd = new Dispatcher(t); + + t.onRenderMenu = new tinymce.util.Dispatcher(this); + + t.classPrefix = 'mceListBox'; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + var t = this, e, o, label; + + if (idx != t.selectedIndex) { + e = DOM.get(t.id + '_text'); + label = DOM.get(t.id + '_voiceDesc'); + o = t.items[idx]; + + if (o) { + t.selectedValue = o.value; + t.selectedIndex = idx; + DOM.setHTML(e, DOM.encode(o.title)); + DOM.setHTML(label, t.settings.title + " - " + o.title); + DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); + } else { + DOM.setHTML(e, DOM.encode(t.settings.title)); + DOM.setHTML(label, DOM.encode(t.settings.title)); + DOM.addClass(e, 'mceTitle'); + t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); + } + e = 0; + } + }, + + add : function(n, v, o) { + var t = this; + + o = o || {}; + o = tinymce.extend(o, { + title : n, + value : v + }); + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = ''; + h += ''; + h += ''; + h += ''; + + return h; + }, + + showMenu : function() { + var t = this, p2, e = DOM.get(this.id), m; + + if (t.isDisabled() || t.items.length == 0) + return; + + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus + + // Select in menu + if (t.oldID) + m.items[t.oldID].setSelected(0); + + each(t.items, function(o) { + if (o.value === t.selectedValue) { + m.items[o.id].setSelected(1); + t.oldID = o.id; + } + }); + + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); + + //DOM.get(t.id + '_text').focus(); + }, + + hideMenu : function(e) { + var t = this; + + if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + t.menu.hideMenu(); + } + } + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : t.classPrefix + 'Menu mceNoIcons', + max_width : 150, + max_height : 150 + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + m.add({ + title : t.settings.title, + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + + each(t.items, function(o) { + // No value then treat it as a title + if (o.value === undefined) { + m.add({ + title : o.title, + role : "option", + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + } else { + o.id = DOM.uniqueId(); + o.role= "option"; + o.onclick = function() { + if (t.settings.onselect(o.value) !== false) + t.select(o.value); // Must be runned after + }; + + m.add(o); + } + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + postRender : function() { + var t = this, cp = t.classPrefix; + + Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { + if (!t._focused) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { + // Fake select on enter + v = t.selectedValue; + t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); + t.settings.onselect(v); + } + }); + } + + t._focused = 1; + }); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); + }); + } + + t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); + Event.clear(this.id + '_open'); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { + NativeListBox : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceNativeListBox'; + }, + + setDisabled : function(s) { + DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); + }, + + isDisabled : function() { + return DOM.get(this.id).disabled; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + DOM.get(this.id).selectedIndex = idx + 1; + this.selectedValue = this.items[idx] ? this.items[idx].value : null; + }, + + add : function(n, v, a) { + var o, t = this; + + a = a || {}; + a.value = v; + + if (t.isRendered()) + DOM.add(DOM.get(this.id), 'option', a, n); + + o = { + title : n, + value : v, + attribs : a + }; + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h, t = this; + + h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); + + each(t.items, function(it) { + h += DOM.createHTML('option', {value : it.value}, it.title); + }); + + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); + return h; + }, + + postRender : function() { + var t = this, ch, changeListenerAdded = true; + + t.rendered = true; + + function onChange(e) { + var v = t.items[e.target.selectedIndex - 1]; + + if (v && (v = v.value)) { + t.onChange.dispatch(t, v); + + if (t.settings.onselect) + t.settings.onselect(v); + } + }; + + Event.add(t.id, 'change', onChange); + + // Accessibility keyhandler + Event.add(t.id, 'keydown', function(e) { + var bf; + + Event.remove(t.id, 'change', ch); + changeListenerAdded = false; + + bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; + Event.add(t.id, 'change', onChange); + Event.remove(t.id, 'blur', bf); + }); + + //prevent default left and right keys on chrome - so that the keyboard navigation is used. + if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { + return Event.prevent(e); + } + + if (e.keyCode == 13 || e.keyCode == 32) { + onChange(e); + return Event.cancel(e); + } + }); + + t.onPostRender.dispatch(t, DOM.get(t.id)); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); + + this.onRenderMenu = new tinymce.util.Dispatcher(this); + + s.menu_container = s.menu_container || DOM.doc.body; + }, + + showMenu : function() { + var t = this, p1, p2, e = DOM.get(t.id), m; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + p1 = DOM.getPos(t.settings.menu_container); + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.vp_offset_x = p2.x; + m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.setState('Selected', 1); + + t.isMenuVisible = 1; + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : this.classPrefix + 'Menu', + icons : t.settings.icons + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + hideMenu : function(e) { + var t = this; + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + t.setState('Selected', 0); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + if (t.menu) + t.menu.hideMenu(); + } + + t.isMenuVisible = 0; + }, + + postRender : function() { + var t = this, s = t.settings; + + Event.add(t.id, 'click', function() { + if (!t.isDisabled()) { + if (s.onclick) + s.onclick(t.value); + + t.showMenu(); + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceSplitButton'; + }, + + renderHTML : function() { + var h, t = this, s = t.settings, h1; + + h = ''; + + if (s.image) + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); + else + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); + + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h += ''; + h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); + }, + + postRender : function() { + var t = this, s = t.settings, activate; + + if (s.onclick) { + activate = function(evt) { + if (!t.isDisabled()) { + s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } + }); + } + + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.addClass(t.id, 'mceSplitButtonHover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.removeClass(t.id, 'mceSplitButtonHover'); + }); + } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); + Event.clear(this.id); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; + + tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { + ColorSplitButton : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.settings = s = tinymce.extend({ + colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', + grid_width : 8, + default_color : '#888888' + }, t.settings); + + t.onShowMenu = new tinymce.util.Dispatcher(t); + + t.onHideMenu = new tinymce.util.Dispatcher(t); + + t.value = s.default_color; + }, + + showMenu : function() { + var t = this, r, p, e, p2; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + e = DOM.get(t.id); + DOM.show(t.id + '_menu'); + DOM.addClass(e, 'mceSplitButtonSelected'); + p2 = DOM.getPos(e); + DOM.setStyles(t.id + '_menu', { + left : p2.x, + top : p2.y + e.clientHeight, + zIndex : 200000 + }); + e = 0; + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.onShowMenu.dispatch(t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.isMenuVisible = 1; + }, + + hideMenu : function(e) { + var t = this; + + if (t.isMenuVisible) { + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } + + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + } + }, + + renderMenu : function() { + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; + + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); + DOM.add(m, 'span', {'class' : 'mceMenuLine'}); + + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); + tb = DOM.add(n, 'tbody'); + + // Generate color grid + i = 0; + each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { + c = c.replace(/^#/, ''); + + if (!i--) { + tr = DOM.add(tb, 'tr'); + i = s.grid_width - 1; + } + + n = DOM.add(tr, 'td'); + n = DOM.add(n, 'a', { + role : 'option', + href : 'javascript:;', + style : { + backgroundColor : '#' + c + }, + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c + }); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } + }); + + if (s.more_colors_func) { + n = DOM.add(tb, 'tr'); + n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); + n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + + Event.add(n, 'click', function(e) { + s.more_colors_func.call(s.more_colors_scope || this); + return Event.cancel(e); // Cancel to fix onbeforeunload problem + }); + } + + DOM.addClass(m, 'mceColorSplitMenu'); + + new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); + + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = DOM.getParent(e.target, 'a', tb); + + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) + t.setColor(c); + + return Event.cancel(e); // Prevent IE auto save warning + }); + + return w; + }, + + setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + displayColor : function(c) { + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); + + t.value = c; + }, + + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_menu'); + Event.clear(this.id + '_more'); + DOM.remove(this.id + '_menu'); + } + }); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + var t = this; + dom.get(t.id).focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + //Move focus if webkit so that navigation back will read the item. + if (tinymce.isWebKit) { + dom.get(t.editor.id+"_ifr").focus(); + } + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; +tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) + c += ' mceToolbarEndButton'; + else if (co.SplitButton) + c += ' mceToolbarEndSplitButton'; + else if (co.ListBox) + c += ' mceToolbarEndListBox'; + + h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); + + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); + } +}); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; + + tinymce.create('tinymce.AddOnManager', { + AddOnManager : function() { + var self = this; + + self.items = []; + self.urls = {}; + self.lookup = {}; + self.onAdd = new Dispatcher(self); + }, + + get : function(n) { + if (this.lookup[n]) { + return this.lookup[n].instance; + } else { + return undefined; + } + }, + + dependencies : function(n) { + var result; + if (this.lookup[n]) { + result = this.lookup[n].dependencies; + } + return result || []; + }, + + requireLangPack : function(n) { + var s = tinymce.settings; + + if (s && s.language && s.language_load !== false) + tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); + }, + + add : function(id, o, dependencies) { + this.items.push(o); + this.lookup[id] = {instance:o, dependencies:dependencies}; + this.onAdd.dispatch(this, id, o); + + return o; + }, + createUrl: function(baseUrl, dep) { + if (typeof dep === "object") { + return dep + } else { + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; + } + }, + + addComponents: function(pluginName, scripts) { + var pluginUrl = this.urls[pluginName]; + tinymce.each(scripts, function(script){ + tinymce.ScriptLoader.add(pluginUrl+"/"+script); + }); + }, + + load : function(n, u, cb, s) { + var t = this, url = u; + + function loadDependencies() { + var dependencies = t.dependencies(n); + tinymce.each(dependencies, function(dep) { + var newUrl = t.createUrl(u, dep); + t.load(newUrl.resource, newUrl, undefined, undefined); + }); + if (cb) { + if (s) { + cb.call(s); + } else { + cb.call(tinymce.ScriptLoader); + } + } + } + + if (t.urls[n]) + return; + if (typeof u === "object") + url = u.prefix + u.resource + u.suffix; + + if (url.indexOf('/') != 0 && url.indexOf('://') == -1) + url = tinymce.baseURL + '/' + url; + + t.urls[n] = url.substring(0, url.lastIndexOf('/')); + + if (t.lookup[n]) { + loadDependencies(); + } else { + tinymce.ScriptLoader.add(url, loadDependencies, s); + } + } + }); + + // Create plugin and theme managers + tinymce.PluginManager = new tinymce.AddOnManager(); + tinymce.ThemeManager = new tinymce.AddOnManager(); +}(tinymce)); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, extend = tinymce.extend, + DOM = tinymce.DOM, Event = tinymce.dom.Event, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode, + Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + + tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + tinymce.onBeforeUnload = new Dispatcher(tinymce); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + tinymce.onBeforeUnload.dispatch(tinymce, e); + }); + + tinymce.onAddEditor = new Dispatcher(tinymce); + + tinymce.onRemoveEditor = new Dispatcher(tinymce); + + tinymce.EditorManager = extend(tinymce, { + editors : [], + + i18n : {}, + + activeEditor : null, + + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; + + function execCallback(se, n, s) { + var f = se[n]; + + if (!f) + return; + + if (tinymce.is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + } + + return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); + }; + + s = extend({ + theme : "simple", + language : "en" + }, s); + + t.settings = s; + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + switch (s.mode) { + case "exact": + l = s.elements || ''; + + if(l.length > 0) { + each(explode(l), function(v) { + if (DOM.get(v)) { + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } else { + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + instanceCounter++; + DOM.setAttrib(e, 'id', v); + + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } + }); + }); + } + }); + } + break; + + case "textareas": + case "specific_textareas": + function hasClass(n, c) { + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); + }; + + each(DOM.select('textarea'), function(v) { + if (s.editor_deselector && hasClass(v, s.editor_deselector)) + return; + + if (!s.editor_selector || hasClass(v, s.editor_selector)) { + // Can we use the name + e = DOM.get(v.name); + if (!v.id && !e) + v.id = v.name; + + // Generate unique name if missing or already exists + if (!v.id || t.get(v.id)) + v.id = DOM.uniqueId(); + + ed = new tinymce.Editor(v.id, s); + el.push(ed); + ed.render(1); + } + }); + break; + } + + // Call onInit when all editors are initialized + if (s.oninit) { + l = co = 0; + + each(el, function(ed) { + co++; + + if (!ed.initialized) { + // Wait for it + ed.onInit.add(function() { + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } else + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } + }); + }, + + get : function(id) { + if (id === undefined) + return this.editors; + + return this.editors[id]; + }, + + getInstanceById : function(id) { + return this.get(id); + }, + + add : function(editor) { + var self = this, editors = self.editors; + + // Add named and index editor instance + editors[editor.id] = editor; + editors.push(editor); + + self._setActive(editor); + self.onAddEditor.dispatch(self, editor); + + + return editor; + }, + + remove : function(editor) { + var t = this, i, editors = t.editors; + + // Not in the collection + if (!editors[editor.id]) + return null; + + delete editors[editor.id]; + + for (i = 0; i < editors.length; i++) { + if (editors[i] == editor) { + editors.splice(i, 1); + break; + } + } + + // Select another editor since the active one was removed + if (t.activeEditor == editor) + t._setActive(editors[0]); + + editor.destroy(); + t.onRemoveEditor.dispatch(t, editor); + + return editor; + }, + + execCommand : function(c, u, v) { + var t = this, ed = t.get(v), w; + + // Manager commands + switch (c) { + case "mceFocus": + ed.focus(); + return true; + + case "mceAddEditor": + case "mceAddControl": + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + + return true; + + case "mceAddFrameControl": + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE) { + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + w.attachEvent('onunload', clr); + } + + v.page_window = null; + + return true; + + case "mceRemoveEditor": + case "mceRemoveControl": + if (ed) + ed.remove(); + + return true; + + case 'mceToggleEditor': + if (!ed) { + t.execCommand('mceAddControl', 0, v); + return true; + } + + if (ed.isHidden()) + ed.show(); + else + ed.hide(); + + return true; + } + + // Run command on active editor + if (t.activeEditor) + return t.activeEditor.execCommand(c, u, v); + + return false; + }, + + execInstanceCommand : function(id, c, u, v) { + var ed = this.get(id); + + if (ed) + return ed.execCommand(c, u, v); + + return false; + }, + + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + addI18n : function(p, o) { + var lo, i18n = this.i18n; + + if (!tinymce.is(p, 'string')) { + each(p, function(o, lc) { + each(o, function(o, g) { + each(o, function(o, k) { + if (g === 'common') + i18n[lc + '.' + k] = o; + else + i18n[lc + '.' + g + '.' + k] = o; + }); + }); + }); + } else { + each(o, function(o, k) { + i18n[p + '.' + k] = o; + }); + } + }, + + // Private methods + + _setActive : function(editor) { + this.selectedInstance = this.activeEditor = editor; + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten these names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, + Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko, + isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; + + tinymce.create('tinymce.Editor', { + Editor : function(id, s) { + var t = this; + + t.id = t.editorId = id; + + t.execCommands = {}; + t.queryStateCommands = {}; + t.queryValueCommands = {}; + + t.isNotDirty = false; + + t.plugins = {}; + + // Add events to the editor + each([ + 'onPreInit', + + 'onBeforeRenderUI', + + 'onPostRender', + + 'onInit', + + 'onRemove', + + 'onActivate', + + 'onDeactivate', + + 'onClick', + + 'onEvent', + + 'onMouseUp', + + 'onMouseDown', + + 'onDblClick', + + 'onKeyDown', + + 'onKeyUp', + + 'onKeyPress', + + 'onContextMenu', + + 'onSubmit', + + 'onReset', + + 'onPaste', + + 'onPreProcess', + + 'onPostProcess', + + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent', + + 'onLoadContent', + + 'onSaveContent', + + 'onNodeChange', + + 'onChange', + + 'onBeforeExecCommand', + + 'onExecCommand', + + 'onUndo', + + 'onRedo', + + 'onVisualAid', + + 'onSetProgressState' + ], function(e) { + t[e] = new Dispatcher(t); + }); + + t.settings = s = extend({ + id : id, + language : 'en', + docs_language : 'en', + theme : 'simple', + skin : 'default', + delta_width : 0, + delta_height : 0, + popup_css : '', + plugins : '', + document_base_url : tinymce.documentBaseURL, + add_form_submit_trigger : 1, + submit_patch : 1, + add_unload_trigger : 1, + convert_urls : 1, + relative_urls : 1, + remove_script_host : 1, + table_inline_editing : 0, + object_resizing : 1, + cleanup : 1, + accessibility_focus : 1, + custom_shortcuts : 1, + custom_undo_redo_keyboard_shortcuts : 1, + custom_undo_redo_restore_selection : 1, + custom_undo_redo : 1, + doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll + visual_table_class : 'mceItemTable', + visual : 1, + font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', + font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size + apply_source_formatting : 1, + directionality : 'ltr', + forced_root_block : 'p', + hidden_input : 1, + padd_empty_editor : 1, + render_ui : 1, + init_theme : 1, + force_p_newlines : 1, + indentation : '30px', + keep_styles : 1, + fix_table_elements : 1, + inline_styles : 1, + convert_fonts_to_spans : true, + indent : 'simple', + indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + validate : true, + entity_encoding : 'named', + url_converter : t.convertURL, + url_converter_scope : t, + ie7_compat : true + }, s); + + t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, { + base_uri : tinyMCE.baseURI + }); + + t.baseURI = tinymce.baseURI; + + t.contentCSS = []; + + // Call setup + t.execCallback('setup', t); + }, + + render : function(nst) { + var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; + + // Page is not loaded yet, wait for it + if (!Event.domLoaded) { + Event.add(document, 'init', function() { + t.render(); + }); + return; + } + + tinyMCE.settings = s; + + // Element not found, then skip initialization + if (!t.getElement()) + return; + + // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff + // here since the browser says it has contentEditable support but there is no visible + // caret We will remove this check ones Apple implements full contentEditable support + if (tinymce.isIDevice && !tinymce.isIOS5) + return; + + // Add hidden input for non input elements inside form elements + if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) + DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); + + if (tinymce.WindowManager) + t.windowManager = new tinymce.WindowManager(t); + + if (s.encoding == 'xml') { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = DOM.encode(o.content); + }); + } + + if (s.add_form_submit_trigger) { + t.onSubmit.addToTop(function() { + if (t.initialized) { + t.save(); + t.isNotDirty = 1; + } + }); + } + + if (s.add_unload_trigger) { + t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { + if (t.initialized && !t.destroyed && !t.isHidden()) + t.save({format : 'raw', no_events : true}); + }); + } + + tinymce.addUnload(t.destroy, t); + + if (s.submit_patch) { + t.onBeforeRenderUI.add(function() { + var n = t.getElement().form; + + if (!n) + return; + + // Already patched + if (n._mceOldSubmit) + return; + + // Check page uses id="submit" or name="submit" for it's submit button + if (!n.submit.nodeType && !n.submit.length) { + t.formElement = n; + n._mceOldSubmit = n.submit; + n.submit = function() { + // Save all instances + tinymce.triggerSave(); + t.isNotDirty = 1; + + return t.formElement._mceOldSubmit(t.formElement); + }; + } + + n = null; + }); + } + + // Load scripts + function loadScripts() { + if (s.language && s.language_load !== false) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + each(explode(s.plugins), function(p) { + if (p &&!PluginManager.urls[p]) { + if (p.charAt(0) == '-') { + p = p.substr(1, p.length); + var dependencies = PluginManager.dependencies(p); + each(dependencies, function(dep) { + var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; + var dep = PluginManager.createUrl(defaultSettings, dep); + PluginManager.load(dep.resource, dep); + + }); + } else { + // Skip safari plugin, since it is removed as of 3.3b1 + if (p == 'safari') { + return; + } + PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); + } + } + }); + + // Init when que is loaded + sl.loadQueue(function() { + if (!t.removed) + t.init(); + }); + }; + + loadScripts(); + }, + + init : function() { + var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; + + tinymce.add(t); + + s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); + + if (s.theme) { + s.theme = s.theme.replace(/-/, ''); + o = ThemeManager.get(s.theme); + t.theme = new o(); + + if (t.theme.init && s.init_theme) + t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); + } + function initPlugin(p) { + var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; + if (c && tinymce.inArray(initializedPlugins,p) === -1) { + each(PluginManager.dependencies(p), function(dep){ + initPlugin(dep); + }); + po = new c(t, u); + + t.plugins[p] = po; + + if (po.init) { + po.init(t, u); + initializedPlugins.push(p); + } + } + } + + // Create all plugins + each(explode(s.plugins.replace(/\-/g, '')), initPlugin); + + // Setup popup CSS path(s) + if (s.popup_css !== false) { + if (s.popup_css) + s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); + else + s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); + } + + if (s.popup_css_add) + s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); + + t.controlManager = new tinymce.ControlManager(t); + + if (s.custom_undo_redo) { + t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.beforeChange(); + }); + + t.onExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.add(); + }); + } + + t.onExecCommand.add(function(ed, c) { + // Don't refresh the select lists until caret move + if (!/^(FontName|FontSize)$/.test(c)) + t.nodeChanged(); + }); + + // Remove ghost selections on images and tables in Gecko + if (isGecko) { + function repaint(a, o) { + if (!o || !o.initial) + t.execCommand('mceRepaint'); + }; + + t.onUndo.add(repaint); + t.onRedo.add(repaint); + t.onSetContent.add(repaint); + } + + // Enables users to override the control factory + t.onBeforeRenderUI.dispatch(t, t.controlManager); + + // Measure box + if (s.render_ui) { + w = s.width || e.style.width || e.offsetWidth; + h = s.height || e.style.height || e.offsetHeight; + t.orgDisplay = e.style.display; + re = /^[0-9\.]+(|px)$/i; + + if (re.test('' + w)) + w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100); + + if (re.test('' + h)) + h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100); + + // Render UI + o = t.theme.renderUI({ + targetNode : e, + width : w, + height : h, + deltaWidth : s.delta_width, + deltaHeight : s.delta_height + }); + + t.editorContainer = o.editorContainer; + } + + + // User specified a document.domain value + if (document.domain && location.hostname != document.domain) + tinymce.relaxedDomain = document.domain; + + // Resize editor + DOM.setStyles(o.sizeContainer || o.editorContainer, { + width : w, + height : h + }); + + // Load specified content CSS last + if (s.content_css) { + tinymce.each(explode(s.content_css), function(u) { + t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); + }); + } + + h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); + if (h < 100) + h = 100; + + t.iframeHTML = s.doctype + ''; + + // We only need to override paths if we have to + // IE has a bug where it remove site absolute urls to relative ones if this is specified + if (s.document_base_url != tinymce.documentBaseURL) + t.iframeHTML += ''; + + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + + t.iframeHTML += ''; + + // Load the CSS by injecting them into the HTML this will reduce "flicker" + for (i = 0; i < t.contentCSS.length; i++) { + t.iframeHTML += ''; + } + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += '
    '; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; + } + + // Create iframe + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + allowTransparency : "true", + title : s.aria_label, + style : { + width : '100%', + height : h, + display : 'block' // Important for Gecko to render the iframe correctly + } + }); + + t.contentAreaContainer = o.iframeContainer; + DOM.get(o.editorContainer).style.display = t.orgDisplay; + DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); + + if (!tinymce.relaxedDomain || !u) + t.setupIframe(); + + e = n = o = null; // Cleanup + }, + + setupIframe : function() { + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; + + // Setup iframe body + if (!isIE || !tinymce.relaxedDomain) { + d.open(); + d.write(t.iframeHTML); + d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; + } + + // It will not steal focus while setting contentEditable + b = t.getBody(); + b.disabled = true; + + if (!s.readonly) + b.contentEditable = true; + + b.disabled = false; + + t.schema = new tinymce.html.Schema(s); + + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + update_styles : 1, + fix_ie_paragraphs : 1, + schema : t.schema + }); + + t.parser = new tinymce.html.DomParser(s, t.schema); + + // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. + if (!t.settings.allow_html_in_named_anchor) { + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + } + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + if (name === "style") + node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr(internalName, t.convertURL(value, name, node.name)); + } + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); + } + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); + + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + + t.formatter = new tinymce.Formatter(this); + + // Register default formats + t.formatter.register({ + alignleft : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} + ], + + bold : [ + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b', remove : 'all'} + ], + + italic : [ + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i', remove : 'all'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u', remove : 'all'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'strike', remove : 'all'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, + + link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, + onmatch : function(node) { + return true; + }, + + onformat : function(elm, fmt, vars) { + each(vars, function(value, key) { + t.dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + t.formatter.register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + t.formatter.register(t.settings.formats); + + t.undoManager = new tinymce.UndoManager(t); + + // Pass through + t.undoManager.onAdd.add(function(um, l) { + if (um.hasUndo()) + return t.onChange.dispatch(t, l, um); + }); + + t.undoManager.onUndo.add(function(um, l) { + return t.onUndo.dispatch(t, l, um); + }); + + t.undoManager.onRedo.add(function(um, l) { + return t.onRedo.dispatch(t, l, um); + }); + + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + + if (!s.gecko_spellcheck) + t.getBody().spellcheck = 0; + + if (!s.readonly) + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + t.quirks = new tinymce.util.Quirks(this); + + if (s.directionality) + t.getBody().dir = s.directionality; + + if (s.nowrap) + t.getBody().style.whiteSpace = "nowrap"; + + if (s.handle_node_change_callback) { + t.onNodeChange.add(function(ed, cm, n) { + t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); + }); + } + + if (s.save_callback) { + t.onSaveContent.add(function(ed, o) { + var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); + + if (h) + o.content = h; + }); + } + + if (s.onchange_callback) { + t.onChange.add(function(ed, l) { + t.execCallback('onchange_callback', t, l); + }); + } + + if (s.protect) { + t.onBeforeSetContent.add(function(ed, o) { + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } + }); + } + + if (s.convert_newlines_to_brs) { + t.onBeforeSetContent.add(function(ed, o) { + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
    '); + }); + } + + if (s.preformatted) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^\s*/, ''); + o.content = o.content.replace(/<\/pre>\s*$/, ''); + + if (o.set) + o.content = '
    ' + o.content + '
    '; + }); + } + + if (s.verify_css_classes) { + t.serializer.attribValueFilter = function(n, v) { + var s, cl; + + if (n == 'class') { + // Build regexp for classes + if (!t.classesRE) { + cl = t.dom.getClasses(); + + if (cl.length > 0) { + s = ''; + + each (cl, function(o) { + s += (s ? '|' : '') + o['class']; + }); + + t.classesRE = new RegExp('(' + s + ')', 'gi'); + } + } + + return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; + } + + return v; + }; + } + + if (s.cleanup_callback) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + t.onPreProcess.add(function(ed, o) { + if (o.set) + t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + t.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + if (s.save_callback) { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); + }); + } + + if (s.handle_event_callback) { + t.onEvent.add(function(ed, e, o) { + if (t.execCallback('handle_event_callback', e, ed, o) === false) + Event.cancel(e); + }); + } + + // Add visual aids when new contents is added + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + // Remove empty contents + if (s.padd_empty_editor) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); + }); + } + + if (isGecko) { + // Fix gecko link bug, when a link is placed at the end of block elements there is + // no way to move the caret behind the link. This fix adds a bogus br element after the link + function fixLinks(ed, o) { + each(ed.dom.select('a'), function(n) { + var pn = n.parentNode; + + if (ed.dom.isBlock(pn) && pn.lastChild === n) + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); + }); + }; + + t.onExecCommand.add(function(ed, cmd) { + if (cmd === 'CreateLink') + fixLinks(ed); + }); + + t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); + } + + t.load({initial : true, format : 'html'}); + t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); + t.initialized = true; + + t.onInit.dispatch(t); + t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); + t.execCallback('init_instance_callback', t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); + + // Handle auto focus + if (s.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(s.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getBody().focus(); + ed.getWin().focus(); + }, 100); + } + + e = null; + }, + + + focus : function(sf) { + var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); + + if (!sf) { + // Get selected control element + ieRng = selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + t._refreshContentEditable(); + selection.normalize(); + + // Is not content editable + if (!ce) + t.getWin().focus(); + + // Focus the body as well since it's contentEditable + if (tinymce.isGecko) { + t.getBody().focus(); + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + + } + + if (tinymce.activeEditor != t) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + tinymce._setActive(t); + }, + + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getStart() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + o = o || {}; + n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + t.dom.getParent(n, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + n, + s.isCollapsed(), + o + ); + } + }, + + addButton : function(n, s) { + var t = this; + + t.buttons = t.buttons || {}; + t.buttons[n] = s; + }, + + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; + }, + + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (!t.settings.custom_shortcuts) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : desc, + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + o = {}; + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); + if (o.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + show : function() { + var t = this; + + DOM.show(t.getContainer()); + DOM.hide(t.id); + t.load(); + }, + + hide : function() { + var t = this, d = t.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && d) + d.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + t.save(); + DOM.hide(t.getContainer()); + DOM.setStyle(t.id, 'display', t.orgDisplay); + }, + + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + // Add undo level will trigger onchange event + if (!o.no_events) { + t.undoManager.typing = false; + t.undoManager.add(); + } + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(), forcedRootBlockName; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + forcedRootBlockName = self.settings.forced_root_block; + if (forcedRootBlockName) + content = '<' + forcedRootBlockName + '>
    '; + else + content = '
    '; + + body.innerHTML = content; + self.selection.select(body, true); + self.selection.collapse(true); + return; + } + + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); + } + + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + self.selection.normalize(); + + return args.content; + }, + + getContent : function(args) { + var self = this, content; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; + + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); + + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); + + args.content = tinymce.trim(content); + + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); + + return args.content; + }, + + isDirty : function() { + var self = this; + + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; + }, + + getContainer : function() { + var t = this; + + if (!t.container) + t.container = DOM.get(t.editorContainer || t.id + '_parent'); + + return t.container; + }, + + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + getWin : function() { + var t = this, e; + + if (!t.contentWindow) { + e = DOM.get(t.id + "_ifr"); + + if (e) + t.contentWindow = e.contentWindow; + } + + return t.contentWindow; + }, + + getDoc : function() { + var t = this, w; + + if (!t.contentDocument) { + w = t.getWin(); + + if (w) + t.contentDocument = w.document; + } + + return t.contentDocument; + }, + + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + convertURL : function(u, n, e) { + var t = this, s = t.settings; + + // Use callback instead + if (s.urlconverter_callback) + return t.execCallback('urlconverter_callback', u, e, true, n); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) + return u; + + // Convert to relative + if (s.relative_urls) + return t.documentBaseURI.toRelative(u); + + // Convert to absolute + u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); + + return u; + }, + + addVisual : function(e) { + var t = this, s = t.settings; + + e = e || t.getBody(); + + if (!is(t.hasVisual)) + t.hasVisual = s.visual; + + each(t.dom.select('table,a', e), function(e) { + var v; + + switch (e.nodeName) { + case 'TABLE': + v = t.dom.getAttrib(e, 'border'); + + if (!v || v == '0') { + if (t.hasVisual) + t.dom.addClass(e, s.visual_table_class); + else + t.dom.removeClass(e, s.visual_table_class); + } + + return; + + case 'A': + v = t.dom.getAttrib(e, 'name'); + + if (v) { + if (t.hasVisual) + t.dom.addClass(e, 'mceItemAnchor'); + else + t.dom.removeClass(e, 'mceItemAnchor'); + } + + return; + } + }); + + t.onVisualAid.dispatch(t, e, t.hasVisual); + }, + + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + tinymce.remove(t); + DOM.remove(e); + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _addEvents : function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var t = this, i, s = t.settings, dom = t.dom, lo = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + function eventHandler(e, o) { + var ty = e.type; + + // Don't fire events when it's removed + if (t.removed) + return; + + // Generic event handler + if (t.onEvent.dispatch(t, e, o) !== false) { + // Specific event handler + t[lo[e.fakeType || e.type]].dispatch(t, e, o); + } + }; + + // Add DOM events + each(lo, function(v, k) { + switch (k) { + case 'contextmenu': + dom.bind(t.getDoc(), k, eventHandler); + break; + + case 'paste': + dom.bind(t.getBody(), k, function(e) { + eventHandler(e); + }); + break; + + case 'submit': + case 'reset': + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + break; + + default: + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + } + }); + + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + t.focus(true); + }); + + + // Fixes bug where a specified document_base_uri could result in broken images + // This will also fix drag drop of images in Gecko + if (tinymce.isGecko) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + var v; + + e = e.target; + + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) + e.src = t.documentBaseURI.toAbsolute(v); + }); + } + + // Set various midas options in Gecko + if (isGecko) { + function setOpts() { + var t = this, d = t.getDoc(), s = t.settings; + + if (isGecko && !s.readonly) { + t._refreshContentEditable(); + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, false); + } catch (ex) { + // Use old method + if (!t._isHidden()) + try {d.execCommand("useCSS", 0, true);} catch (ex) {} + } + + if (!s.table_inline_editing) + try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} + + if (!s.object_resizing) + try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} + } + }; + + t.onBeforeExecCommand.add(setOpts); + t.onMouseDown.add(setOpts); + } + + // Add node change handlers + t.onMouseUp.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); + t.onKeyUp.add(function(ed, e) { + var c = e.keyCode; + + if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) + t.nodeChanged(); + }); + + + // Add block quote deletion handler + t.onKeyDown.add(function(ed, e) { + // Was the BACKSPACE key pressed? + if (e.keyCode != 8) + return; + + var n = ed.selection.getRng().startContainer; + var offset = ed.selection.getRng().startOffset; + + while (n && n.nodeType && n.nodeType != 1 && n.parentNode) + n = n.parentNode; + + // Is the cursor at the beginning of a blockquote? + if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) { + // Remove the blockquote + ed.formatter.toggle('blockquote', null, n.parentNode); + + // Move the caret to the beginning of n + var rng = ed.selection.getRng(); + rng.setStart(n, 0); + rng.setEnd(n, 0); + ed.selection.setRng(rng); + ed.selection.collapse(false); + } + }); + + + + // Add reset handler + t.onReset.add(function() { + t.setContent(t.startContent, {format : 'raw'}); + }); + + // Add shortcuts + if (s.custom_shortcuts) { + if (s.custom_undo_redo_keyboard_shortcuts) { + t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); + t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); + } + + // Add default shortcuts for gecko + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); + + // BlockFormat shortcuts keys + for (i=1; i<=6; i++) + t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + + t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); + t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); + + function find(e) { + var v = null; + + if (!e.altKey && !e.ctrlKey && !e.metaKey) + return v; + + each(t.shortcuts, function(o) { + if (tinymce.isMac && o.ctrl != e.metaKey) + return; + else if (!tinymce.isMac && o.ctrl != e.ctrlKey) + return; + + if (o.alt != e.altKey) + return; + + if (o.shift != e.shiftKey) + return; + + if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { + v = o; + return false; + } + }); + + return v; + }; + + t.onKeyUp.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyPress.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyDown.add(function(ed, e) { + var o = find(e); + + if (o) { + o.func.call(o.scope); + return Event.cancel(e); + } + }); + } + + if (tinymce.isIE) { + // Fix so resize will only update the width and height attributes not the styles of an image + // It will also block mceItemNoResize items + dom.bind(t.getDoc(), 'controlselect', function(e) { + var re = t.resizeInfo, cb; + + e = e.target; + + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + + if (re) + dom.unbind(re.node, re.ev, re.cb); + + if (!dom.hasClass(e, 'mceItemNoResize')) { + ev = 'resizeend'; + cb = dom.bind(e, ev, function(e) { + var v; + + e = e.target; + + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); + } + + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); + } + }); + } else { + ev = 'resizestart'; + cb = dom.bind(e, 'resizestart', Event.cancel, Event); + } + + re = t.resizeInfo = { + node : e, + ev : ev, + cb : cb + }; + }); + } + + if (tinymce.isOpera) { + t.onClick.add(function(ed, e) { + Event.prevent(e); + }); + } + + // Add custom undo/redo handlers + if (s.custom_undo_redo) { + function addUndo() { + t.undoManager.typing = false; + t.undoManager.add(); + }; + + dom.bind(t.getDoc(), 'focusout', function(e) { + if (!t.removed && t.undoManager.typing) + addUndo(); + }); + + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + + t.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey) + addUndo(); + }); + + t.onKeyDown.add(function(ed, e) { + var keyCode = e.keyCode, sel; + + if (keyCode == 8) { + sel = t.getDoc().selection; + + // Fix IE control + backspace browser bug + if (sel && sel.createRange && sel.createRange().item) { + t.undoManager.beforeChange(); + ed.dom.remove(sel.createRange().item(0)); + addUndo(); + + return Event.cancel(e); + } + } + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + + if (t.undoManager.typing) + addUndo(); + + return; + } + + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + }); + + t.onMouseDown.add(function() { + if (t.undoManager.typing) + addUndo(); + }); + } + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + + if (target !== t.getBody()) { + t.dom.setAttrib(target, "style", null); + + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + var s = t.selection; + + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } + + t.onKeyPress.add(function(ed, e) { + var applyAttributes; + + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); + + return Event.cancel(e); + } + }); + + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); + + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); + } + }); + } + }, + + _refreshContentEditable : function() { + var self = this, body, parent; + + // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again + if (self._isHidden()) { + body = self.getBody(); + parent = body.parentNode; + + parent.removeChild(body); + parent.appendChild(body); + + body.focus(); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); + } + }); +})(tinymce); + +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undefined, TRUE = true, FALSE = false; + + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + formatter = editor.formatter, + bookmark; + + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) + ui = FALSE; + + if (value === undefined) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return formatter.match(name); + }; + + function toggleFormat(name, value) { + formatter.toggle(name, value ? {value : value} : undefined); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + execCommand('mceRepaint'); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + var parser, serializer, parentNode, rootNode, fragment, args, + marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; + + // Setup parser and serializer + parser = editor.parser; + serializer = new tinymce.html.Serializer({}, editor.schema); + bookmarkHtml = '\uFEFF'; + + // Run beforeSetContent handlers on the HTML to be inserted + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Replace the caret marker with a span bookmark element + value = value.replace(/\{\$caret\}/, bookmarkHtml); + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) + editor.getDoc().execCommand('Delete', false, null); + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + args = {context : parentNode.nodeName.toLowerCase()}; + fragment = parser.parse(value, args); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + node.parent.insert(marker, node, node.name === 'br'); + break; + } + } + } + + // If parser says valid we can insert the contents into that parent + if (!args.invalid) { + value = serializer.serialize(fragment); + + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + node = parentNode.firstChild; + node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) + dom.setHTML(parentNode, value); + else + selection.setContent(value); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = editor.selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) + parentNode = node = rootNode; + else + node = parentNode; + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function() { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) + dom.setHTML(rootNode, value); + else + dom.setOuterHTML(parentNode, value); + } + + marker = dom.get('mce_marker'); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + nodeRect = dom.getRect(marker); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); + viewportBodyElement.scrollLeft = nodeRect.x; + viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; + } + + // Move selection before marker and remove it + rng = dom.createRng(); + + // If previous sibling is a text node set the selection to the end of that node + node = marker.previousSibling; + if (node && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + } else { + // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node + rng.setStartBefore(marker); + rng.setEndBefore(marker); + } + + // Remove the marker node and set the new range + dom.remove(marker); + selection.setRng(rng); + + // Dispatch after event and add any visual elements needed + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + editor.execCommand('mceInsertContent', false, '
    '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var anchor; + + if (typeof(value) == 'string') + value = {href : value}; + + anchor = dom.getParent(selection.getNode(), 'a'); + + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + + // Remove existing links if there could be child links or that the href isn't specified + if (!anchor || !value.href) { + formatter.remove('link'); + } + + // Apply new link to selection + if (value.href) { + formatter.apply('link', value, anchor); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + editor.selection.setRng(rng); + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + return isFormatMatch('align' + command.substring(7)); + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + if (settings.custom_undo_redo) { + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + } + }; +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + tinymce.UndoManager = function(editor) { + var self, index = 0, data = [], beforeBookmark; + + function getContent() { + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); + }; + + return self = { + typing : false, + + onAdd : new Dispatcher(self), + + onUndo : new Dispatcher(self), + + onRedo : new Dispatcher(self), + + beforeChange : function() { + beforeBookmark = editor.selection.getBookmark(2, true); + }, + + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) + return null; + + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = beforeBookmark; + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) + data.length = index + 1; + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = false; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.beforeBookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + clear : function() { + data = []; + index = 0; + self.typing = false; + }, + + hasUndo : function() { + return index > 0 || this.typing; + }, + + hasRedo : function() { + return index < data.length - 1 && !this.typing; + } + }; + }; +})(tinymce); + +(function(tinymce) { + // Shorten names + var Event = tinymce.dom.Event, + isIE = tinymce.isIE, + isGecko = tinymce.isGecko, + isOpera = tinymce.isOpera, + each = tinymce.each, + extend = tinymce.extend, + TRUE = true, + FALSE = false; + + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + + // Checks if the selection/caret is at the end of the specified block element + function isAtEnd(rng, par) { + var rng2 = par.ownerDocument.createRange(); + + rng2.setStart(rng.endContainer, rng.endOffset); + rng2.setEndAfter(par); + + // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element + return rng2.cloneContents().textContent.length == 0; + }; + + function splitList(selection, dom, li) { + var listBlock, block; + + if (dom.isEmpty(li)) { + listBlock = dom.getParent(li, 'ul,ol'); + + if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { + dom.split(listBlock, li); + block = dom.create('p', 0, '
    '); + dom.replace(block, li); + selection.select(block, 1); + } + + return FALSE; + } + + return TRUE; + }; + + tinymce.create('tinymce.ForceBlocks', { + ForceBlocks : function(ed) { + var t = this, s = ed.settings, elm; + + t.editor = ed; + t.dom = ed.dom; + elm = (s.forced_root_block || 'p').toLowerCase(); + s.element = elm.toUpperCase(); + + ed.onPreInit.add(t.setup, t); + }, + + setup : function() { + var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements(); + + // Force root blocks + if (s.forced_root_block) { + function addRootBlocks() { + var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF; + + if (!node || node.nodeType !== 1) + return; + + // Check if node is wrapped in block + while (node != rootNode) { + if (blockElements[node.nodeName]) + return; + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + } else { + // Force control range into text range + if (rng.item) { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rng.item(0)); + } + + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + for (node = rootNode.firstChild; node; node) { + if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { + if (!rootBlockNode) { + rootBlockNode = dom.create(s.forced_root_block); + node.parentNode.insertBefore(rootBlockNode, node); + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + try { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) + rng.moveEnd('character', endOffset); + + rng.select(); + } catch (ex) { + // Ignore + } + } + + ed.nodeChanged(); + }; + + ed.onKeyUp.add(addRootBlocks); + ed.onClick.add(addRootBlocks); + } + + if (s.force_br_newlines) { + // Force IE to produce BRs on enter + if (isIE) { + ed.onKeyPress.add(function(ed, e) { + var n; + + if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { + selection.setContent('
    ', {format : 'raw'}); + n = dom.get('__'); + n.removeAttribute('id'); + selection.select(n); + selection.collapse(); + return Event.cancel(e); + } + }); + } + } + + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + selection.collapse(true); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } + + if (isGecko) { + ed.onKeyDown.add(function(ed, e) { + if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) + t.backspaceDelete(e, e.keyCode == 8); + }); + } + } + + // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 + if (tinymce.isWebKit) { + function insertBr(ed) { + var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; + + // Insert BR element + rng.insertNode(br = dom.create('br')); + + // Place caret after BR + rng.setStartAfter(br); + rng.setEndAfter(br); + selection.setRng(rng); + + // Could not place caret after BR then insert an nbsp entity and move the caret + if (selection.getSel().focusNode == br.previousSibling) { + selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); + selection.collapse(TRUE); + } + + // Create a temporary DIV after the BR and get the position as it + // seems like getPos() returns 0 for text nodes and BR elements. + dom.insertAfter(div, br); + divYPos = dom.getPos(div).y; + dom.remove(div); + + // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 + if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. + ed.getWin().scrollTo(0, divYPos); + }; + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { + insertBr(ed); + Event.cancel(e); + } + }); + } + + // IE specific fixes + if (isIE) { + // Replaces IE:s auto generated paragraphs with the specified element name + if (s.element != 'P') { + ed.onKeyPress.add(function(ed, e) { + t.lastElm = selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, n = selection.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = dom.rename(n, s.element); + selection.select(n); + selection.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = dom.getParent(n, 'p'); + + if (bl) { + dom.rename(bl, s.element); + ed.nodeChanged(); + } + } + }); + } + } + }, + + getParentBlock : function(n) { + var d = this.dom; + + return d.getParent(n, d.isBlock); + }, + + insertPara : function(e) { + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + + ed.undoManager.beforeChange(); + + // If root blocks are forced then use Operas default behavior since it's really good +// Removed due to bug: #1853816 +// if (se.forced_root_block && isOpera) +// return TRUE; + + // Setup before range + rb = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + rb.setStart(s.anchorNode, s.anchorOffset); + rb.collapse(TRUE); + + // Setup after range + ra = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + ra.setStart(s.focusNode, s.focusOffset); + ra.collapse(TRUE); + + // Setup start/end points + dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; + sn = dir ? s.anchorNode : s.focusNode; + so = dir ? s.anchorOffset : s.focusOffset; + en = dir ? s.focusNode : s.anchorNode; + eo = dir ? s.focusOffset : s.anchorOffset; + + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + if (sn.firstChild.nodeName == 'BR') + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + if (sn.childNodes.length == 0) { + ed.dom.add(sn, se.element, null, '
    '); + aft = ed.dom.add(sn, se.element, null, '
    '); + } else { + n = sn.innerHTML; + sn.innerHTML = ''; + ed.dom.add(sn, se.element, null, n); + aft = ed.dom.add(sn, se.element, null, '
    '); + } + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return FALSE; + } + + // If the caret is in an invalid location in FF we need to move it into the first block + if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { + sn = en = sn.firstChild; + so = eo = 0; + rb = d.createRange(); + rb.setStart(sn, 0); + ra = d.createRange(); + ra.setStart(en, 0); + } + + // If the body is totally empty add a BR element this might happen on webkit + if (!d.body.hasChildNodes()) { + d.body.appendChild(dom.create('br')); + } + + // Never use body as start or end node + sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + sn = sn.nodeName == "BODY" ? sn.firstChild : sn; + en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + en = en.nodeName == "BODY" ? en.firstChild : en; + + // Get start and end blocks + sb = t.getParentBlock(sn); + eb = t.getParentBlock(en); + bn = sb ? sb.nodeName : se.element; // Get block name to create + + // Return inside list use default browser behavior + if (n = t.dom.getParent(sb, 'li,pre')) { + if (n.nodeName == 'LI') + return splitList(ed.selection, t.dom, n); + + return TRUE; + } + + // If caption or absolute layers then always generate new blocks within + if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + sb = null; + } + + // If caption or absolute layers then always generate new blocks within + if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + eb = null; + } + + // Use P instead + if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { + bn = se.element; + sb = eb = null; + } + + // Setup new before and after blocks + bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); + aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); + + // Remove id from after clone + aft.removeAttribute('id'); + + // Is header and cursor is at the end, then force paragraph under + if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) + aft = ed.dom.create(se.element); + + // Find start chop node + n = sc = sn; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + sc = n; + } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); + + // Find end chop node + n = ec = en; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + ec = n; + } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); + + // Place first chop part into before block element + if (sc.nodeName == bn) + rb.setStart(sc, 0); + else + rb.setStartBefore(sc); + + rb.setEnd(sn, so); + bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Place secnd chop part within new block element + try { + ra.setEndAfter(ec); + } catch(ex) { + //console.debug(s.focusNode, s.focusOffset); + } + + ra.setStart(en, eo); + aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Create range around everything + r = d.createRange(); + if (!sc.previousSibling && sc.parentNode.nodeName == bn) { + r.setStartBefore(sc.parentNode); + } else { + if (rb.startContainer.nodeName == bn && rb.startOffset == 0) + r.setStartBefore(rb.startContainer); + else + r.setStart(rb.startContainer, rb.startOffset); + } + + if (!ec.nextSibling && ec.parentNode.nodeName == bn) + r.setEndAfter(ec.parentNode); + else + r.setEnd(ra.endContainer, ra.endOffset); + + // Delete and replace it with new block elements + r.deleteContents(); + + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + + // Never wrap blocks in blocks + if (bef.firstChild && bef.firstChild.nodeName == bn) + bef.innerHTML = bef.firstChild.innerHTML; + + if (aft.firstChild && aft.firstChild.nodeName == bn) + aft.innerHTML = aft.firstChild.innerHTML; + + function appendStyles(e, en) { + var nl = [], nn, n, i; + + e.innerHTML = ''; + + // Make clones of style elements + if (se.keep_styles) { + n = en; + do { + // We only want style specific elements + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { + nn = n.cloneNode(FALSE); + dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique + nl.push(nn); + } + } while (n = n.parentNode); + } + + // Append style elements to aft + if (nl.length > 0) { + for (i = nl.length - 1, nn = e; i >= 0; i--) + nn = nn.appendChild(nl[i]); + + // Padd most inner style element + nl[0].innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + return nl[0]; // Move caret to most inner element + } else + e.innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + }; + + // Padd empty blocks + if (dom.isEmpty(bef)) + appendStyles(bef, sn); + + // Fill empty afterblook with current style + if (dom.isEmpty(aft)) + car = appendStyles(aft, en); + + // Opera needs this one backwards for older versions + if (isOpera && parseFloat(opera.version()) < 9.5) { + r.insertNode(bef); + r.insertNode(aft); + } else { + r.insertNode(aft); + r.insertNode(bef); + } + + // Normalize + aft.normalize(); + bef.normalize(); + + // Move cursor and scroll into view + ed.selection.select(aft, true); + ed.selection.collapse(true); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + //ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + 25 > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ + } + + ed.undoManager.add(); + + return FALSE; + }, + + backspaceDelete : function(e, bs) { + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } + + // The caret sometimes gets stuck in Gecko if you delete empty paragraphs + // This workaround removes the element by hand and moves the caret to the previous element + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + + if (n) { + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + + return Event.cancel(e); + } + } + } + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + tinymce.create('tinymce.ControlManager', { + ControlManager : function(ed, s) { + var t = this, i; + + s = s || {}; + t.editor = ed; + t.controls = {}; + t.onAdd = new tinymce.util.Dispatcher(t); + t.onPostRender = new tinymce.util.Dispatcher(t); + t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; + + t.onPostRender.add(function() { + each(t.controls, function(c) { + c.postRender(); + }); + }); + }, + + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + createControl : function(n) { + var c, t = this, ed = t.editor; + + each(ed.plugins, function(p) { + if (p.createControl) { + c = p.createControl(n, t); + + if (c) + return false; + } + }); + + switch (n) { + case "|": + case "separator": + return t.createSeparator(); + } + + if (!c && ed.buttons && (c = ed.buttons[n])) + return t.createButton(n, c); + + return t.add(c); + }, + + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; + + s = extend({ + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus + }, s); + + s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; + if (v = ed.getParam('skin_variant')) + s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + + id = t.prefix + id; + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); + c.onAddItem.add(function(c, o) { + var s = o.settings; + + s.title = ed.getLang(s.title, s.title); + + if (!s.onclick) { + s.onclick = function(v) { + if (s.cmd) + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + }); + + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + + function useNativeListForAccessibility(ed) { + return ed.settings.use_accessible_selects && !tinymce.isGecko + } + + if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) + c = new tinymce.ui.NativeListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s, ed); + } + + t.controls[id] = c; + + // Fix focus problem in Safari + if (tinymce.isWebKit) { + c.onPostRender.add(function(c, n) { + // Store bookmark on mousedown + Event.add(n, 'mousedown', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + + // Restore on focus, since it might be lost + Event.add(n, 'focus', function() { + ed.selection.moveToBookmark(ed.bookmark); + ed.bookmark = null; + }); + }); + } + + if (c.hideMenu) + ed.onMouseDown.add(c.hideMenu, c); + + return t.add(c); + }, + + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.label = ed.translate(s.label); + s.scope = s.scope || ed; + + if (!s.onclick && !s.menu_button) { + s.onclick = function() { + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + unavailable_prefix : ed.getLang('unavailable', ''), + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (s.menu_button) { + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s, ed); + } + + return t.add(c); + }, + + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s, ed)); + ed.onMouseDown.add(c.hideMenu, c); + + return c; + }, + + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls, bm; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + if (tinymce.isIE) + bm = ed.selection.getBookmark(1); + + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + 'menu_class' : ed.getParam('skin') + 'Skin', + scope : s.scope, + more_colors_title : ed.getLang('more_colors') + }, s); + + id = t.prefix + id; + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createToolbar : function(id, s, cc) { + var c, t = this, cls; + + id = t.prefix + id; + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + }); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + tinymce.create('tinymce.WindowManager', { + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); +}(tinymce)); +(function(tinymce) { + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValidChild, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + undefined; + + function isArray(obj) { + return obj instanceof Array; + }; + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); + }; + + // Public functions + + function get(name) { + return name ? formats[name] : formats; + }; + + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undefined) + format.deep = !format.selector; + + // Default to true + if (format.split === undefined) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undefined && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node; + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1 || container.nodeValue === "") { + container = container.nodeType == 1 ? container.childNodes[offset] : container; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } + } + } + } + + return rng; + }; + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.current(); node; node = walker.prev()) { + if (node.childNodes.length > 1 || node == start) { + return node; + } + } + }; + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset == 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ + var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; + + // find the index of the first child list. + each(node.childNodes, function(n, index) { + if (n.nodeName === "UL" || n.nodeName === "OL") { + listIndex = index; + list = n; + return false; + } + }); + + // get the index of the bookmarks + each(node.childNodes, function(n, index) { + if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { + if (n.id == bookmark.id + "_start") { + startIndex = index; + } else if (n.id == bookmark.id + "_end") { + endIndex = index; + } + } + }); + + // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally + if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { + each(tinymce.grep(node.childNodes), process); + return 0; + } else { + currentWrapElm = wrapElm.cloneNode(FALSE); + + // create a list of the nodes on the same side of the list as the selection + each(tinymce.grep(node.childNodes), function(n, index) { + if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { + nodes.push(n); + n.parentNode.removeChild(n); + } + }); + + // insert the wrapping element either before or after the list. + if (startIndex < listIndex) { + node.insertBefore(currentWrapElm, list); + } else if (startIndex > listIndex) { + node.insertBefore(currentWrapElm, list.nextSibling); + } + + // add the new nodes to the list. + newWrappers.push(currentWrapElm); + + each(nodes, function(node) { + currentWrapElm.appendChild(node); + }); + + return currentWrapElm; + } + }; + + function applyRngStyle(rng, bookmark, node_specific) { + var newWrappers = [], wrapName, wrapElm; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + function process(node) { + var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && node.id !== '_mce_caret') { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = wrapElm.cloneNode(FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else if (nodeName == 'li' && bookmark) { + // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. + currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + + // Cleanup + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = child.cloneNode(FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + selection.moveToBookmark(bookmark); + selection.setRng(moveStart(selection.getRng(TRUE))); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng; + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + + // Merges the styles for each node + function process(node) { + var children, i, l; + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + + // Process the children + if (format.deep) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = parent.cloneNode(FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } + }); + }); + }; + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + + // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width + if (tinymce.isWebKit) { + ed.execCommand('mceCleanup'); + } + }; + + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, item_name); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undefined) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }); + + return matchedFormatNames; + }; + + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply + }); + + // Private functions + + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + function expandRng(rng, format, remove) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, child, sibling, siblingName; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + for (;;) { + // Stop expanding on block elements or root depending on format + if (parent == root || (!format[0].block_expand && isBlock(parent))) + return parent; + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + parent = parent.parentNode; + } + + return container; + }; + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) + endOffset = endContainer.length; + } + + if (format[0].inline) { + if (rng.collapsed) { + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof(offset) == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + }; + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return {container : container, offset : pos}; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while (node = walker[start ? 'prev' : 'next']()) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return {container : node, offset : pos}; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return {container: lastTextNode, offset: offset}; + } + } + + // Expand left to closest word boundery + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundery + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + // TODO: Figure out why this is in here + //endContainer = leaf.node.previousSibling; + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

    *texttext*

    ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) + return parents[i]; + } + } + + return container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(true); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('data-mce-' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + if (format.block) { + if (!forcedRootBlock) { + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; + }; + + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undefined) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + function isTextBlock(name) { + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); + }; + + function getContainer(rng, start) { + var container, offset, lastIdx, walker; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + // If start text node is excluded then walk to the next node + if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { + container = new TreeWalker(container, ed.getBody()).next() || container; + } + + // If end text node is excluded then walk to the previous node + if (container.nodeType === 3 && !start && offset == 0) { + container = new TreeWalker(container, ed.getBody()).prev() || container; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; + + // Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container + invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR; + + // Creates a caret container bogus element + function createCaretContainer(fill) { + var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); + + if (fill) { + caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar)); + } + + return caretContainer; + }; + + function isCaretContainerEmpty(node, nodes) { + while (node) { + if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) { + return false; + } + + // Collect nodes + if (nodes && node.nodeType === 1) { + nodes.push(node); + } + + node = node.firstChild; + } + + return true; + }; + + // Returns any parent caret container element + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Removes the caret container for the specified node or all on the current document + function removeCaretContainer(node, move_caret) { + var child, rng; + + if (!node) { + node = getParentCaretContainer(selection.getStart()); + + if (!node) { + while (node = dom.get(caretContainerId)) { + removeCaretContainer(node, false); + } + } + } else { + rng = selection.getRng(true); + + if (isCaretContainerEmpty(node)) { + if (move_caret !== false) { + rng.setStartBefore(node); + rng.setEndBefore(node); + } + + dom.remove(node); + } else { + child = findFirstTextNode(node); + child = child.deleteData(0, 1); + dom.remove(node, 1); + } + + selection.setRng(rng); + } + }; + + // Applies formatting to the caret postion + function applyCaretFormat() { + var rng, caretContainer, textNode, offset, bookmark, container, text; + + rng = selection.getRng(true); + offset = rng.startOffset; + container = rng.startContainer; + text = container.nodeValue; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer) { + textNode = findFirstTextNode(caretContainer); + } + + // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character + if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name)); + rng = rangeUtils.split(rng); + + // Apply the format to the range + apply(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + if (!caretContainer || textNode.nodeValue !== invisibleChar) { + caretContainer = createCaretContainer(true); + textNode = caretContainer.firstChild; + + rng.insertNode(caretContainer); + offset = 1; + + apply(name, vars, caretContainer); + } else { + apply(name, vars, caretContainer); + } + + // Move selection to text node + selection.setCursorLocation(textNode, offset); + } + }; + + function removeCaretFormat() { + var rng = selection.getRng(true), container, offset, bookmark, + hasContentAfter, node, formatNode, parents = [], i, caretContainer; + + container = rng.startContainer; + offset = rng.startOffset; + node = container; + + if (container.nodeType == 3) { + if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) { + hasContentAfter = true; + } + + node = node.parentNode; + } + + while (node) { + if (matchNode(node, name, vars)) { + formatNode = node; + break; + } + + if (node.nextSibling) { + hasContentAfter = true; + } + + parents.push(node); + node = node.parentNode; + } + + // Node doesn't have the specified format + if (!formatNode) { + return; + } + + // Is there contents after the caret then remove the format on the element + if (hasContentAfter) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name), true); + rng = rangeUtils.split(rng); + + // Remove the format from the range + remove(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + caretContainer = createCaretContainer(); + + node = caretContainer; + for (i = parents.length - 1; i >= 0; i--) { + node.appendChild(parents[i].cloneNode(false)); + node = node.firstChild; + } + + // Insert invisible character into inner most format element + node.appendChild(dom.doc.createTextNode(invisibleChar)); + node = node.firstChild; + + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + + // Move selection to text node + selection.setCursorLocation(node, 1); + } + }; + + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + ed.onBeforeGetContent.addToTop(function() { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }); + + // Remove caret container on mouse up and on key up + tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { + ed[name].addToTop(function() { + removeCaretContainer(); + }); + }); + + // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys + ed.onKeyDown.addToTop(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == 8 || keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + }); + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + }; + }; +})(tinymce); + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_legacy_values); + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); + diff --git a/js/tiny_mce/tiny_mce_src.js b/js/tiny_mce/tiny_mce_src.js index c4a62650..a1281778 100644 --- a/js/tiny_mce/tiny_mce_src.js +++ b/js/tiny_mce/tiny_mce_src.js @@ -1,13861 +1,16728 @@ -(function(win) { - var whiteSpaceRe = /^\s*|\s*$/g, - undefined; - - var tinymce = { - majorVersion : '3', - - minorVersion : '3.2', - - releaseDate : '2010-03-25', - - _init : function() { - var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; - - t.isOpera = win.opera && opera.buildNumber; - - t.isWebKit = /WebKit/.test(ua); - - t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); - - t.isIE6 = t.isIE && /MSIE [56]/.test(ua); - - t.isGecko = !t.isWebKit && /Gecko/.test(ua); - - t.isMac = ua.indexOf('Mac') != -1; - - t.isAir = /adobeair/i.test(ua); - - // TinyMCE .NET webcontrol might be setting the values for TinyMCE - if (win.tinyMCEPreInit) { - t.suffix = tinyMCEPreInit.suffix; - t.baseURL = tinyMCEPreInit.base; - t.query = tinyMCEPreInit.query; - return; - } - - // Get suffix and base - t.suffix = ''; - - // If base element found, add that infront of baseURL - nl = d.getElementsByTagName('base'); - for (i=0; i : - s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); - cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name - - // Create namespace for new class - ns = t.createNS(s[3].replace(/\.\w+$/, '')); - - // Class already exists - if (ns[cn]) - return; - - // Make pure static class - if (s[2] == 'static') { - ns[cn] = p; - - if (this.onCreate) - this.onCreate(s[2], s[3], ns[cn]); - - return; - } - - // Create default constructor - if (!p[cn]) { - p[cn] = function() {}; - de = 1; - } - - // Add constructor and methods - ns[cn] = p[cn]; - t.extend(ns[cn].prototype, p); - - // Extend - if (s[5]) { - sp = t.resolve(s[5]).prototype; - scn = s[5].match(/\.(\w+)$/i)[1]; // Class name - - // Extend constructor - c = ns[cn]; - if (de) { - // Add passthrough constructor - ns[cn] = function() { - return sp[scn].apply(this, arguments); - }; - } else { - // Add inherit constructor - ns[cn] = function() { - this.parent = sp[scn]; - return c.apply(this, arguments); - }; - } - ns[cn].prototype[cn] = ns[cn]; - - // Add super methods - t.each(sp, function(f, n) { - ns[cn].prototype[n] = sp[n]; - }); - - // Add overridden methods - t.each(p, function(f, n) { - // Extend methods if needed - if (sp[n]) { - ns[cn].prototype[n] = function() { - this.parent = sp[n]; - return f.apply(this, arguments); - }; - } else { - if (n != cn) - ns[cn].prototype[n] = f; - } - }); - } - - // Add static methods - t.each(p['static'], function(f, n) { - ns[cn][n] = f; - }); - - if (this.onCreate) - this.onCreate(s[2], s[3], ns[cn].prototype); - }, - - walk : function(o, f, n, s) { - s = s || this; - - if (o) { - if (n) - o = o[n]; - - tinymce.each(o, function(o, i) { - if (f.call(s, o, i, n) === false) - return false; - - tinymce.walk(o, f, n, s); - }); - } - }, - - createNS : function(n, o) { - var i, v; - - o = o || win; - - n = n.split('.'); - for (i=0; i= items.length) { - for (i = 0, l = base.length; i < l; i++) { - if (i >= items.length || base[i] != items[i]) { - bp = i + 1; - break; - } - } - } - - if (base.length < items.length) { - for (i = 0, l = items.length; i < l; i++) { - if (i >= base.length || base[i] != items[i]) { - bp = i + 1; - break; - } - } - } - - if (bp == 1) - return path; - - for (i = 0, l = base.length - (bp - 1); i < l; i++) - out += "../"; - - for (i = bp - 1, l = items.length; i < l; i++) { - if (i != bp - 1) - out += "/" + items[i]; - else - out += items[i]; - } - - return out; - }, - - toAbsPath : function(base, path) { - var i, nb = 0, o = [], tr, outPath; - - // Split paths - tr = /\/$/.test(path) ? '/' : ''; - base = base.split('/'); - path = path.split('/'); - - // Remove empty chunks - each(base, function(k) { - if (k) - o.push(k); - }); - - base = o; - - // Merge relURLParts chunks - for (i = path.length - 1, o = []; i >= 0; i--) { - // Ignore empty or . - if (path[i].length == 0 || path[i] == ".") - continue; - - // Is parent - if (path[i] == '..') { - nb++; - continue; - } - - // Move up - if (nb > 0) { - nb--; - continue; - } - - o.push(path[i]); - } - - i = base.length - nb; - - // If /a/b/c or / - if (i <= 0) - outPath = o.reverse().join('/'); - else - outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); - - // Add front / if it's needed - if (outPath.indexOf('/') !== 0) - outPath = '/' + outPath; - - // Add traling / if it's needed - if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) - outPath += tr; - - return outPath; - }, - - getURI : function(nh) { - var s, t = this; - - // Rebuild source - if (!t.source || nh) { - s = ''; - - if (!nh) { - if (t.protocol) - s += t.protocol + '://'; - - if (t.userInfo) - s += t.userInfo + '@'; - - if (t.host) - s += t.host; - - if (t.port) - s += ':' + t.port; - } - - if (t.path) - s += t.path; - - if (t.query) - s += '?' + t.query; - - if (t.anchor) - s += '#' + t.anchor; - - t.source = s; - } - - return t.source; - } - }); -})(); - -(function() { - var each = tinymce.each; - - tinymce.create('static tinymce.util.Cookie', { - getHash : function(n) { - var v = this.get(n), h; - - if (v) { - each(v.split('&'), function(v) { - v = v.split('='); - h = h || {}; - h[unescape(v[0])] = unescape(v[1]); - }); - } - - return h; - }, - - setHash : function(n, v, e, p, d, s) { - var o = ''; - - each(v, function(v, k) { - o += (!o ? '' : '&') + escape(k) + '=' + escape(v); - }); - - this.set(n, o, e, p, d, s); - }, - - get : function(n) { - var c = document.cookie, e, p = n + "=", b; - - // Strict mode - if (!c) - return; - - b = c.indexOf("; " + p); - - if (b == -1) { - b = c.indexOf(p); - - if (b != 0) - return null; - } else - b += 2; - - e = c.indexOf(";", b); - - if (e == -1) - e = c.length; - - return unescape(c.substring(b + p.length, e)); - }, - - set : function(n, v, e, p, d, s) { - document.cookie = n + "=" + escape(v) + - ((e) ? "; expires=" + e.toGMTString() : "") + - ((p) ? "; path=" + escape(p) : "") + - ((d) ? "; domain=" + d : "") + - ((s) ? "; secure" : ""); - }, - - remove : function(n, p) { - var d = new Date(); - - d.setTime(d.getTime() - 1000); - - this.set(n, '', d, p, d); - } - }); -})(); - -tinymce.create('static tinymce.util.JSON', { - serialize : function(o) { - var i, v, s = tinymce.util.JSON.serialize, t; - - if (o == null) - return 'null'; - - t = typeof o; - - if (t == 'string') { - v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; - - return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) { - i = v.indexOf(b); - - if (i + 1) - return '\\' + v.charAt(i + 1); - - a = b.charCodeAt().toString(16); - - return '\\u' + '0000'.substring(a.length) + a; - }) + '"'; - } - - if (t == 'object') { - if (o.hasOwnProperty && o instanceof Array) { - for (i=0, v = '['; i 0 ? ',' : '') + s(o[i]); - - return v + ']'; - } - - v = '{'; - - for (i in o) - v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : ''; - - return v + '}'; - } - - return '' + o; - }, - - parse : function(s) { - try { - return eval('(' + s + ')'); - } catch (ex) { - // Ignore - } - } - - }); - -tinymce.create('static tinymce.util.XHR', { - send : function(o) { - var x, t, w = window, c = 0; - - // Default settings - o.scope = o.scope || this; - o.success_scope = o.success_scope || o.scope; - o.error_scope = o.error_scope || o.scope; - o.async = o.async === false ? false : true; - o.data = o.data || ''; - - function get(s) { - x = 0; - - try { - x = new ActiveXObject(s); - } catch (ex) { - } - - return x; - }; - - x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); - - if (x) { - if (x.overrideMimeType) - x.overrideMimeType(o.content_type); - - x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); - - if (o.content_type) - x.setRequestHeader('Content-Type', o.content_type); - - x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - - x.send(o.data); - - function ready() { - if (!o.async || x.readyState == 4 || c++ > 10000) { - if (o.success && c < 10000 && x.status == 200) - o.success.call(o.success_scope, '' + x.responseText, x, o); - else if (o.error) - o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); - - x = null; - } else - w.setTimeout(ready, 10); - }; - - // Syncronous request - if (!o.async) - return ready(); - - // Wait for response, onReadyStateChange can not be used since it leaks memory in IE - t = w.setTimeout(ready, 10); - } - } -}); - -(function() { - var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; - - tinymce.create('tinymce.util.JSONRequest', { - JSONRequest : function(s) { - this.settings = extend({ - }, s); - this.count = 0; - }, - - send : function(o) { - var ecb = o.error, scb = o.success; - - o = extend(this.settings, o); - - o.success = function(c, x) { - c = JSON.parse(c); - - if (typeof(c) == 'undefined') { - c = { - error : 'JSON Parse error.' - }; - } - - if (c.error) - ecb.call(o.error_scope || o.scope, c.error, x); - else - scb.call(o.success_scope || o.scope, c.result); - }; - - o.error = function(ty, x) { - ecb.call(o.error_scope || o.scope, ty, x); - }; - - o.data = JSON.serialize({ - id : o.id || 'c' + (this.count++), - method : o.method, - params : o.params - }); - - // JSON content type for Ruby on rails. Bug: #1883287 - o.content_type = 'application/json'; - - XHR.send(o); - }, - - 'static' : { - sendRPC : function(o) { - return new tinymce.util.JSONRequest().send(o); - } - } - }); -}()); -(function(tinymce) { - // Shorten names - var each = tinymce.each, - is = tinymce.is, - isWebKit = tinymce.isWebKit, - isIE = tinymce.isIE, - blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/, - boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'), - mceAttribs = makeMap('src,href,style,coords,shape'), - encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'}, - encodeCharsRe = /[<>&\"]/g, - simpleSelectorRe = /^([a-z0-9],?)+$/i, - tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g, - attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; - - function makeMap(str) { - var map = {}, i; - - str = str.split(','); - for (i = str.length; i >= 0; i--) - map[str[i]] = 1; - - return map; - }; - - tinymce.create('tinymce.dom.DOMUtils', { - doc : null, - root : null, - files : null, - pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, - props : { - "for" : "htmlFor", - "class" : "className", - className : "className", - checked : "checked", - disabled : "disabled", - maxlength : "maxLength", - readonly : "readOnly", - selected : "selected", - value : "value", - id : "id", - name : "name", - type : "type" - }, - - DOMUtils : function(d, s) { - var t = this, globalStyle; - - t.doc = d; - t.win = window; - t.files = {}; - t.cssFlicker = false; - t.counter = 0; - t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat"; - t.stdMode = d.documentMode === 8; - - t.settings = s = tinymce.extend({ - keep_values : false, - hex_colors : 1, - process_html : 1 - }, s); - - // Fix IE6SP2 flicker and check it failed for pre SP2 - if (tinymce.isIE6) { - try { - d.execCommand('BackgroundImageCache', false, true); - } catch (e) { - t.cssFlicker = true; - } - } - - // Build styles list - if (s.valid_styles) { - t._styles = {}; - - // Convert styles into a rule list - each(s.valid_styles, function(value, key) { - t._styles[key] = tinymce.explode(value); - }); - } - - tinymce.addUnload(t.destroy, t); - }, - - getRoot : function() { - var t = this, s = t.settings; - - return (s && t.get(s.root_element)) || t.doc.body; - }, - - getViewPort : function(w) { - var d, b; - - w = !w ? this.win : w; - d = w.document; - b = this.boxModel ? d.documentElement : d.body; - - // Returns viewport size excluding scrollbars - return { - x : w.pageXOffset || b.scrollLeft, - y : w.pageYOffset || b.scrollTop, - w : w.innerWidth || b.clientWidth, - h : w.innerHeight || b.clientHeight - }; - }, - - getRect : function(e) { - var p, t = this, sr; - - e = t.get(e); - p = t.getPos(e); - sr = t.getSize(e); - - return { - x : p.x, - y : p.y, - w : sr.w, - h : sr.h - }; - }, - - getSize : function(e) { - var t = this, w, h; - - e = t.get(e); - w = t.getStyle(e, 'width'); - h = t.getStyle(e, 'height'); - - // Non pixel value, then force offset/clientWidth - if (w.indexOf('px') === -1) - w = 0; - - // Non pixel value, then force offset/clientWidth - if (h.indexOf('px') === -1) - h = 0; - - return { - w : parseInt(w) || e.offsetWidth || e.clientWidth, - h : parseInt(h) || e.offsetHeight || e.clientHeight - }; - }, - - getParent : function(n, f, r) { - return this.getParents(n, f, r, false); - }, - - getParents : function(n, f, r, c) { - var t = this, na, se = t.settings, o = []; - - n = t.get(n); - c = c === undefined; - - if (se.strict_root) - r = r || t.getRoot(); - - // Wrap node name as func - if (is(f, 'string')) { - na = f; - - if (f === '*') { - f = function(n) {return n.nodeType == 1;}; - } else { - f = function(n) { - return t.is(n, na); - }; - } - } - - while (n) { - if (n == r || !n.nodeType || n.nodeType === 9) - break; - - if (!f || f(n)) { - if (c) - o.push(n); - else - return n; - } - - n = n.parentNode; - } - - return c ? o : null; - }, - - get : function(e) { - var n; - - if (e && this.doc && typeof(e) == 'string') { - n = e; - e = this.doc.getElementById(e); - - // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick - if (e && e.id !== n) - return this.doc.getElementsByName(n)[1]; - } - - return e; - }, - - getNext : function(node, selector) { - return this._findSib(node, selector, 'nextSibling'); - }, - - getPrev : function(node, selector) { - return this._findSib(node, selector, 'previousSibling'); - }, - - - select : function(pa, s) { - var t = this; - - return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); - }, - - is : function(n, selector) { - var i; - - // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance - if (n.length === undefined) { - // Simple all selector - if (selector === '*') - return n.nodeType == 1; - - // Simple selector just elements - if (simpleSelectorRe.test(selector)) { - selector = selector.toLowerCase().split(/,/); - n = n.nodeName.toLowerCase(); - - for (i = selector.length - 1; i >= 0; i--) { - if (selector[i] == n) - return true; - } - - return false; - } - } - - return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; - }, - - - add : function(p, n, a, h, c) { - var t = this; - - return this.run(p, function(p) { - var e, k; - - e = is(n, 'string') ? t.doc.createElement(n) : n; - t.setAttribs(e, a); - - if (h) { - if (h.nodeType) - e.appendChild(h); - else - t.setHTML(e, h); - } - - return !c ? p.appendChild(e) : e; - }); - }, - - create : function(n, a, h) { - return this.add(this.doc.createElement(n), n, a, h, 1); - }, - - createHTML : function(n, a, h) { - var o = '', t = this, k; - - o += '<' + n; - - for (k in a) { - if (a.hasOwnProperty(k)) - o += ' ' + k + '="' + t.encode(a[k]) + '"'; - } - - if (tinymce.is(h)) - return o + '>' + h + ''; - - return o + ' />'; - }, - - remove : function(node, keep_children) { - return this.run(node, function(node) { - var parent, child; - - parent = node.parentNode; - - if (!parent) - return null; - - if (keep_children) { - while (child = node.firstChild) { - // IE 8 will crash if you don't remove completely empty text nodes - if (child.nodeType !== 3 || child.nodeValue) - parent.insertBefore(child, node); - else - node.removeChild(child); - } - } - - return parent.removeChild(node); - }); - }, - - setStyle : function(n, na, v) { - var t = this; - - return t.run(n, function(e) { - var s, i; - - s = e.style; - - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); - - // Default px suffix on these - if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) - v += 'px'; - - switch (na) { - case 'opacity': - // IE specific opacity - if (isIE) { - s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; - - if (!n.currentStyle || !n.currentStyle.hasLayout) - s.display = 'inline-block'; - } - - // Fix for older browsers - s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; - break; - - case 'float': - isIE ? s.styleFloat = v : s.cssFloat = v; - break; - - default: - s[na] = v || ''; - } - - // Force update of the style data - if (t.settings.update_styles) - t.setAttrib(e, '_mce_style'); - }); - }, - - getStyle : function(n, na, c) { - n = this.get(n); - - if (!n) - return false; - - // Gecko - if (this.doc.defaultView && c) { - // Remove camelcase - na = na.replace(/[A-Z]/g, function(a){ - return '-' + a; - }); - - try { - return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); - } catch (ex) { - // Old safari might fail - return null; - } - } - - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); - - if (na == 'float') - na = isIE ? 'styleFloat' : 'cssFloat'; - - // IE & Opera - if (n.currentStyle && c) - return n.currentStyle[na]; - - return n.style[na]; - }, - - setStyles : function(e, o) { - var t = this, s = t.settings, ol; - - ol = s.update_styles; - s.update_styles = 0; - - each(o, function(v, n) { - t.setStyle(e, n, v); - }); - - // Update style info - s.update_styles = ol; - if (s.update_styles) - t.setAttrib(e, s.cssText); - }, - - setAttrib : function(e, n, v) { - var t = this; - - // Whats the point - if (!e || !n) - return; - - // Strict XML mode - if (t.settings.strict) - n = n.toLowerCase(); - - return this.run(e, function(e) { - var s = t.settings; - - switch (n) { - case "style": - if (!is(v, 'string')) { - each(v, function(v, n) { - t.setStyle(e, n, v); - }); - - return; - } - - // No mce_style for elements with these since they might get resized by the user - if (s.keep_values) { - if (v && !t._isRes(v)) - e.setAttribute('_mce_style', v, 2); - else - e.removeAttribute('_mce_style', 2); - } - - e.style.cssText = v; - break; - - case "class": - e.className = v || ''; // Fix IE null bug - break; - - case "src": - case "href": - if (s.keep_values) { - if (s.url_converter) - v = s.url_converter.call(s.url_converter_scope || t, v, n, e); - - t.setAttrib(e, '_mce_' + n, v, 2); - } - - break; - - case "shape": - e.setAttribute('_mce_style', v); - break; - } - - if (is(v) && v !== null && v.length !== 0) - e.setAttribute(n, '' + v, 2); - else - e.removeAttribute(n, 2); - }); - }, - - setAttribs : function(e, o) { - var t = this; - - return this.run(e, function(e) { - each(o, function(v, n) { - t.setAttrib(e, n, v); - }); - }); - }, - - getAttrib : function(e, n, dv) { - var v, t = this; - - e = t.get(e); - - if (!e || e.nodeType !== 1) - return false; - - if (!is(dv)) - dv = ''; - - // Try the mce variant for these - if (/^(src|href|style|coords|shape)$/.test(n)) { - v = e.getAttribute("_mce_" + n); - - if (v) - return v; - } - - if (isIE && t.props[n]) { - v = e[t.props[n]]; - v = v && v.nodeValue ? v.nodeValue : v; - } - - if (!v) - v = e.getAttribute(n, 2); - - // Check boolean attribs - if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { - if (e[t.props[n]] === true && v === '') - return n; - - return v ? n : ''; - } - - // Inner input elements will override attributes on form elements - if (e.nodeName === "FORM" && e.getAttributeNode(n)) - return e.getAttributeNode(n).nodeValue; - - if (n === 'style') { - v = v || e.style.cssText; - - if (v) { - v = t.serializeStyle(t.parseStyle(v), e.nodeName); - - if (t.settings.keep_values && !t._isRes(v)) - e.setAttribute('_mce_style', v); - } - } - - // Remove Apple and WebKit stuff - if (isWebKit && n === "class" && v) - v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); - - // Handle IE issues - if (isIE) { - switch (n) { - case 'rowspan': - case 'colspan': - // IE returns 1 as default value - if (v === 1) - v = ''; - - break; - - case 'size': - // IE returns +0 as default value for size - if (v === '+0' || v === 20 || v === 0) - v = ''; - - break; - - case 'width': - case 'height': - case 'vspace': - case 'checked': - case 'disabled': - case 'readonly': - if (v === 0) - v = ''; - - break; - - case 'hspace': - // IE returns -1 as default value - if (v === -1) - v = ''; - - break; - - case 'maxlength': - case 'tabindex': - // IE returns default value - if (v === 32768 || v === 2147483647 || v === '32768') - v = ''; - - break; - - case 'multiple': - case 'compact': - case 'noshade': - case 'nowrap': - if (v === 65535) - return n; - - return dv; - - case 'shape': - v = v.toLowerCase(); - break; - - default: - // IE has odd anonymous function for event attributes - if (n.indexOf('on') === 0 && v) - v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1'); - } - } - - return (v !== undefined && v !== null && v !== '') ? '' + v : dv; - }, - - getPos : function(n, ro) { - var t = this, x = 0, y = 0, e, d = t.doc, r; - - n = t.get(n); - ro = ro || d.body; - - if (n) { - // Use getBoundingClientRect on IE, Opera has it but it's not perfect - if (isIE && !t.stdMode) { - n = n.getBoundingClientRect(); - e = t.boxModel ? d.documentElement : d.body; - x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border - x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x; - n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset - - return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x}; - } - - r = n; - while (r && r != ro && r.nodeType) { - x += r.offsetLeft || 0; - y += r.offsetTop || 0; - r = r.offsetParent; - } - - r = n.parentNode; - while (r && r != ro && r.nodeType) { - x -= r.scrollLeft || 0; - y -= r.scrollTop || 0; - r = r.parentNode; - } - } - - return {x : x, y : y}; - }, - - parseStyle : function(st) { - var t = this, s = t.settings, o = {}; - - if (!st) - return o; - - function compress(p, s, ot) { - var t, r, b, l; - - // Get values and check it it needs compressing - t = o[p + '-top' + s]; - if (!t) - return; - - r = o[p + '-right' + s]; - if (t != r) - return; - - b = o[p + '-bottom' + s]; - if (r != b) - return; - - l = o[p + '-left' + s]; - if (b != l) - return; - - // Compress - o[ot] = l; - delete o[p + '-top' + s]; - delete o[p + '-right' + s]; - delete o[p + '-bottom' + s]; - delete o[p + '-left' + s]; - }; - - function compress2(ta, a, b, c) { - var t; - - t = o[a]; - if (!t) - return; - - t = o[b]; - if (!t) - return; - - t = o[c]; - if (!t) - return; - - // Compress - o[ta] = o[a] + ' ' + o[b] + ' ' + o[c]; - delete o[a]; - delete o[b]; - delete o[c]; - }; - - st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities - - each(st.split(';'), function(v) { - var sv, ur = []; - - if (v) { - v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities - v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';}); - v = v.split(':'); - sv = tinymce.trim(v[1]); - sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];}); - - sv = sv.replace(/rgb\([^\)]+\)/g, function(v) { - return t.toHex(v); - }); - - if (s.url_converter) { - sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) { - return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')'; - }); - } - - o[tinymce.trim(v[0]).toLowerCase()] = sv; - } - }); - - compress("border", "", "border"); - compress("border", "-width", "border-width"); - compress("border", "-color", "border-color"); - compress("border", "-style", "border-style"); - compress("padding", "", "padding"); - compress("margin", "", "margin"); - compress2('border', 'border-width', 'border-style', 'border-color'); - - if (isIE) { - // Remove pointless border - if (o.border == 'medium none') - o.border = ''; - } - - return o; - }, - - serializeStyle : function(o, name) { - var t = this, s = ''; - - function add(v, k) { - if (k && v) { - // Remove browser specific styles like -moz- or -webkit- - if (k.indexOf('-') === 0) - return; - - switch (k) { - case 'font-weight': - // Opera will output bold as 700 - if (v == 700) - v = 'bold'; - - break; - - case 'color': - case 'background-color': - v = v.toLowerCase(); - break; - } - - s += (s ? ' ' : '') + k + ': ' + v + ';'; - } - }; - - // Validate style output - if (name && t._styles) { - each(t._styles['*'], function(name) { - add(o[name], name); - }); - - each(t._styles[name.toLowerCase()], function(name) { - add(o[name], name); - }); - } else - each(o, add); - - return s; - }, - - loadCSS : function(u) { - var t = this, d = t.doc, head; - - if (!u) - u = ''; - - head = t.select('head')[0]; - - each(u.split(','), function(u) { - var link; - - if (t.files[u]) - return; - - t.files[u] = true; - link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); - - // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug - // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading - // It's ugly but it seems to work fine. - if (isIE && d.documentMode) { - link.onload = function() { - d.recalc(); - link.onload = null; - }; - } - - head.appendChild(link); - }); - }, - - addClass : function(e, c) { - return this.run(e, function(e) { - var o; - - if (!c) - return 0; - - if (this.hasClass(e, c)) - return e.className; - - o = this.removeClass(e, c); - - return e.className = (o != '' ? (o + ' ') : '') + c; - }); - }, - - removeClass : function(e, c) { - var t = this, re; - - return t.run(e, function(e) { - var v; - - if (t.hasClass(e, c)) { - if (!re) - re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); - - v = e.className.replace(re, ' '); - v = tinymce.trim(v != ' ' ? v : ''); - - e.className = v; - - // Empty class attr - if (!v) { - e.removeAttribute('class'); - e.removeAttribute('className'); - } - - return v; - } - - return e.className; - }); - }, - - hasClass : function(n, c) { - n = this.get(n); - - if (!n || !c) - return false; - - return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; - }, - - show : function(e) { - return this.setStyle(e, 'display', 'block'); - }, - - hide : function(e) { - return this.setStyle(e, 'display', 'none'); - }, - - isHidden : function(e) { - e = this.get(e); - - return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; - }, - - uniqueId : function(p) { - return (!p ? 'mce_' : p) + (this.counter++); - }, - - setHTML : function(e, h) { - var t = this; - - return this.run(e, function(e) { - var x, i, nl, n, p, x; - - h = t.processHTML(h); - - if (isIE) { - function set() { - // Remove all child nodes - while (e.firstChild) - e.firstChild.removeNode(); - - try { - // IE will remove comments from the beginning - // unless you padd the contents with something - e.innerHTML = '
    ' + h; - e.removeChild(e.firstChild); - } catch (ex) { - // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p - // This seems to fix this problem - - // Create new div with HTML contents and a BR infront to keep comments - x = t.create('div'); - x.innerHTML = '
    ' + h; - - // Add all children from div to target - each (x.childNodes, function(n, i) { - // Skip br element - if (i) - e.appendChild(n); - }); - } - }; - - // IE has a serious bug when it comes to paragraphs it can produce an invalid - // DOM tree if contents like this

    • Item 1

    is inserted - // It seems to be that IE doesn't like a root block element placed inside another root block element - if (t.settings.fix_ie_paragraphs) - h = h.replace(/

    <\/p>|]+)><\/p>|/gi, ' 

    '); - - set(); - - if (t.settings.fix_ie_paragraphs) { - // Check for odd paragraphs this is a sign of a broken DOM - nl = e.getElementsByTagName("p"); - for (i = nl.length - 1, x = 0; i >= 0; i--) { - n = nl[i]; - - if (!n.hasChildNodes()) { - if (!n._mce_keep) { - x = 1; // Is broken - break; - } - - n.removeAttribute('_mce_keep'); - } - } - } - - // Time to fix the madness IE left us - if (x) { - // So if we replace the p elements with divs and mark them and then replace them back to paragraphs - // after we use innerHTML we can fix the DOM tree - h = h.replace(/

    ]+)>|

    /ig, '

    '); - h = h.replace(/<\/p>/g, '
    '); - - // Set the new HTML with DIVs - set(); - - // Replace all DIV elements with the _mce_tmp attibute back to paragraphs - // This is needed since IE has a annoying bug see above for details - // This is a slow process but it has to be done. :( - if (t.settings.fix_ie_paragraphs) { - nl = e.getElementsByTagName("DIV"); - for (i = nl.length - 1; i >= 0; i--) { - n = nl[i]; - - // Is it a temp div - if (n._mce_tmp) { - // Create new paragraph - p = t.doc.createElement('p'); - - // Copy all attributes - n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) { - var v; - - if (b !== '_mce_tmp') { - v = n.getAttribute(b); - - if (!v && b === 'class') - v = n.className; - - p.setAttribute(b, v); - } - }); - - // Append all children to new paragraph - for (x = 0; x]+)\/>|/gi, ''); // Force open - - // Store away src and href in _mce_src and mce_href since browsers mess them up - if (s.keep_values) { - // Wrap scripts and styles in comments for serialization purposes - if (/)/g, '\n'); - s = s.replace(/^[\r\n]*|[\r\n]*$/g, ''); - s = s.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); - - return s; - }; - - // Wrap the script contents in CDATA and keep them from executing - h = h.replace(/]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) { - // Force type attribute - if (!attribs) - attribs = ' type="text/javascript"'; - - // Convert the src attribute of the scripts - attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) { - if (s.url_converter) - url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script')); - - return '_mce_src="' + url + '"'; - }); - - // Wrap text contents - if (tinymce.trim(text)) { - codeBlocks.push(trim(text)); - text = ''; - } - - return '' + text + ''; - }); - - // Wrap style elements - h = h.replace(/]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) { - // Wrap text contents - if (text) { - codeBlocks.push(trim(text)); - text = ''; - } - - return '' + text + ''; - }); - - // Wrap noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return ''; - }); - } - - h = h.replace(//g, ''); - - // This function processes the attributes in the HTML string to force boolean - // attributes to the attr="attr" format and convert style, src and href to _mce_ versions - function processTags(html) { - return html.replace(tagRegExp, function(match, elm_name, attrs, end) { - return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) { - var mceValue; - - name = name.toLowerCase(); - value = value || val2 || val3 || ""; - - // Treat boolean attributes - if (boolAttrs[name]) { - // false or 0 is treated as a missing attribute - if (value === 'false' || value === '0') - return; - - return name + '="' + name + '"'; - } - - // Is attribute one that needs special treatment - if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) { - mceValue = t.decode(value); - - // Convert URLs to relative/absolute ones - if (s.url_converter && (name == "src" || name == "href")) - mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name); - - // Process styles lowercases them and compresses them - if (name == 'style') - mceValue = t.serializeStyle(t.parseStyle(mceValue), name); - - return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"'; - } - - return match; - }) + end + '>'; - }); - }; - - h = processTags(h); - - // Restore script blocks - h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) { - return codeBlocks[idx]; - }); - } - - return h; - }, - - getOuterHTML : function(e) { - var d; - - e = this.get(e); - - if (!e) - return null; - - if (e.outerHTML !== undefined) - return e.outerHTML; - - d = (e.ownerDocument || this.doc).createElement("body"); - d.appendChild(e.cloneNode(true)); - - return d.innerHTML; - }, - - setOuterHTML : function(e, h, d) { - var t = this; - - function setHTML(e, h, d) { - var n, tp; - - tp = d.createElement("body"); - tp.innerHTML = h; - - n = tp.lastChild; - while (n) { - t.insertAfter(n.cloneNode(true), e); - n = n.previousSibling; - } - - t.remove(e); - }; - - return this.run(e, function(e) { - e = t.get(e); - - // Only set HTML on elements - if (e.nodeType == 1) { - d = d || e.ownerDocument || t.doc; - - if (isIE) { - try { - // Try outerHTML for IE it sometimes produces an unknown runtime error - if (isIE && e.nodeType == 1) - e.outerHTML = h; - else - setHTML(e, h, d); - } catch (ex) { - // Fix for unknown runtime error - setHTML(e, h, d); - } - } else - setHTML(e, h, d); - } - }); - }, - - decode : function(s) { - var e, n, v; - - // Look for entities to decode - if (/&[\w#]+;/.test(s)) { - // Decode the entities using a div element not super efficient but less code - e = this.doc.createElement("div"); - e.innerHTML = s; - n = e.firstChild; - v = ''; - - if (n) { - do { - v += n.nodeValue; - } while (n = n.nextSibling); - } - - return v || s; - } - - return s; - }, - - encode : function(str) { - return ('' + str).replace(encodeCharsRe, function(chr) { - return encodedChars[chr]; - }); - }, - - insertAfter : function(node, reference_node) { - reference_node = this.get(reference_node); - - return this.run(node, function(node) { - var parent, nextSibling; - - parent = reference_node.parentNode; - nextSibling = reference_node.nextSibling; - - if (nextSibling) - parent.insertBefore(node, nextSibling); - else - parent.appendChild(node); - - return node; - }); - }, - - isBlock : function(n) { - if (n.nodeType && n.nodeType !== 1) - return false; - - n = n.nodeName || n; - - return blockRe.test(n); - }, - - replace : function(n, o, k) { - var t = this; - - if (is(o, 'array')) - n = n.cloneNode(true); - - return t.run(o, function(o) { - if (k) { - each(tinymce.grep(o.childNodes), function(c) { - n.appendChild(c); - }); - } - - return o.parentNode.replaceChild(n, o); - }); - }, - - rename : function(elm, name) { - var t = this, newElm; - - if (elm.nodeName != name.toUpperCase()) { - // Rename block element - newElm = t.create(name); - - // Copy attribs to new block - each(t.getAttribs(elm), function(attr_node) { - t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); - }); - - // Replace block - t.replace(newElm, elm, 1); - } - - return newElm || elm; - }, - - findCommonAncestor : function(a, b) { - var ps = a, pe; - - while (ps) { - pe = b; - - while (pe && ps != pe) - pe = pe.parentNode; - - if (ps == pe) - break; - - ps = ps.parentNode; - } - - if (!ps && a.ownerDocument) - return a.ownerDocument.documentElement; - - return ps; - }, - - toHex : function(s) { - var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); - - function hex(s) { - s = parseInt(s).toString(16); - - return s.length > 1 ? s : '0' + s; // 0 -> 00 - }; - - if (c) { - s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); - - return s; - } - - return s; - }, - - getClasses : function() { - var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; - - if (t.classes) - return t.classes; - - function addClasses(s) { - // IE style imports - each(s.imports, function(r) { - addClasses(r); - }); - - each(s.cssRules || s.rules, function(r) { - // Real type or fake it on IE - switch (r.type || 1) { - // Rule - case 1: - if (r.selectorText) { - each(r.selectorText.split(','), function(v) { - v = v.replace(/^\s*|\s*$|^\s\./g, ""); - - // Is internal or it doesn't contain a class - if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) - return; - - // Remove everything but class name - ov = v; - v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1'); - - // Filter classes - if (f && !(v = f(v, ov))) - return; - - if (!lo[v]) { - cl.push({'class' : v}); - lo[v] = 1; - } - }); - } - break; - - // Import - case 3: - addClasses(r.styleSheet); - break; - } - }); - }; - - try { - each(t.doc.styleSheets, addClasses); - } catch (ex) { - // Ignore - } - - if (cl.length > 0) - t.classes = cl; - - return cl; - }, - - run : function(e, f, s) { - var t = this, o; - - if (t.doc && typeof(e) === 'string') - e = t.get(e); - - if (!e) - return false; - - s = s || this; - if (!e.nodeType && (e.length || e.length === 0)) { - o = []; - - each(e, function(e, i) { - if (e) { - if (typeof(e) == 'string') - e = t.doc.getElementById(e); - - o.push(f.call(s, e, i)); - } - }); - - return o; - } - - return f.call(s, e); - }, - - getAttribs : function(n) { - var o; - - n = this.get(n); - - if (!n) - return []; - - if (isIE) { - o = []; - - // Object will throw exception in IE - if (n.nodeName == 'OBJECT') - return n.attributes; - - // IE doesn't keep the selected attribute if you clone option elements - if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) - o.push({specified : 1, nodeName : 'selected'}); - - // It's crazy that this is faster in IE but it's because it returns all attributes all the time - n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { - o.push({specified : 1, nodeName : a}); - }); - - return o; - } - - return n.attributes; - }, - - destroy : function(s) { - var t = this; - - if (t.events) - t.events.destroy(); - - t.win = t.doc = t.root = t.events = null; - - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - }, - - createRng : function() { - var d = this.doc; - - return d.createRange ? d.createRange() : new tinymce.dom.Range(this); - }, - - nodeIndex : function(node, normalized) { - var idx = 0, lastNodeType, lastNode, nodeType; - - if (node) { - for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { - nodeType = node.nodeType; - - // Handle normalization of text nodes - if (!normalized || nodeType != 3 || (lastNodeType != nodeType && node.nodeValue.length)) - idx++; - - lastNodeType = nodeType; - } - } - - return idx; - }, - - split : function(pe, e, re) { - var t = this, r = t.createRng(), bef, aft, pa; - - // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense - // but we don't want that in our code since it serves no purpose for the end user - // For example if this is chopped: - //

    text 1CHOPtext 2

    - // would produce: - //

    text 1

    CHOP

    text 2

    - // this function will then trim of empty edges and produce: - //

    text 1

    CHOP

    text 2

    - function trim(node) { - var i, children = node.childNodes; - - if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark') - return; - - for (i = children.length - 1; i >= 0; i--) - trim(children[i]); - - if (node.nodeType != 9) { - // Keep non whitespace text nodes - if (node.nodeType == 3 && node.nodeValue.length > 0) - return; - - if (node.nodeType == 1) { - // If the only child is a bookmark then move it up - children = node.childNodes; - if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark') - node.parentNode.insertBefore(children[0], node); - - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) - return; - } - - t.remove(node); - } - - return node; - }; - - if (pe && e) { - // Get before chunk - r.setStart(pe.parentNode, t.nodeIndex(pe)); - r.setEnd(e.parentNode, t.nodeIndex(e)); - bef = r.extractContents(); - - // Get after chunk - r = t.createRng(); - r.setStart(e.parentNode, t.nodeIndex(e) + 1); - r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); - aft = r.extractContents(); - - // Insert before chunk - pa = pe.parentNode; - pa.insertBefore(trim(bef), pe); - - // Insert middle chunk - if (re) - pa.replaceChild(re, e); - else - pa.insertBefore(e, pe); - - // Insert after chunk - pa.insertBefore(trim(aft), pe); - t.remove(pe); - - return re || e; - } - }, - - bind : function(target, name, func, scope) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); - - return t.events.add(target, name, func, scope || this); - }, - - unbind : function(target, name, func) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); - - return t.events.remove(target, name, func); - }, - - - _findSib : function(node, selector, name) { - var t = this, f = selector; - - if (node) { - // If expression make a function of it using is - if (is(f, 'string')) { - f = function(node) { - return t.is(node, selector); - }; - } - - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (f(node)) - return node; - } - } - - return null; - }, - - _isRes : function(c) { - // Is live resizble element - return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); - } - - /* - walk : function(n, f, s) { - var d = this.doc, w; - - if (d.createTreeWalker) { - w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); - - while ((n = w.nextNode()) != null) - f.call(s || this, n); - } else - tinymce.walk(n, f, 'childNodes', s); - } - */ - - /* - toRGB : function(s) { - var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); - - if (c) { - // #FFF -> #FFFFFF - if (!is(c[3])) - c[3] = c[2] = c[1]; - - return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; - } - - return s; - } - */ - }); - - tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); -})(tinymce); - -(function(ns) { - // Range constructor - function Range(dom) { - var t = this, - doc = dom.doc, - EXTRACT = 0, - CLONE = 1, - DELETE = 2, - TRUE = true, - FALSE = false, - START_OFFSET = 'startOffset', - START_CONTAINER = 'startContainer', - END_CONTAINER = 'endContainer', - END_OFFSET = 'endOffset', - extend = tinymce.extend, - nodeIndex = dom.nodeIndex; - - extend(t, { - // Inital states - startContainer : doc, - startOffset : 0, - endContainer : doc, - endOffset : 0, - collapsed : TRUE, - commonAncestorContainer : doc, - - // Range constants - START_TO_START : 0, - START_TO_END : 1, - END_TO_END : 2, - END_TO_START : 3, - - // Public methods - setStart : setStart, - setEnd : setEnd, - setStartBefore : setStartBefore, - setStartAfter : setStartAfter, - setEndBefore : setEndBefore, - setEndAfter : setEndAfter, - collapse : collapse, - selectNode : selectNode, - selectNodeContents : selectNodeContents, - compareBoundaryPoints : compareBoundaryPoints, - deleteContents : deleteContents, - extractContents : extractContents, - cloneContents : cloneContents, - insertNode : insertNode, - surroundContents : surroundContents, - cloneRange : cloneRange - }); - - function setStart(n, o) { - _setEndPoint(TRUE, n, o); - }; - - function setEnd(n, o) { - _setEndPoint(FALSE, n, o); - }; - - function setStartBefore(n) { - setStart(n.parentNode, nodeIndex(n)); - }; - - function setStartAfter(n) { - setStart(n.parentNode, nodeIndex(n) + 1); - }; - - function setEndBefore(n) { - setEnd(n.parentNode, nodeIndex(n)); - }; - - function setEndAfter(n) { - setEnd(n.parentNode, nodeIndex(n) + 1); - }; - - function collapse(ts) { - if (ts) { - t[END_CONTAINER] = t[START_CONTAINER]; - t[END_OFFSET] = t[START_OFFSET]; - } else { - t[START_CONTAINER] = t[END_CONTAINER]; - t[START_OFFSET] = t[END_OFFSET]; - } - - t.collapsed = TRUE; - }; - - function selectNode(n) { - setStartBefore(n); - setEndAfter(n); - }; - - function selectNodeContents(n) { - setStart(n, 0); - setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); - }; - - function compareBoundaryPoints(h, r) { - var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET]; - - // Check START_TO_START - if (h === 0) - return _compareBoundaryPoints(sc, so, sc, so); - - // Check START_TO_END - if (h === 1) - return _compareBoundaryPoints(sc, so, ec, eo); - - // Check END_TO_END - if (h === 2) - return _compareBoundaryPoints(ec, eo, ec, eo); - - // Check END_TO_START - if (h === 3) - return _compareBoundaryPoints(ec, eo, sc, so); - }; - - function deleteContents() { - _traverse(DELETE); - }; - - function extractContents() { - return _traverse(EXTRACT); - }; - - function cloneContents() { - return _traverse(CLONE); - }; - - function insertNode(n) { - var startContainer = this[START_CONTAINER], - startOffset = this[START_OFFSET], nn, o; - - // Node is TEXT_NODE or CDATA - if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { - if (!startOffset) { - // At the start of text - startContainer.parentNode.insertBefore(n, startContainer); - } else if (startOffset >= startContainer.nodeValue.length) { - // At the end of text - dom.insertAfter(n, startContainer); - } else { - // Middle, need to split - nn = startContainer.splitText(startOffset); - startContainer.parentNode.insertBefore(n, nn); - } - } else { - // Insert element node - if (startContainer.childNodes.length > 0) - o = startContainer.childNodes[startOffset]; - - if (o) - startContainer.insertBefore(n, o); - else - startContainer.appendChild(n); - } - }; - - function surroundContents(n) { - var f = t.extractContents(); - - t.insertNode(n); - n.appendChild(f); - t.selectNode(n); - }; - - function cloneRange() { - return extend(new Range(dom), { - startContainer : t[START_CONTAINER], - startOffset : t[START_OFFSET], - endContainer : t[END_CONTAINER], - endOffset : t[END_OFFSET], - collapsed : t.collapsed, - commonAncestorContainer : t.commonAncestorContainer - }); - }; - - // Private methods - - function _getSelectedNode(container, offset) { - var child; - - if (container.nodeType == 3 /* TEXT_NODE */) - return container; - - if (offset < 0) - return container; - - child = container.firstChild; - while (child && offset > 0) { - --offset; - child = child.nextSibling; - } - - if (child) - return child; - - return container; - }; - - function _isCollapsed() { - return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); - }; - - function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { - var c, offsetC, n, cmnRoot, childA, childB; - - // In the first case the boundary-points have the same container. A is before B - // if its offset is less than the offset of B, A is equal to B if its offset is - // equal to the offset of B, and A is after B if its offset is greater than the - // offset of B. - if (containerA == containerB) { - if (offsetA == offsetB) - return 0; // equal - - if (offsetA < offsetB) - return -1; // before - - return 1; // after - } - - // In the second case a child node C of the container of A is an ancestor - // container of B. In this case, A is before B if the offset of A is less than or - // equal to the index of the child node C and A is after B otherwise. - c = containerB; - while (c && c.parentNode != containerA) - c = c.parentNode; - - if (c) { - offsetC = 0; - n = containerA.firstChild; - - while (n != c && offsetC < offsetA) { - offsetC++; - n = n.nextSibling; - } - - if (offsetA <= offsetC) - return -1; // before - - return 1; // after - } - - // In the third case a child node C of the container of B is an ancestor container - // of A. In this case, A is before B if the index of the child node C is less than - // the offset of B and A is after B otherwise. - c = containerA; - while (c && c.parentNode != containerB) { - c = c.parentNode; - } - - if (c) { - offsetC = 0; - n = containerB.firstChild; - - while (n != c && offsetC < offsetB) { - offsetC++; - n = n.nextSibling; - } - - if (offsetC < offsetB) - return -1; // before - - return 1; // after - } - - // In the fourth case, none of three other cases hold: the containers of A and B - // are siblings or descendants of sibling nodes. In this case, A is before B if - // the container of A is before the container of B in a pre-order traversal of the - // Ranges' context tree and A is after B otherwise. - cmnRoot = dom.findCommonAncestor(containerA, containerB); - childA = containerA; - - while (childA && childA.parentNode != cmnRoot) - childA = childA.parentNode; - - if (!childA) - childA = cmnRoot; - - childB = containerB; - while (childB && childB.parentNode != cmnRoot) - childB = childB.parentNode; - - if (!childB) - childB = cmnRoot; - - if (childA == childB) - return 0; // equal - - n = cmnRoot.firstChild; - while (n) { - if (n == childA) - return -1; // before - - if (n == childB) - return 1; // after - - n = n.nextSibling; - } - }; - - function _setEndPoint(st, n, o) { - var ec, sc; - - if (st) { - t[START_CONTAINER] = n; - t[START_OFFSET] = o; - } else { - t[END_CONTAINER] = n; - t[END_OFFSET] = o; - } - - // If one boundary-point of a Range is set to have a root container - // other than the current one for the Range, the Range is collapsed to - // the new position. This enforces the restriction that both boundary- - // points of a Range must have the same root container. - ec = t[END_CONTAINER]; - while (ec.parentNode) - ec = ec.parentNode; - - sc = t[START_CONTAINER]; - while (sc.parentNode) - sc = sc.parentNode; - - if (sc == ec) { - // The start position of a Range is guaranteed to never be after the - // end position. To enforce this restriction, if the start is set to - // be at a position after the end, the Range is collapsed to that - // position. - if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) - t.collapse(st); - } else - t.collapse(st); - - t.collapsed = _isCollapsed(); - t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); - }; - - function _traverse(how) { - var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; - - if (t[START_CONTAINER] == t[END_CONTAINER]) - return _traverseSameContainer(how); - - for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[START_CONTAINER]) - return _traverseCommonStartContainer(c, how); - - ++endContainerDepth; - } - - for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[END_CONTAINER]) - return _traverseCommonEndContainer(c, how); - - ++startContainerDepth; - } - - depthDiff = startContainerDepth - endContainerDepth; - - startNode = t[START_CONTAINER]; - while (depthDiff > 0) { - startNode = startNode.parentNode; - depthDiff--; - } - - endNode = t[END_CONTAINER]; - while (depthDiff < 0) { - endNode = endNode.parentNode; - depthDiff++; - } - - // ascend the ancestor hierarchy until we have a common parent. - for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { - startNode = sp; - endNode = ep; - } - - return _traverseCommonAncestors(startNode, endNode, how); - }; - - function _traverseSameContainer(how) { - var frag, s, sub, n, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - // If selection is empty, just return the fragment - if (t[START_OFFSET] == t[END_OFFSET]) - return frag; - - // Text node needs special case handling - if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { - // get the substring - s = t[START_CONTAINER].nodeValue; - sub = s.substring(t[START_OFFSET], t[END_OFFSET]); - - // set the original text node to its new value - if (how != CLONE) { - t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); - - // Nothing is partially selected, so collapse to start point - t.collapse(TRUE); - } - - if (how == DELETE) - return; - - frag.appendChild(doc.createTextNode(sub)); - return frag; - } - - // Copy nodes between the start/end offsets. - n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); - cnt = t[END_OFFSET] - t[START_OFFSET]; - - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.appendChild( xferNode ); - - --cnt; - n = sibling; - } - - // Nothing is partially selected, so collapse to start point - if (how != CLONE) - t.collapse(TRUE); - - return frag; - }; - - function _traverseCommonStartContainer(endAncestor, how) { - var frag, n, endIdx, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseRightBoundary(endAncestor, how); - - if (frag) - frag.appendChild(n); - - endIdx = nodeIndex(endAncestor); - cnt = endIdx - t[START_OFFSET]; - - if (cnt <= 0) { - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); - } - - return frag; - } - - n = endAncestor.previousSibling; - while (cnt > 0) { - sibling = n.previousSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.insertBefore(xferNode, frag.firstChild); - - --cnt; - n = sibling; - } - - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); - } - - return frag; - }; - - function _traverseCommonEndContainer(startAncestor, how) { - var frag, startIdx, n, cnt, sibling, xferNode; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); - - startIdx = nodeIndex(startAncestor); - ++startIdx; // Because we already traversed it.... - - cnt = t[END_OFFSET] - startIdx; - n = startAncestor.nextSibling; - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); - - if (frag) - frag.appendChild(xferNode); - - --cnt; - n = sibling; - } - - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); - } - - return frag; - }; - - function _traverseCommonAncestors(startAncestor, endAncestor, how) { - var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; - - if (how != DELETE) - frag = doc.createDocumentFragment(); - - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); - - commonParent = startAncestor.parentNode; - startOffset = nodeIndex(startAncestor); - endOffset = nodeIndex(endAncestor); - ++startOffset; - - cnt = endOffset - startOffset; - sibling = startAncestor.nextSibling; - - while (cnt > 0) { - nextSibling = sibling.nextSibling; - n = _traverseFullySelected(sibling, how); - - if (frag) - frag.appendChild(n); - - sibling = nextSibling; - --cnt; - } - - n = _traverseRightBoundary(endAncestor, how); - - if (frag) - frag.appendChild(n); - - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); - } - - return frag; - }; - - function _traverseRightBoundary(root, how) { - var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; - - if (next == root) - return _traverseNode(next, isFullySelected, FALSE, how); - - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, FALSE, how); - - while (parent) { - while (next) { - prevSibling = next.previousSibling; - clonedChild = _traverseNode(next, isFullySelected, FALSE, how); - - if (how != DELETE) - clonedParent.insertBefore(clonedChild, clonedParent.firstChild); - - isFullySelected = TRUE; - next = prevSibling; - } - - if (parent == root) - return clonedParent; - - next = parent.previousSibling; - parent = parent.parentNode; - - clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); - - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); - - clonedParent = clonedGrandParent; - } - }; - - function _traverseLeftBoundary(root, how) { - var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; - - if (next == root) - return _traverseNode(next, isFullySelected, TRUE, how); - - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, TRUE, how); - - while (parent) { - while (next) { - nextSibling = next.nextSibling; - clonedChild = _traverseNode(next, isFullySelected, TRUE, how); - - if (how != DELETE) - clonedParent.appendChild(clonedChild); - - isFullySelected = TRUE; - next = nextSibling; - } - - if (parent == root) - return clonedParent; - - next = parent.nextSibling; - parent = parent.parentNode; - - clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); - - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); - - clonedParent = clonedGrandParent; - } - }; - - function _traverseNode(n, isFullySelected, isLeft, how) { - var txtValue, newNodeValue, oldNodeValue, offset, newNode; - - if (isFullySelected) - return _traverseFullySelected(n, how); - - if (n.nodeType == 3 /* TEXT_NODE */) { - txtValue = n.nodeValue; - - if (isLeft) { - offset = t[START_OFFSET]; - newNodeValue = txtValue.substring(offset); - oldNodeValue = txtValue.substring(0, offset); - } else { - offset = t[END_OFFSET]; - newNodeValue = txtValue.substring(0, offset); - oldNodeValue = txtValue.substring(offset); - } - - if (how != CLONE) - n.nodeValue = oldNodeValue; - - if (how == DELETE) - return; - - newNode = n.cloneNode(FALSE); - newNode.nodeValue = newNodeValue; - - return newNode; - } - - if (how == DELETE) - return; - - return n.cloneNode(FALSE); - }; - - function _traverseFullySelected(n, how) { - if (how != DELETE) - return how == CLONE ? n.cloneNode(TRUE) : n; - - n.parentNode.removeChild(n); - }; - }; - - ns.Range = Range; -})(tinymce.dom); - -(function() { - function Selection(selection) { - var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; - - // Compares two IE specific ranges to see if they are different - // this method is useful when invalidating the cached selection range - function compareRanges(rng1, rng2) { - if (rng1 && rng2) { - // Both are control ranges and the selected element matches - if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) - return TRUE; - - // Both are text ranges and the range matches - if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { - // IE will say that the range is equal then produce an invalid argument exception - // if you perform specific operations in a keyup event. For example Ctrl+Del. - // This hack will invalidate the range cache if the exception occurs - try { - // Try accessing nextSibling will producer an invalid argument some times - range.startContainer.nextSibling; - return TRUE; - } catch (ex) { - // Ignore - } - } - } - - return FALSE; - }; - - // Returns a W3C DOM compatible range object by using the IE Range API - function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged; - - // If selection is outside the current document just return an empty range - element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); - if (element.ownerDocument != dom.doc) - return domRange; - - // Handle control selection or text selection of a image - if (ieRange.item || !element.hasChildNodes()) { - domRange.setStart(element.parentNode, dom.nodeIndex(element)); - domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); - - return domRange; - } - - // Duplicare IE selection range and check if the range is collapsed - ieRange2 = ieRange.duplicate(); - collapsed = selection.isCollapsed(); - - // Insert invisible start marker - ieRange.collapse(); - ieRange.pasteHTML(''); - - // Insert invisible end marker - if (!collapsed) { - ieRange2.collapse(FALSE); - ieRange2.pasteHTML(''); - } - - // Sets the end point of the range by looking for the marker - // This method also merges the text nodes it splits so that - // the DOM doesn't get fragmented. - function setEndPoint(start) { - var container, offset, marker, sibling; - - // Look for endpoint marker - marker = dom.get('_mce_' + (start ? 'start' : 'end')); - sibling = marker.previousSibling; - - // Is marker after a text node - if (sibling && sibling.nodeType == 3) { - // Get container node and calc offset - container = sibling; - offset = container.nodeValue.length; - dom.remove(marker); - - // Merge text nodes to reduce DOM fragmentation - sibling = container.nextSibling; - if (sibling && sibling.nodeType == 3) { - isMerged = TRUE; - container.appendData(sibling.nodeValue); - dom.remove(sibling); - } - } else { - sibling = marker.nextSibling; - - // Is marker before a text node - if (sibling && sibling.nodeType == 3) { - container = sibling; - offset = 0; - } else { - // Is marker before an element - if (sibling) - offset = dom.nodeIndex(sibling) - 1; - else - offset = dom.nodeIndex(marker); - - container = marker.parentNode; - } - - dom.remove(marker); - } - - // Set start of range - if (start) - domRange.setStart(container, offset); - - // Set end of range or automatically if it's collapsed to increase performance - if (!start || collapsed) - domRange.setEnd(container, offset); - }; - - // Set start of range - setEndPoint(TRUE); - - // Set end of range if needed - if (!collapsed) - setEndPoint(FALSE); - - // Restore selection if the range contents was merged - // since the selection was then moved since the text nodes got changed - if (isMerged) - t.addRange(domRange); - - return domRange; - }; - - this.addRange = function(rng) { - var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd; - - this.destroy(); - - // Setup some shorter versions - sc = rng.startContainer; - so = rng.startOffset; - ec = rng.endContainer; - eo = rng.endOffset; - ieRng = body.createTextRange(); - - // If document selection move caret to first node in document - if (sc == doc || ec == doc) { - ieRng = body.createTextRange(); - ieRng.collapse(); - ieRng.select(); - return; - } - - // If child index resolve it - if (sc.nodeType == 1 && sc.hasChildNodes()) { - lastIndex = sc.childNodes.length - 1; - - // Index is higher that the child count then we need to jump over the start container - if (so > lastIndex) { - skipStart = 1; - sc = sc.childNodes[lastIndex]; - } else - sc = sc.childNodes[so]; - - // Child was text node then move offset to start of it - if (sc.nodeType == 3) - so = 0; - } - - // If child index resolve it - if (ec.nodeType == 1 && ec.hasChildNodes()) { - lastIndex = ec.childNodes.length - 1; - - if (eo == 0) { - skipEnd = 1; - ec = ec.childNodes[0]; - } else { - ec = ec.childNodes[Math.min(lastIndex, eo - 1)]; - - // Child was text node then move offset to end of text node - if (ec.nodeType == 3) - eo = ec.nodeValue.length; - } - } - - // Single element selection - if (sc == ec && sc.nodeType == 1) { - // Make control selection for some elements - if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) { - ieRng = body.createControlRange(); - ieRng.addElement(sc); - } else { - ieRng = body.createTextRange(); - - // Padd empty elements with invisible character - if (!sc.hasChildNodes() && sc.canHaveHTML) - sc.innerHTML = invisibleChar; - - // Select element contents - ieRng.moveToElementText(sc); - - // If it's only containing a padding remove it so the caret remains - if (sc.innerHTML == invisibleChar) { - ieRng.collapse(TRUE); - sc.removeChild(sc.firstChild); - } - } - - if (so == eo) - ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1); - - ieRng.select(); - ieRng.scrollIntoView(); - return; - } - - // Create range and marker - ieRng = body.createTextRange(); - marker = doc.createElement('span'); - marker.innerHTML = ' '; - - // Set start of range to startContainer/startOffset - if (sc.nodeType == 3) { - // Insert marker after/before startContainer - if (skipStart) - dom.insertAfter(marker, sc); - else - sc.parentNode.insertBefore(marker, sc); - - // Select marker the caret to offset position - ieRng.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng.move('character', so); - } else { - ieRng.moveToElementText(sc); - - if (skipStart) - ieRng.collapse(FALSE); - } - - // If same text container then we can do a more simple move - if (sc == ec && sc.nodeType == 3) { - ieRng.moveEnd('character', eo - so); - ieRng.select(); - ieRng.scrollIntoView(); - return; - } - - // Set end of range to endContainer/endOffset - ieRng2 = body.createTextRange(); - if (ec.nodeType == 3) { - // Insert marker after/before startContainer - ec.parentNode.insertBefore(marker, ec); - - // Move selection to end marker and move caret to end offset - ieRng2.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng2.move('character', eo); - ieRng.setEndPoint('EndToStart', ieRng2); - } else { - ieRng2.moveToElementText(ec); - ieRng2.collapse(!!skipEnd); - ieRng.setEndPoint('EndToEnd', ieRng2); - } - - ieRng.select(); - ieRng.scrollIntoView(); - }; - - this.getRangeAt = function() { - // Setup new range if the cache is empty - if (!range || !compareRanges(lastIERng, selection.getRng())) { - range = getRange(); - - // Store away text range for next call - lastIERng = selection.getRng(); - } - - // Return cached range - return range; - }; - - this.destroy = function() { - // Destroy cached range and last IE range to avoid memory leaks - lastIERng = range = null; - }; - - // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode - if (selection.dom.boxModel) { - (function() { - var doc = dom.doc, body = doc.body, started, startRng; - - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = TRUE; - - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); - - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } - - return rng; - }; - - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; - - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); - - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) - pointRng.setEndPoint('StartToStart', startRng); - else - pointRng.setEndPoint('EndToEnd', startRng); - - pointRng.select(); - } - } else - endSelection(); - } - - // Removes listeners - function endSelection() { - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - started = 0; - }; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) - endSelection(); - - started = 1; - - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); - - startRng.select(); - } - } - }); - })(); - } - }; - - // Expose the selection object - tinymce.dom.TridentSelection = Selection; -})(); - - -/* - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false; - -var Sizzle = function(selector, context, results, seed) { - results = results || []; - var origContext = context = context || document; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context); - - // Reset the position of the chunker regexp (start from head) - chunker.lastIndex = 0; - - while ( (m = chunker.exec(selector)) !== null ) { - parts.push( m[1] ); - - if ( m[2] ) { - extra = RegExp.rightContext; - break; - } - } - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) - selector += parts.shift(); - - set = posProcess( selector, set ); - } - } - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - - if ( context ) { - var ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } - - while ( parts.length ) { - var cur = parts.pop(), pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = false; - results.sort(sortOrder); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } -}; - -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; - -Sizzle.find = function(expr, context, isXML){ - var set, match; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.match[ type ].exec( expr )) ) { - var left = RegExp.leftContext; - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = context.getElementsByTagName("*"); - } - - return {set: set, expr: expr}; -}; - -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; - anyFound = false; - - if ( curLoop == result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr == old ) { - if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ - }, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag && !isXML ) { - part = part.toUpperCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; - - if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); - - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( typeof part === "string" && !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context, isXML){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) - result.push( elem ); - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); - }, - CHILD: function(match){ - if ( match[1] == "nth" ) { - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - // Accessing this property makes selected-by-default - // options in Safari work properly - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; - }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 == i; - }, - eq: function(elem, i, match){ - return match[3] - 0 == i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } - - return true; - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while (node = node.previousSibling) { - if ( node.nodeType === 1 ) return false; - } - if ( type == 'first') return true; - node = elem; - case 'last': - while (node = node.nextSibling) { - if ( node.nodeType === 1 ) return false; - } - return true; - case 'nth': - var first = match[2], last = match[3]; - - if ( first == 1 && last == 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first == 0 ) { - return diff == 0; - } else { - return ( diff % first == 0 && diff / first >= 0 ); - } - } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value != check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); -} - -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -try { - Array.prototype.slice.call( document.documentElement.childNodes ); - -// Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( var i = 0; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( "sourceIndex" in document.documentElement ) { - sortOrder = function( a, b ) { - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date).getTime(); - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); - }; - } -})(); - -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } -})(); - -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ - var div = document.createElement("div"); - div.innerHTML = "
    "; - - // Opera can't find a second classname (in 9.6) - if ( div.getElementsByClassName("e").length === 0 ) - return; - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) - return; - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; - -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; -}; - -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE - -window.tinymce.dom.Sizzle = Sizzle; - -})(); - - -(function(tinymce) { - // Shorten names - var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; - - tinymce.create('tinymce.dom.EventUtils', { - EventUtils : function() { - this.inits = []; - this.events = []; - }, - - add : function(o, n, f, s) { - var cb, t = this, el = t.events, r; - - if (n instanceof Array) { - r = []; - - each(n, function(n) { - r.push(t.add(o, n, f, s)); - }); - - return r; - } - - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; - - each(o, function(o) { - o = DOM.get(o); - r.push(t.add(o, n, f, s)); - }); - - return r; - } - - o = DOM.get(o); - - if (!o) - return; - - // Setup event callback - cb = function(e) { - // Is all events disabled - if (t.disabled) - return; - - e = e || window.event; - - // Patch in target, preventDefault and stopPropagation in IE it's W3C valid - if (e && isIE) { - if (!e.target) - e.target = e.srcElement; - - // Patch in preventDefault, stopPropagation methods for W3C compatibility - tinymce.extend(e, t._stoppers); - } - - if (!s) - return f(e); - - return f.call(s, e); - }; - - if (n == 'unload') { - tinymce.unloads.unshift({func : cb}); - return cb; - } - - if (n == 'init') { - if (t.domLoaded) - cb(); - else - t.inits.push(cb); - - return cb; - } - - // Store away listener reference - el.push({ - obj : o, - name : n, - func : f, - cfunc : cb, - scope : s - }); - - t._add(o, n, cb); - - return f; - }, - - remove : function(o, n, f) { - var t = this, a = t.events, s = false, r; - - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; - - each(o, function(o) { - o = DOM.get(o); - r.push(t.remove(o, n, f)); - }); - - return r; - } - - o = DOM.get(o); - - each(a, function(e, i) { - if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { - a.splice(i, 1); - t._remove(o, n, e.cfunc); - s = true; - return false; - } - }); - - return s; - }, - - clear : function(o) { - var t = this, a = t.events, i, e; - - if (o) { - o = DOM.get(o); - - for (i = a.length - 1; i >= 0; i--) { - e = a[i]; - - if (e.obj === o) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - a.splice(i, 1); - } - } - } - }, - - cancel : function(e) { - if (!e) - return false; - - this.stop(e); - - return this.prevent(e); - }, - - stop : function(e) { - if (e.stopPropagation) - e.stopPropagation(); - else - e.cancelBubble = true; - - return false; - }, - - prevent : function(e) { - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; - - return false; - }, - - destroy : function() { - var t = this; - - each(t.events, function(e, i) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - }); - - t.events = []; - t = null; - }, - - _add : function(o, n, f) { - if (o.attachEvent) - o.attachEvent('on' + n, f); - else if (o.addEventListener) - o.addEventListener(n, f, false); - else - o['on' + n] = f; - }, - - _remove : function(o, n, f) { - if (o) { - try { - if (o.detachEvent) - o.detachEvent('on' + n, f); - else if (o.removeEventListener) - o.removeEventListener(n, f, false); - else - o['on' + n] = null; - } catch (ex) { - // Might fail with permission denined on IE so we just ignore that - } - } - }, - - _pageInit : function(win) { - var t = this; - - // Keep it from running more than once - if (t.domLoaded) - return; - - t.domLoaded = true; - - each(t.inits, function(c) { - c(); - }); - - t.inits = []; - }, - - _wait : function(win) { - var t = this, doc = win.document; - - // No need since the document is already loaded - if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { - t.domLoaded = 1; - return; - } - - // Use IE method - if (doc.attachEvent) { - doc.attachEvent("onreadystatechange", function() { - if (doc.readyState === "complete") { - doc.detachEvent("onreadystatechange", arguments.callee); - t._pageInit(win); - } - }); - - if (doc.documentElement.doScroll && win == win.top) { - (function() { - if (t.domLoaded) - return; - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - doc.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(arguments.callee, 0); - return; - } - - t._pageInit(win); - })(); - } - } else if (doc.addEventListener) { - t._add(win, 'DOMContentLoaded', function() { - t._pageInit(win); - }); - } - - t._add(win, 'load', function() { - t._pageInit(win); - }); - }, - - _stoppers : { - preventDefault : function() { - this.returnValue = false; - }, - - stopPropagation : function() { - this.cancelBubble = true; - } - } - }); - - Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); - - // Dispatch DOM content loaded event for IE and Safari - Event._wait(window); - - tinymce.addUnload(function() { - Event.destroy(); - }); -})(tinymce); - -(function(tinymce) { - tinymce.dom.Element = function(id, settings) { - var t = this, dom, el; - - t.settings = settings = settings || {}; - t.id = id; - t.dom = dom = settings.dom || tinymce.DOM; - - // Only IE leaks DOM references, this is a lot faster - if (!tinymce.isIE) - el = dom.get(t.id); - - tinymce.each( - ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + - 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + - 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + - 'isHidden,setHTML,get').split(/,/) - , function(k) { - t[k] = function() { - var a = [id], i; - - for (i = 0; i < arguments.length; i++) - a.push(arguments[i]); - - a = dom[k].apply(dom, a); - t.update(k); - - return a; - }; - }); - - tinymce.extend(t, { - on : function(n, f, s) { - return tinymce.dom.Event.add(t.id, n, f, s); - }, - - getXY : function() { - return { - x : parseInt(t.getStyle('left')), - y : parseInt(t.getStyle('top')) - }; - }, - - getSize : function() { - var n = dom.get(t.id); - - return { - w : parseInt(t.getStyle('width') || n.clientWidth), - h : parseInt(t.getStyle('height') || n.clientHeight) - }; - }, - - moveTo : function(x, y) { - t.setStyles({left : x, top : y}); - }, - - moveBy : function(x, y) { - var p = t.getXY(); - - t.moveTo(p.x + x, p.y + y); - }, - - resizeTo : function(w, h) { - t.setStyles({width : w, height : h}); - }, - - resizeBy : function(w, h) { - var s = t.getSize(); - - t.resizeTo(s.w + w, s.h + h); - }, - - update : function(k) { - var b; - - if (tinymce.isIE6 && settings.blocker) { - k = k || ''; - - // Ignore getters - if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) - return; - - // Remove blocker on remove - if (k == 'remove') { - dom.remove(t.blocker); - return; - } - - if (!t.blocker) { - t.blocker = dom.uniqueId(); - b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); - dom.setStyle(b, 'opacity', 0); - } else - b = dom.get(t.blocker); - - dom.setStyles(b, { - left : t.getStyle('left', 1), - top : t.getStyle('top', 1), - width : t.getStyle('width', 1), - height : t.getStyle('height', 1), - display : t.getStyle('display', 1), - zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 - }); - } - } - }); - }; -})(tinymce); - -(function(tinymce) { - function trimNl(s) { - return s.replace(/[\n\r]+/g, ''); - }; - - // Shorten names - var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; - - tinymce.create('tinymce.dom.Selection', { - Selection : function(dom, win, serializer) { - var t = this; - - t.dom = dom; - t.win = win; - t.serializer = serializer; - - // Add events - each([ - 'onBeforeSetContent', - 'onBeforeGetContent', - 'onSetContent', - 'onGetContent' - ], function(e) { - t[e] = new tinymce.util.Dispatcher(t); - }); - - // No W3C Range support - if (!t.win.getSelection) - t.tridentSel = new tinymce.dom.TridentSelection(t); - - // Prevent leaks - tinymce.addUnload(t.destroy, t); - }, - - getContent : function(s) { - var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; - - s = s || {}; - wb = wa = ''; - s.get = true; - s.format = s.format || 'html'; - t.onBeforeGetContent.dispatch(t, s); - - if (s.format == 'text') - return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); - - if (r.cloneContents) { - n = r.cloneContents(); - - if (n) - e.appendChild(n); - } else if (is(r.item) || is(r.htmlText)) - e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText; - else - e.innerHTML = r.toString(); - - // Keep whitespace before and after - if (/^\s/.test(e.innerHTML)) - wb = ' '; - - if (/\s+$/.test(e.innerHTML)) - wa = ' '; - - s.getInner = true; - - s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; - t.onGetContent.dispatch(t, s); - - return s.content; - }, - - setContent : function(h, s) { - var t = this, r = t.getRng(), c, d = t.win.document; - - s = s || {format : 'html'}; - s.set = true; - h = s.content = t.dom.processHTML(h); - - // Dispatch before set content event - t.onBeforeSetContent.dispatch(t, s); - h = s.content; - - if (r.insertNode) { - // Make caret marker since insertNode places the caret in the beginning of text after insert - h += '_'; - - // Delete and insert new node - if (r.startContainer == d && r.endContainer == d) { - // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents - d.body.innerHTML = h; - } else { - r.deleteContents(); - r.insertNode(t.getRng().createContextualFragment(h)); - } - - // Move to caret marker - c = t.dom.get('__caret'); - - // Make sure we wrap it compleatly, Opera fails with a simple select call - r = d.createRange(); - r.setStartBefore(c); - r.setEndBefore(c); - t.setRng(r); - - // Remove the caret position - t.dom.remove('__caret'); - } else { - if (r.item) { - // Delete content and get caret text selection - d.execCommand('Delete', false, null); - r = t.getRng(); - } - - r.pasteHTML(h); - } - - // Dispatch set content event - t.onSetContent.dispatch(t, s); - }, - - getStart : function() { - var t = this, r = t.getRng(), e; - - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); - - r = r.duplicate(); - r.collapse(1); - e = r.parentElement(); - - if (e && e.nodeName == 'BODY') - return e.firstChild || e; - - return e; - } else { - e = r.startContainer; - - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)]; - - if (e && e.nodeType == 3) - return e.parentNode; - - return e; - } - }, - - getEnd : function() { - var t = this, r = t.getRng(), e, eo; - - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); - - r = r.duplicate(); - r.collapse(0); - e = r.parentElement(); - - if (e && e.nodeName == 'BODY') - return e.lastChild || e; - - return e; - } else { - e = r.endContainer; - eo = r.endOffset; - - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[eo > 0 ? eo - 1 : eo]; - - if (e && e.nodeType == 3) - return e.parentNode; - - return e; - } - }, - - getBookmark : function(type, normalized) { - var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; - - function findIndex(name, element) { - var index = 0; - - each(dom.select(name), function(node, i) { - if (node == element) - index = i; - }); - - return index; - }; - - if (type == 2) { - function getLocation() { - var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; - - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; - - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) - offset += node.nodeValue.length; - } - - point.push(offset); - } else { - childNodes = container.childNodes; - - if (offset >= childNodes.length) { - after = 1; - offset = childNodes.length - 1; - } - - point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); - } - - for (; container && container != root; container = container.parentNode) - point.push(t.dom.nodeIndex(container, normalized)); - - return point; - }; - - bookmark.start = getPoint(rng, true); - - if (!t.isCollapsed()) - bookmark.end = getPoint(rng); - - return bookmark; - }; - - return getLocation(); - } - - // Handle simple range - if (type) - return {rng : t.getRng()}; - - rng = t.getRng(); - id = dom.uniqueId(); - collapsed = tinyMCE.activeEditor.selection.isCollapsed(); - styles = 'overflow:hidden;line-height:0px'; - - // Explorer method - if (rng.duplicate || rng.item) { - // Text selection - if (!rng.item) { - rng2 = rng.duplicate(); - - // Insert start marker - rng.collapse(); - rng.pasteHTML('' + chr + ''); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.pasteHTML('' + chr + ''); - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name : name, index : findIndex(name, element)}; - } - } else { - element = t.getNode(); - name = element.nodeName; - if (name == 'IMG') - return {name : name, index : findIndex(name, element)}; - - // W3C method - rng2 = rng.cloneRange(); - - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr)); - } - - rng.collapse(true); - rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr)); - } - - t.moveToBookmark({id : id, keep : 1}); - - return {id : id}; - }, - - moveToBookmark : function(bookmark) { - var t = this, dom = t.dom, marker1, marker2, rng, root; - - // Clear selection cache - if (t.tridentSel) - t.tridentSel.destroy(); - - if (bookmark) { - if (bookmark.start) { - rng = dom.createRng(); - root = dom.getRoot(); - - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset; - - if (point) { - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) - node = node.childNodes[point[i]]; - - // Set offset within container node - if (start) - rng.setStart(node, point[0]); - else - rng.setEnd(node, point[0]); - } - }; - - setEndPoint(true); - setEndPoint(); - - t.setRng(rng); - } else if (bookmark.id) { - rng = dom.createRng(); - - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; - - if (marker) { - node = marker.parentNode; - - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } - - rng.setStart(node, idx); - rng.setEnd(node, idx); - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } - - rng.setEnd(node, idx); - } - - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; - - // Remove all marker text nodes - each(tinymce.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - }); - - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature - while (marker = dom.get(bookmark.id + '_' + suffix)) - dom.remove(marker, 1); - - // If siblings are text nodes then merge them - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); - - if (suffix == 'start') { - rng.setStart(prev, idx); - rng.setEnd(prev, idx); - } else - rng.setEnd(prev, idx); - } - } - } - }; - - // Restore start/end points - restoreEndPoint('start'); - restoreEndPoint('end'); - - t.setRng(rng); - } else if (bookmark.name) { - t.select(dom.select(bookmark.name)[bookmark.index]); - } else if (bookmark.rng) - t.setRng(bookmark.rng); - } - }, - - select : function(node, content) { - var t = this, dom = t.dom, rng = dom.createRng(), idx; - - idx = dom.nodeIndex(node); - rng.setStart(node.parentNode, idx); - rng.setEnd(node.parentNode, idx + 1); - - // Find first/last text node or BR element - if (content) { - function setPoint(node, start) { - var walker = new tinymce.dom.TreeWalker(node, node); - - do { - // Text node - if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { - if (start) - rng.setStart(node, 0); - else - rng.setEnd(node, node.nodeValue.length); - - return; - } - - // BR element - if (node.nodeName == 'BR') { - if (start) - rng.setStartBefore(node); - else - rng.setEndBefore(node); - - return; - } - } while (node = (start ? walker.next() : walker.prev())); - }; - - setPoint(node, 1); - setPoint(node); - } - - t.setRng(rng); - - return node; - }, - - isCollapsed : function() { - var t = this, r = t.getRng(), s = t.getSel(); - - if (!r || r.item) - return false; - - if (r.compareEndPoints) - return r.compareEndPoints('StartToEnd', r) === 0; - - return !s || r.collapsed; - }, - - collapse : function(b) { - var t = this, r = t.getRng(), n; - - // Control range on IE - if (r.item) { - n = r.item(0); - r = this.win.document.body.createTextRange(); - r.moveToElementText(n); - } - - r.collapse(!!b); - t.setRng(r); - }, - - getSel : function() { - var t = this, w = this.win; - - return w.getSelection ? w.getSelection() : w.document.selection; - }, - - getRng : function(w3c) { - var t = this, s, r; - - // Found tridentSel object then we need to use that one - if (w3c && t.tridentSel) - return t.tridentSel.getRangeAt(0); - - try { - if (s = t.getSel()) - r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange()); - } catch (ex) { - // IE throws unspecified error here if TinyMCE is placed in a frame/iframe - } - - // No range found then create an empty one - // This can occur when the editor is placed in a hidden container element on Gecko - // Or on IE when there was an exception - if (!r) - r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange(); - - return r; - }, - - setRng : function(r) { - var s, t = this; - - if (!t.tridentSel) { - s = t.getSel(); - - if (s) { - s.removeAllRanges(); - s.addRange(r); - } - } else { - // Is W3C Range - if (r.cloneRange) { - t.tridentSel.addRange(r); - return; - } - - // Is IE specific range - try { - r.select(); - } catch (ex) { - // Needed for some odd IE bug #1843306 - } - } - }, - - setNode : function(n) { - var t = this; - - t.setContent(t.dom.getOuterHTML(n)); - - return n; - }, - - getNode : function() { - var t = this, rng = t.getRng(), sel = t.getSel(), elm; - - if (rng.setStart) { - // Range maybe lost after the editor is made visible again - if (!rng) - return t.dom.getRoot(); - - elm = rng.commonAncestorContainer; - - // Handle selection a image or other control like element such as anchors - if (!rng.collapsed) { - if (rng.startContainer == rng.endContainer) { - if (rng.startOffset - rng.endOffset < 2) { - if (rng.startContainer.hasChildNodes()) - elm = rng.startContainer.childNodes[rng.startOffset]; - } - } - - // If the anchor node is a element instead of a text node then return this element - if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) - return sel.anchorNode.childNodes[sel.anchorOffset]; - } - - if (elm && elm.nodeType == 3) - return elm.parentNode; - - return elm; - } - - return rng.item ? rng.item(0) : rng.parentElement(); - }, - - getSelectedBlocks : function(st, en) { - var t = this, dom = t.dom, sb, eb, n, bl = []; - - sb = dom.getParent(st || t.getStart(), dom.isBlock); - eb = dom.getParent(en || t.getEnd(), dom.isBlock); - - if (sb) - bl.push(sb); - - if (sb && eb && sb != eb) { - n = sb; - - while ((n = n.nextSibling) && n != eb) { - if (dom.isBlock(n)) - bl.push(n); - } - } - - if (eb && sb != eb) - bl.push(eb); - - return bl; - }, - - destroy : function(s) { - var t = this; - - t.win = null; - - if (t.tridentSel) - t.tridentSel.destroy(); - - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - } - }); -})(tinymce); - -(function(tinymce) { - tinymce.create('tinymce.dom.XMLWriter', { - node : null, - - XMLWriter : function(s) { - // Get XML document - function getXML() { - var i = document.implementation; - - if (!i || !i.createDocument) { - // Try IE objects - try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {} - try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {} - } else - return i.createDocument('', '', null); - }; - - this.doc = getXML(); - - // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers - this.valid = tinymce.isOpera || tinymce.isWebKit; - - this.reset(); - }, - - reset : function() { - var t = this, d = t.doc; - - if (d.firstChild) - d.removeChild(d.firstChild); - - t.node = d.appendChild(d.createElement("html")); - }, - - writeStartElement : function(n) { - var t = this; - - t.node = t.node.appendChild(t.doc.createElement(n)); - }, - - writeAttribute : function(n, v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); - - this.node.setAttribute(n, v); - }, - - writeEndElement : function() { - this.node = this.node.parentNode; - }, - - writeFullEndElement : function() { - var t = this, n = t.node; - - n.appendChild(t.doc.createTextNode("")); - t.node = n.parentNode; - }, - - writeText : function(v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); - - this.node.appendChild(this.doc.createTextNode(v)); - }, - - writeCDATA : function(v) { - this.node.appendChild(this.doc.createCDATASection(v)); - }, - - writeComment : function(v) { - // Fix for bug #2035694 - if (tinymce.isIE) - v = v.replace(/^\-|\-$/g, ' '); - - this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' '))); - }, - - getContent : function() { - var h; - - h = this.doc.xml || new XMLSerializer().serializeToString(this.doc); - h = h.replace(/<\?[^?]+\?>||<\/html>||]+>/g, ''); - h = h.replace(/ ?\/>/g, ' />'); - - if (this.valid) - h = h.replace(/\%MCGT%/g, '>'); - - return h; - } - }); -})(tinymce); - -(function(tinymce) { - tinymce.create('tinymce.dom.StringWriter', { - str : null, - tags : null, - count : 0, - settings : null, - indent : null, - - StringWriter : function(s) { - this.settings = tinymce.extend({ - indent_char : ' ', - indentation : 0 - }, s); - - this.reset(); - }, - - reset : function() { - this.indent = ''; - this.str = ""; - this.tags = []; - this.count = 0; - }, - - writeStartElement : function(n) { - this._writeAttributesEnd(); - this.writeRaw('<' + n); - this.tags.push(n); - this.inAttr = true; - this.count++; - this.elementCount = this.count; - }, - - writeAttribute : function(n, v) { - var t = this; - - t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"'); - }, - - writeEndElement : function() { - var n; - - if (this.tags.length > 0) { - n = this.tags.pop(); - - if (this._writeAttributesEnd(1)) - this.writeRaw(''); - - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, - - writeFullEndElement : function() { - if (this.tags.length > 0) { - this._writeAttributesEnd(); - this.writeRaw(''); - - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, - - writeText : function(v) { - this._writeAttributesEnd(); - this.writeRaw(this.encode(v)); - this.count++; - }, - - writeCDATA : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, - - writeComment : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, - - writeRaw : function(v) { - this.str += v; - }, - - encode : function(s) { - return s.replace(/[<>&"]/g, function(v) { - switch (v) { - case '<': - return '<'; - - case '>': - return '>'; - - case '&': - return '&'; - - case '"': - return '"'; - } - - return v; - }); - }, - - getContent : function() { - return this.str; - }, - - _writeAttributesEnd : function(s) { - if (!this.inAttr) - return; - - this.inAttr = false; - - if (s && this.elementCount == this.count) { - this.writeRaw(' />'); - return false; - } - - this.writeRaw('>'); - - return true; - } - }); -})(tinymce); - -(function(tinymce) { - // Shorten names - var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko; - - function wildcardToRE(s) { - return s.replace(/([?+*])/g, '.$1'); - }; - - tinymce.create('tinymce.dom.Serializer', { - Serializer : function(s) { - var t = this; - - t.key = 0; - t.onPreProcess = new Dispatcher(t); - t.onPostProcess = new Dispatcher(t); - - try { - t.writer = new tinymce.dom.XMLWriter(); - } catch (ex) { - // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter - t.writer = new tinymce.dom.StringWriter(); - } - - // Default settings - t.settings = s = extend({ - dom : tinymce.DOM, - valid_nodes : 0, - node_filter : 0, - attr_filter : 0, - invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/, - closed : /^(br|hr|input|meta|img|link|param|area)$/, - entity_encoding : 'named', - entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro', - valid_elements : '*[*]', - extended_valid_elements : 0, - invalid_elements : 0, - fix_table_elements : 1, - fix_list_elements : true, - fix_content_duplication : true, - convert_fonts_to_spans : false, - font_size_classes : 0, - apply_source_formatting : 0, - indent_mode : 'simple', - indent_char : '\t', - indent_levels : 1, - remove_linebreaks : 1, - remove_redundant_brs : 1, - element_format : 'xhtml' - }, s); - - t.dom = s.dom; - t.schema = s.schema; - - // Use raw entities if no entities are defined - if (s.entity_encoding == 'named' && !s.entities) - s.entity_encoding = 'raw'; - - if (s.remove_redundant_brs) { - t.onPostProcess.add(function(se, o) { - // Remove single BR at end of block elements since they get rendered - o.content = o.content.replace(/(
    \s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) { - // Check if it's a single element - if (/^
    \s*<\//.test(a)) - return ''; - - return a; - }); - }); - } - - // Remove XHTML element endings i.e. produce crap :) XHTML is better - if (s.element_format == 'html') { - t.onPostProcess.add(function(se, o) { - o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>'); - }); - } - - if (s.fix_list_elements) { - t.onPreProcess.add(function(se, o) { - var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np; - - function prevNode(e, n) { - var a = n.split(','), i; - - while ((e = e.previousSibling) != null) { - for (i=0; i= 1767) { - each(t.dom.select('p table', o.node).reverse(), function(n) { - var parent = t.dom.getParent(n.parentNode, 'table,p'); - - if (parent.nodeName != 'TABLE') { - try { - t.dom.split(parent, n); - } catch (ex) { - // IE can sometimes fire an unknown runtime error so we just ignore it - } - } - }); - } - }); - } - }, - - setEntities : function(s) { - var t = this, a, i, l = {}, v; - - // No need to setup more than once - if (t.entityLookup) - return; - - // Build regex and lookup array - a = s.split(','); - for (i = 0; i < a.length; i += 2) { - v = a[i]; - - // Don't add default & " etc. - if (v == 34 || v == 38 || v == 60 || v == 62) - continue; - - l[String.fromCharCode(a[i])] = a[i + 1]; - - v = parseInt(a[i]).toString(16); - } - - t.entityLookup = l; - }, - - setRules : function(s) { - var t = this; - - t._setup(); - t.rules = {}; - t.wildRules = []; - t.validElements = {}; - - return t.addRules(s); - }, - - addRules : function(s) { - var t = this, dr; - - if (!s) - return; - - t._setup(); - - each(s.split(','), function(s) { - var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = []; - - // Extend with default rules - if (dr) - at = tinymce.extend([], dr.attribs); - - // Parse attributes - if (p.length > 1) { - each(p[1].split('|'), function(s) { - var ar = {}, i; - - at = at || []; - - // Parse attribute rule - s = s.replace(/::/g, '~'); - s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s); - s[2] = s[2].replace(/~/g, ':'); - - // Add required attributes - if (s[1] == '!') { - ra = ra || []; - ra.push(s[2]); - } - - // Remove inherited attributes - if (s[1] == '-') { - for (i = 0; i = 1767)) { - // Create an empty HTML document - doc = impl.createHTMLDocument(""); - - // Add the element or it's children if it's a body element to the new document - each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) { - doc.body.appendChild(doc.importNode(node, true)); - }); - - // Grab first child or body element for serialization - if (n.nodeName != 'BODY') - n = doc.body.firstChild; - else - n = doc.body; - - // set the new document in DOMUtils so createElement etc works - oldDoc = t.dom.doc; - t.dom.doc = doc; - } - - t.key = '' + (parseInt(t.key) + 1); - - // Pre process - if (!o.no_events) { - o.node = n; - t.onPreProcess.dispatch(t, o); - } - - // Serialize HTML DOM into a string - t.writer.reset(); - t._info = o; - t._serializeNode(n, o.getInner); - - // Post process - o.content = t.writer.getContent(); - - // Restore the old document if it was changed - if (oldDoc) - t.dom.doc = oldDoc; - - if (!o.no_events) - t.onPostProcess.dispatch(t, o); - - t._postProcess(o); - o.node = null; - - return tinymce.trim(o.content); - }, - - // Internal functions - - _postProcess : function(o) { - var t = this, s = t.settings, h = o.content, sc = [], p; - - if (o.format == 'html') { - // Protect some elements - p = t._protect({ - content : h, - patterns : [ - {pattern : /(]*>)(.*?)(<\/script>)/g}, - {pattern : /(]*>)(.*?)(<\/noscript>)/g}, - {pattern : /(]*>)(.*?)(<\/style>)/g}, - {pattern : /(]*>)(.*?)(<\/pre>)/g, encode : 1}, - {pattern : /()/g} - ] - }); - - h = p.content; - - // Entity encode - if (s.entity_encoding !== 'raw') - h = t._encode(h); - - // Use BR instead of   padded P elements inside editor and use

     

    outside editor -/* if (o.set) - h = h.replace(/

    \s+( | |\u00a0|
    )\s+<\/p>/g, '


    '); - else - h = h.replace(/

    \s+( | |\u00a0|
    )\s+<\/p>/g, '

    $1

    ');*/ - - // Since Gecko and Safari keeps whitespace in the DOM we need to - // remove it inorder to match other browsers. But I think Gecko and Safari is right. - // This process is only done when getting contents out from the editor. - if (!o.set) { - // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char - h = h.replace(/

    \s+<\/p>|]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? ' 

    ' : ' 

    '); - - if (s.remove_linebreaks) { - h = h.replace(/\r?\n|\r/g, ' '); - h = h.replace(/(<[^>]+>)\s+/g, '$1 '); - h = h.replace(/\s+(<\/[^>]+>)/g, ' $1'); - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start - h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, ''); // Trim block end - } - - // Simple indentation - if (s.apply_source_formatting && s.indent_mode == 'simple') { - // Add line breaks before and after block elements - h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n'); - h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>'); - h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '\n'); - h = h.replace(/\n\n/g, '\n'); - } - } - - h = t._unprotect(h, p); - - // Restore CDATA sections - h = h.replace(//g, ''); - - // Restore the \u00a0 character if raw mode is enabled - if (s.entity_encoding == 'raw') - h = h.replace(/

     <\/p>|]+)> <\/p>/g, '\u00a0

    '); - - // Restore noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return '' + t.dom.decode(text.replace(//g, '')) + ''; - }); - } - - o.content = h; - }, - - _serializeNode : function(n, inner) { - var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type; - - if (!s.node_filter || s.node_filter(n)) { - switch (n.nodeType) { - case 1: // Element - if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus')) - return; - - iv = keep = false; - hc = n.hasChildNodes(); - nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase(); - - // Get internal type - type = n.getAttribute('_mce_type'); - if (type) { - if (!t._info.cleanup) { - iv = true; - return; - } else - keep = 1; - } - - // Add correct prefix on IE - if (isIE) { - if (n.scopeName !== 'HTML' && n.scopeName !== 'html') - nn = n.scopeName + ':' + nn; - } - - // Remove mce prefix on IE needed for the abbr element - if (nn.indexOf('mce:') === 0) - nn = nn.substring(4); - - // Check if valid - if (!keep) { - if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) { - iv = true; - break; - } - } - - if (isIE) { - // Fix IE content duplication (DOM can have multiple copies of the same node) - if (s.fix_content_duplication) { - if (n._mce_serialized == t.key) - return; - - n._mce_serialized = t.key; - } - - // IE sometimes adds a / infront of the node name - if (nn.charAt(0) == '/') - nn = nn.substring(1); - } else if (isGecko) { - // Ignore br elements - if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz') - return; - } - - // Check if valid child - if (s.validate_children) { - if (t.elementName && !t.schema.isValid(t.elementName, nn)) { - iv = true; - break; - } - - t.elementName = nn; - } - - ru = t.findRule(nn); - - // No valid rule for this element could be found then skip it - if (!ru) { - iv = true; - break; - } - - nn = ru.name || nn; - closed = s.closed.test(nn); - - // Skip empty nodes or empty node name in IE - if ((!hc && ru.noEmpty) || (isIE && !nn)) { - iv = true; - break; - } - - // Check required - if (ru.requiredAttribs) { - a = ru.requiredAttribs; - - for (i = a.length - 1; i >= 0; i--) { - if (this.dom.getAttrib(n, a[i]) !== '') - break; - } - - // None of the required was there - if (i == -1) { - iv = true; - break; - } - } - - w.writeStartElement(nn); - - // Add ordered attributes - if (ru.attribs) { - for (i=0, at = ru.attribs, l = at.length; i-1; i--) { - no = at[i]; - - if (no.specified) { - a = no.nodeName.toLowerCase(); - - if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a)) - continue; - - ar = t.findAttribRule(ru, a); - v = t._getAttrib(n, ar, a); - - if (v !== null) - w.writeAttribute(a, v); - } - } - } - - // Keep type attribute - if (type && keep) - w.writeAttribute('_mce_type', type); - - // Write text from script - if (nn === 'script' && tinymce.trim(n.innerHTML)) { - w.writeText('// '); // Padd it with a comment so it will parse on older browsers - w.writeCDATA(n.innerHTML.replace(/|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures - hc = false; - break; - } - - // Padd empty nodes with a   - if (ru.padd) { - // If it has only one bogus child, padd it anyway workaround for
    bug - if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) { - if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus')) - w.writeText('\u00a0'); - } else if (!hc) - w.writeText('\u00a0'); // No children then padd it - } - - break; - - case 3: // Text - // Check if valid child - if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text')) - return; - - return w.writeText(n.nodeValue); - - case 4: // CDATA - return w.writeCDATA(n.nodeValue); - - case 8: // Comment - return w.writeComment(n.nodeValue); - } - } else if (n.nodeType == 1) - hc = n.hasChildNodes(); - - if (hc && !closed) { - cn = n.firstChild; - - while (cn) { - t._serializeNode(cn); - t.elementName = nn; - cn = cn.nextSibling; - } - } - - // Write element end - if (!iv) { - if (!closed) - w.writeFullEndElement(); - else - w.writeEndElement(); - } - }, - - _protect : function(o) { - var t = this; - - o.items = o.items || []; - - function enc(s) { - return s.replace(/[\r\n\\]/g, function(c) { - if (c === '\n') - return '\\n'; - else if (c === '\\') - return '\\\\'; - - return '\\r'; - }); - }; - - function dec(s) { - return s.replace(/\\[\\rn]/g, function(c) { - if (c === '\\n') - return '\n'; - else if (c === '\\\\') - return '\\'; - - return '\r'; - }); - }; - - each(o.patterns, function(p) { - o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) { - b = dec(b); - - if (p.encode) - b = t._encode(b); - - o.items.push(b); - return a + '' + c; - })); - }); - - return o; - }, - - _unprotect : function(h, o) { - h = h.replace(/\')); - } - - // Add toolbar end before list box and after the previous button - // This is to fix the o2k7 editor skins - if (pr && co.ListBox) { - if (pr.Button || pr.SplitButton) - h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); - } - - // Render control HTML - - // IE 8 quick fix, needed to propertly generate a hit area for anchors - if (dom.stdMode) - h += '' + co.renderHTML() + ''; - else - h += '' + co.renderHTML() + ''; - - // Add toolbar start after list box and before the next button - // This is to fix the o2k7 editor skins - if (nx && co.ListBox) { - if (nx.Button || nx.SplitButton) - h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); - } - } - - c = 'mceToolbarEnd'; - - if (co.Button) - c += ' mceToolbarEndButton'; - else if (co.SplitButton) - c += ' mceToolbarEndSplitButton'; - else if (co.ListBox) - c += ' mceToolbarEndListBox'; - - h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); - - return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '' + h + ''); - } -}); - -(function(tinymce) { - var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; - - tinymce.create('tinymce.AddOnManager', { - items : [], - urls : {}, - lookup : {}, - - onAdd : new Dispatcher(this), - - get : function(n) { - return this.lookup[n]; - }, - - requireLangPack : function(n) { - var s = tinymce.settings; - - if (s && s.language) - tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); - }, - - add : function(id, o) { - this.items.push(o); - this.lookup[id] = o; - this.onAdd.dispatch(this, id, o); - - return o; - }, - - load : function(n, u, cb, s) { - var t = this; - - if (t.urls[n]) - return; - - if (u.indexOf('/') != 0 && u.indexOf('://') == -1) - u = tinymce.baseURL + '/' + u; - - t.urls[n] = u.substring(0, u.lastIndexOf('/')); - tinymce.ScriptLoader.add(u, cb, s); - } - }); - - // Create plugin and theme managers - tinymce.PluginManager = new tinymce.AddOnManager(); - tinymce.ThemeManager = new tinymce.AddOnManager(); -}(tinymce)); - -(function(tinymce) { - // Shorten names - var each = tinymce.each, extend = tinymce.extend, - DOM = tinymce.DOM, Event = tinymce.dom.Event, - ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, - explode = tinymce.explode, - Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; - - // Setup some URLs where the editor API is located and where the document is - tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); - if (!/[\/\\]$/.test(tinymce.documentBaseURL)) - tinymce.documentBaseURL += '/'; - - tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); - - tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); - - // Add before unload listener - // This was required since IE was leaking memory if you added and removed beforeunload listeners - // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event - tinymce.onBeforeUnload = new Dispatcher(tinymce); - - // Must be on window or IE will leak if the editor is placed in frame or iframe - Event.add(window, 'beforeunload', function(e) { - tinymce.onBeforeUnload.dispatch(tinymce, e); - }); - - tinymce.onAddEditor = new Dispatcher(tinymce); - - tinymce.onRemoveEditor = new Dispatcher(tinymce); - - tinymce.EditorManager = extend(tinymce, { - editors : [], - - i18n : {}, - - activeEditor : null, - - init : function(s) { - var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; - - function execCallback(se, n, s) { - var f = se[n]; - - if (!f) - return; - - if (tinymce.is(f, 'string')) { - s = f.replace(/\.\w+$/, ''); - s = s ? tinymce.resolve(s) : 0; - f = tinymce.resolve(f); - } - - return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); - }; - - s = extend({ - theme : "simple", - language : "en" - }, s); - - t.settings = s; - - // Legacy call - Event.add(document, 'init', function() { - var l, co; - - execCallback(s, 'onpageload'); - - switch (s.mode) { - case "exact": - l = s.elements || ''; - - if(l.length > 0) { - each(explode(l), function(v) { - if (DOM.get(v)) { - ed = new tinymce.Editor(v, s); - el.push(ed); - ed.render(1); - } else { - each(document.forms, function(f) { - each(f.elements, function(e) { - if (e.name === v) { - v = 'mce_editor_' + instanceCounter++; - DOM.setAttrib(e, 'id', v); - - ed = new tinymce.Editor(v, s); - el.push(ed); - ed.render(1); - } - }); - }); - } - }); - } - break; - - case "textareas": - case "specific_textareas": - function hasClass(n, c) { - return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); - }; - - each(DOM.select('textarea'), function(v) { - if (s.editor_deselector && hasClass(v, s.editor_deselector)) - return; - - if (!s.editor_selector || hasClass(v, s.editor_selector)) { - // Can we use the name - e = DOM.get(v.name); - if (!v.id && !e) - v.id = v.name; - - // Generate unique name if missing or already exists - if (!v.id || t.get(v.id)) - v.id = DOM.uniqueId(); - - ed = new tinymce.Editor(v.id, s); - el.push(ed); - ed.render(1); - } - }); - break; - } - - // Call onInit when all editors are initialized - if (s.oninit) { - l = co = 0; - - each(el, function(ed) { - co++; - - if (!ed.initialized) { - // Wait for it - ed.onInit.add(function() { - l++; - - // All done - if (l == co) - execCallback(s, 'oninit'); - }); - } else - l++; - - // All done - if (l == co) - execCallback(s, 'oninit'); - }); - } - }); - }, - - get : function(id) { - if (id === undefined) - return this.editors; - - return this.editors[id]; - }, - - getInstanceById : function(id) { - return this.get(id); - }, - - add : function(editor) { - var self = this, editors = self.editors; - - // Add named and index editor instance - editors[editor.id] = editor; - editors.push(editor); - - self._setActive(editor); - self.onAddEditor.dispatch(self, editor); - - - return editor; - }, - - remove : function(editor) { - var t = this, i, editors = t.editors; - - // Not in the collection - if (!editors[editor.id]) - return null; - - delete editors[editor.id]; - - for (i = 0; i < editors.length; i++) { - if (editors[i] == editor) { - editors.splice(i, 1); - break; - } - } - - // Select another editor since the active one was removed - if (t.activeEditor == editor) - t._setActive(editors[0]); - - editor.destroy(); - t.onRemoveEditor.dispatch(t, editor); - - return editor; - }, - - execCommand : function(c, u, v) { - var t = this, ed = t.get(v), w; - - // Manager commands - switch (c) { - case "mceFocus": - ed.focus(); - return true; - - case "mceAddEditor": - case "mceAddControl": - if (!t.get(v)) - new tinymce.Editor(v, t.settings).render(); - - return true; - - case "mceAddFrameControl": - w = v.window; - - // Add tinyMCE global instance and tinymce namespace to specified window - w.tinyMCE = tinyMCE; - w.tinymce = tinymce; - - tinymce.DOM.doc = w.document; - tinymce.DOM.win = w; - - ed = new tinymce.Editor(v.element_id, v); - ed.render(); - - // Fix IE memory leaks - if (tinymce.isIE) { - function clr() { - ed.destroy(); - w.detachEvent('onunload', clr); - w = w.tinyMCE = w.tinymce = null; // IE leak - }; - - w.attachEvent('onunload', clr); - } - - v.page_window = null; - - return true; - - case "mceRemoveEditor": - case "mceRemoveControl": - if (ed) - ed.remove(); - - return true; - - case 'mceToggleEditor': - if (!ed) { - t.execCommand('mceAddControl', 0, v); - return true; - } - - if (ed.isHidden()) - ed.show(); - else - ed.hide(); - - return true; - } - - // Run command on active editor - if (t.activeEditor) - return t.activeEditor.execCommand(c, u, v); - - return false; - }, - - execInstanceCommand : function(id, c, u, v) { - var ed = this.get(id); - - if (ed) - return ed.execCommand(c, u, v); - - return false; - }, - - triggerSave : function() { - each(this.editors, function(e) { - e.save(); - }); - }, - - addI18n : function(p, o) { - var lo, i18n = this.i18n; - - if (!tinymce.is(p, 'string')) { - each(p, function(o, lc) { - each(o, function(o, g) { - each(o, function(o, k) { - if (g === 'common') - i18n[lc + '.' + k] = o; - else - i18n[lc + '.' + g + '.' + k] = o; - }); - }); - }); - } else { - each(o, function(o, k) { - i18n[p + '.' + k] = o; - }); - } - }, - - // Private methods - - _setActive : function(editor) { - this.selectedInstance = this.activeEditor = editor; - } - }); -})(tinymce); - -(function(tinymce) { - // Shorten these names - var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, - Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko, - isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, - ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, - inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; - - tinymce.create('tinymce.Editor', { - Editor : function(id, s) { - var t = this; - - t.id = t.editorId = id; - - t.execCommands = {}; - t.queryStateCommands = {}; - t.queryValueCommands = {}; - - t.isNotDirty = false; - - t.plugins = {}; - - // Add events to the editor - each([ - 'onPreInit', - - 'onBeforeRenderUI', - - 'onPostRender', - - 'onInit', - - 'onRemove', - - 'onActivate', - - 'onDeactivate', - - 'onClick', - - 'onEvent', - - 'onMouseUp', - - 'onMouseDown', - - 'onDblClick', - - 'onKeyDown', - - 'onKeyUp', - - 'onKeyPress', - - 'onContextMenu', - - 'onSubmit', - - 'onReset', - - 'onPaste', - - 'onPreProcess', - - 'onPostProcess', - - 'onBeforeSetContent', - - 'onBeforeGetContent', - - 'onSetContent', - - 'onGetContent', - - 'onLoadContent', - - 'onSaveContent', - - 'onNodeChange', - - 'onChange', - - 'onBeforeExecCommand', - - 'onExecCommand', - - 'onUndo', - - 'onRedo', - - 'onVisualAid', - - 'onSetProgressState' - ], function(e) { - t[e] = new Dispatcher(t); - }); - - t.settings = s = extend({ - id : id, - language : 'en', - docs_language : 'en', - theme : 'simple', - skin : 'default', - delta_width : 0, - delta_height : 0, - popup_css : '', - plugins : '', - document_base_url : tinymce.documentBaseURL, - add_form_submit_trigger : 1, - submit_patch : 1, - add_unload_trigger : 1, - convert_urls : 1, - relative_urls : 1, - remove_script_host : 1, - table_inline_editing : 0, - object_resizing : 1, - cleanup : 1, - accessibility_focus : 1, - custom_shortcuts : 1, - custom_undo_redo_keyboard_shortcuts : 1, - custom_undo_redo_restore_selection : 1, - custom_undo_redo : 1, - doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll - visual_table_class : 'mceItemTable', - visual : 1, - font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', - apply_source_formatting : 1, - directionality : 'ltr', - forced_root_block : 'p', - valid_elements : '@[id|class|style|title|dir
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } + + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element + + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; + + attrList = []; + attrList.map = {}; + + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; + + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute + + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; + + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } + + // No rule matched + if (i === -1) + attrRule = null; + } + + // No attribute rule found + if (!attrRule) + return; + + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } + + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } + + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; + + if (!(name in attrList.map)) { + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } + + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } + + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } + + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } + + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; + + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; + + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); + + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } + + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } + + index = matches.index + matches[0].length; + } + + // Text + if (index < html.length) + self.text(decode(html.substr(index))); + + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; + + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); + +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; + + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; + + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; + + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; + + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; + + if (sibling) + return sibling; + } + } + }; + + function Node(name, type) { + this.name = name; + this.type = type; + + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } + + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; + + if (node.parent) + node.remove(); + + self.insert(node, self); + self.remove(); + + return self; + }, + + attr : function(name, value) { + var self = this, attrs, i, undef; + + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); + + return self; + } + + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; + + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } + } + + return self; + } + + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; + } + } + } else + attrs.push({name: name, value: value}); + + attrs.map[name] = value; + + return self; + } else { + return attrs.map[name]; + } + } + }, + + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; + + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; + + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; + + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } + + clone.attributes = cloneAttrs; + } + + clone.value = self.value; + clone.shortEnded = self.shortEnded; + + return clone; + }, + + wrap : function(wrapper) { + var self = this; + + self.parent.insert(wrapper, self); + wrapper.append(self); + + return self; + }, + + unwrap : function() { + var self = this, node, next; + + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } + + self.remove(); + }, + + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; + + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; + + if (next) + next.prev = null; + } else { + prev.next = next; + } + + if (parent.lastChild === self) { + parent.lastChild = prev; + + if (prev) + prev.next = null; + } else { + next.prev = prev; + } + + self.parent = self.next = self.prev = null; + } + + return self; + }, + + append : function(node) { + var self = this, last; + + if (node.parent) + node.remove(); + + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; + + node.parent = self; + + return node; + }, + + insert : function(node, ref_node, before) { + var parent; + + if (node.parent) + node.remove(); + + parent = ref_node.parent || this; + + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; + + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; + + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } + + node.parent = parent; + + return node; + }, + + getAll : function(name) { + var self = this, node, collection = []; + + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); + } + + return collection; + }, + + empty : function() { + var self = this, nodes, i, node; + + // Remove all children + if (self.firstChild) { + nodes = []; + + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); + + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } + + self.firstChild = self.lastChild = null; + + return self; + }, + + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; + + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; + + // Keep empty elements like + if (elements[node.name]) + return false; + + // Keep elements with data attributes or name attribute like + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } + } + + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } + + return true; + }, + + walk : function(prev) { + return walk(this, null, prev); + } + }); + + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; + + // Create node + node = new Node(name, typeLookup[name] || 1); + + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } + + return node; + } + }); + + tinymce.html.Node = Node; +})(tinymce); + +(function(tinymce) { + var Node = tinymce.html.Node; + + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); + + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; + + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); + + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; + + // Already removed + if (!node.parent) + continue; + + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); + + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); + + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); + + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; + + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } + + currentNode = tempNode; + } + + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } + + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } + + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } + + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } + + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; + + self.filterNode = function(node) { + var i, name, list; + + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; + + if (name in node.attributes.map) { + list = matchedAttributes[name]; + + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } + + return node; + }; + + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; + + if (!list) + nodeFilters[name] = list = []; + + list.push(callback); + }); + }; + + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } + + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; + + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], + endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } + + node = next; + }; + }; + + function createNode(name, type) { + var node = new Node(name, type), list; + + if (name in nodeFilters) { + list = matchedNodes[name]; + + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } + + return node; + }; + + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; + + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; + + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results + + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, + + text: function(text, raw) { + var textNode; + + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); + + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } + + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, + + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, + + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, + + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, + + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; + + node.append(newNode); + + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); + + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; + + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; + + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } + + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + } + }, + + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; + + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!whiteSpaceElements[node.name]) { + // Trim whitespace at beginning of block + for (textNode = node.firstChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } + + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); + + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } + + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); + + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } + + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like + if (!node.attributes.map.name) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } + + node = node.parent; + } + } + }, schema); + + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; + } + + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); + + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } + + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; + + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } + + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } + + return rootNode; + }; + + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = schema.getBlockElements(), + nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the right of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } + + prev = prev.prev; + } + + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } + } + }); + } + } +})(tinymce); + +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; + + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; + + return { + start: function(name, attrs, empty) { + var i, l, attr, value; + + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } + } + + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; + + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + end: function(name) { + var value; + + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ + + html.push(''); + + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; + + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, + + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, + + cdata: function(text) { + html.push(''); + }, + + comment: function(text) { + html.push(''); + }, + + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); + + if (indent) + html.push('\n'); + }, + + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, + + reset: function() { + html.length = 0; + }, + + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; + +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); + + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; + + self.serialize = function(node) { + var handlers, validate; + + validate = settings.validate; + + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, + + // #comment + 8: function(node) { + writer.comment(node.value); + }, + + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, + + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; + + writer.reset(); + + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; + + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; + + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; + + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; + + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; + + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } + + attrs = sortedAttrs; + } + + writer.start(node.name, attrs, isEmpty); + + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + + writer.end(name); + } + } else + handler(node); + } + + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); + + return writer.getContent(); + }; + } +})(tinymce); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + blockElementsMap = tinymce.html.Schema.blockElementsMap, + whiteSpaceRegExp = /^[ \t\r\n]*$/; + + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, + + DOMUtils : function(d, s) { + var t = this, globalStyle, name; + + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); + + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); + + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } + } + + if (isIE && s.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + d.createElement(name); + }); + + // Create all custom elements + for (name in s.schema.getCustomElements()) { + d.createElement(name); + } + } + + tinymce.addUnload(t.destroy, t); + }, + + getRoot : function() { + var t = this, s = t.settings; + + return (s && t.get(s.root_element)) || t.doc.body; + }, + + getViewPort : function(w) { + var d, b; + + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; + + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, + + getRect : function(e) { + var p, t = this, sr; + + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); + + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, + + getSize : function(e) { + var t = this, w, h; + + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); + + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; + + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; + + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, + + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, + + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; + + n = t.get(n); + c = c === undefined; + + if (se.strict_root) + r = r || t.getRoot(); + + // Wrap node name as func + if (is(f, 'string')) { + na = f; + + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } + + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; + + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } + + n = n.parentNode; + } + + return c ? o : null; + }, + + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; + } + + return e; + }, + + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, + + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, + + + select : function(pa, s) { + var t = this; + + return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); + }, + + is : function(n, selector) { + var i; + + // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance + if (n.length === undefined) { + // Simple all selector + if (selector === '*') + return n.nodeType == 1; + + // Simple selector just elements + if (simpleSelectorRe.test(selector)) { + selector = selector.toLowerCase().split(/,/); + n = n.nodeName.toLowerCase(); + + for (i = selector.length - 1; i >= 0; i--) { + if (selector[i] == n) + return true; + } + + return false; + } + } + + return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + }, + + + add : function(p, n, a, h, c) { + var t = this; + + return this.run(p, function(p) { + var e, k; + + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); + + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } + + return !c ? p.appendChild(e) : e; + }); + }, + + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, + + createHTML : function(n, a, h) { + var o = '', t = this, k; + + o += '<' + n; + + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } + + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; + + return o + ' />'; + }, + + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } + } + + return parent.removeChild(node); + }); + }, + + setStyle : function(n, na, v) { + var t = this; + + return t.run(n, function(e) { + var s, i; + + s = e.style; + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; + + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } + + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; + + case 'float': + isIE ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } + + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, + + getStyle : function(n, na, c) { + n = this.get(n); + + if (!n) + return; + + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } + } + + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); + + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; + + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; + + return n.style ? n.style[na] : undefined; + }, + + setStyles : function(e, o) { + var t = this, s = t.settings, ol; + + ol = s.update_styles; + s.update_styles = 0; + + each(o, function(v, n) { + t.setStyle(e, n, v); + }); + + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, + + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, + + setAttrib : function(e, n, v) { + var t = this; + + // Whats the point + if (!e || !n) + return; + + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); + + return this.run(e, function(e) { + var s = t.settings; + if (v !== null) { + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); + + return; + } + + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } + + e.style.cssText = v; + break; + + case "class": + e.className = v || ''; // Fix IE null bug + break; + + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + + t.setAttrib(e, 'data-mce-' + n, v, 2); + } + + break; + + case "shape": + e.setAttribute('data-mce-style', v); + break; + } + } + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + }); + }, + + setAttribs : function(e, o) { + var t = this; + + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, + + getAttrib : function(e, n, dv) { + var v, t = this, undef; + + e = t.get(e); + + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; + + if (!is(dv)) + dv = ''; + + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); + + if (v) + return v; + } + + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } + + if (!v) + v = e.getAttribute(n, 2); + + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; + + return v ? n : ''; + } + + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; + + if (n === 'style') { + v = v || e.style.cssText; + + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); + + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); + } + } + + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; + + break; + + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; + + break; + + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; + + break; + + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; + + break; + + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; + + break; + + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; + + return dv; + + case 'shape': + v = v.toLowerCase(); + break; + + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); + } + } + + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; + } + + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } + + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } + + return {x : x, y : y}; + }, + + parseStyle : function(st) { + return this.styles.parse(st); + }, + + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, + + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = t.select('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; + } + + head.appendChild(link); + }); + }, + + addClass : function(e, c) { + return this.run(e, function(e) { + var o; + + if (!c) + return 0; + + if (this.hasClass(e, c)) + return e.className; + + o = this.removeClass(e, c); + + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, + + removeClass : function(e, c) { + var t = this, re; + + return t.run(e, function(e) { + var v; + + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); + + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); + } + + return v; + } + + return e.className; + }); + }, + + hasClass : function(n, c) { + n = this.get(n); + + if (!n || !c) + return false; + + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, + + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, + + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, + + isHidden : function(e) { + e = this.get(e); + + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, + + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, + + setHTML : function(element, html) { + var self = this; + + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + element = self.create('div'); + element.innerHTML = '
    ' + html; + + // Add all children from div to target + each (element.childNodes, function(node, i) { + // Skip br element + if (i) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; + + return html; + }); + }, + + getOuterHTML : function(elm) { + var doc, self = this; + + elm = self.get(elm); + + if (!elm) + return null; + + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; + + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); + + return doc.innerHTML; + }, + + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } + + t.remove(e); + }; + + return this.run(e, function(e) { + e = t.get(e); + + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; + + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); + } + }); + }, + + decode : Entities.decode, + + encode : Entities.encodeAllRaw, + + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); + + return this.run(node, function(node) { + var parent, nextSibling; + + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); + + return node; + }); + }, + + isBlock : function(node) { + var type = node.nodeType; + + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); + + return !!blockElementsMap[node]; + }, + + replace : function(n, o, k) { + var t = this; + + if (is(o, 'array')) + n = n.cloneNode(true); + + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } + + return o.parentNode.replaceChild(n, o); + }); + }, + + rename : function(elm, name) { + var t = this, newElm; + + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); + + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); + + // Replace block + t.replace(newElm, elm, 1); + } + + return newElm || elm; + }, + + findCommonAncestor : function(a, b) { + var ps = a, pe; + + while (ps) { + pe = b; + + while (pe && ps != pe) + pe = pe.parentNode; + + if (ps == pe) + break; + + ps = ps.parentNode; + } + + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; + + return ps; + }, + + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + + function hex(s) { + s = parseInt(s).toString(16); + + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; + + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + + return s; + } + + return s; + }, + + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + + if (t.classes) + return t.classes; + + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); + + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); + + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; + + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + + // Filter classes + if (f && !(v = f(v, ov))) + return; + + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; + + // Import + case 3: + addClasses(r.styleSheet); + break; + } + }); + }; + + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } + + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); + } + }); + + return o; + } + + return f.call(s, e); + }, + + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; + } + + return n.attributes; + }, + + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, parentNode; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    + parentNode = node.parentNode; + if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) { + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } + } + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); + } + + return true; + }, + + destroy : function(s) { + var t = this; + + if (t.events) + t.events.destroy(); + + t.win = t.doc = t.root = t.events = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); + }, + + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; + + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; + } + idx++; + lastNodeType = nodeType; + } + } + + return idx; + }, + + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; + } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; + } + }, + + bind : function(target, name, func, scope) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.add(target, name, func, scope || this); + }, + + unbind : function(target, name, func) { + var t = this; + + if (!t.events) + t.events = new tinymce.dom.EventUtils(); + + return t.events.remove(target, name, func); + }, + + + _findSib : function(node, selector, name) { + var t = this, f = selector; + + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _isRes : function(c) { + // Is live resizble element + return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); + } + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); + } + */ + + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; + + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } + + return s; + } + */ + }); + + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); + +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; + + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, + + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, + + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange + }); + + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; + + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; + + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; + + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; + + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; + + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; + } + + t.collapsed = TRUE; + }; + + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; + + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; + + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; + + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; + + function deleteContents() { + _traverse(DELETE); + }; + + function extractContents() { + return _traverse(EXTRACT); + }; + + function cloneContents() { + return _traverse(CLONE); + }; + + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; + + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; + + function surroundContents(n) { + var f = t.extractContents(); + + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; + + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; + + // Private methods + + function _getSelectedNode(container, offset) { + var child; + + if (container.nodeType == 3 /* TEXT_NODE */) + return container; + + if (offset < 0) + return container; + + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } + + if (child) + return child; + + return container; + }; + + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; + + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal + + if (offsetA < offsetB) + return -1; // before + + return 1; // after + } + + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; + + if (c) { + offsetC = 0; + n = containerA.firstChild; + + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + + if (offsetA <= offsetC) + return -1; // before + + return 1; // after + } + + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } + + if (c) { + offsetC = 0; + n = containerB.firstChild; + + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } + + if (offsetC < offsetB) + return -1; // before + + return 1; // after + } + + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; + + if (!childA) + childA = cmnRoot; + + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; + + if (!childB) + childB = cmnRoot; + + if (childA == childB) + return 0; // equal + + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before + + if (n == childB) + return 1; // after + + n = n.nextSibling; + } + }; + + function _setEndPoint(st, n, o) { + var ec, sc; + + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; + } + + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; + + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; + + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); + + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; + + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); + + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); + + ++endContainerDepth; + } + + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); + + ++startContainerDepth; + } + + depthDiff = startContainerDepth - endContainerDepth; + + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } + + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } + + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } + + return _traverseCommonAncestors(startNode, endNode, how); + }; + + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; + + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } + + if (how == DELETE) + return; + + frag.appendChild(doc.createTextNode(sub)); + return frag; + } + + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; + + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + } + + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; + } + + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } + + return frag; + }; + + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; + } + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = n.cloneNode(FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return n.cloneNode(FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? n.cloneNode(TRUE) : n; + + n.parentNode.removeChild(n); + }; + }; + + ns.Range = Range; +})(tinymce.dom); + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng); + + // Fix for edge case:
    ..
    ab|c
    + if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) { + checkRng = rng.duplicate(); + checkRng.collapse(start); + + offset = -1; + while (parent == checkRng.parentElement()) { + if (checkRng.move('character', -1) == 0) + break; + + offset++; + } + } + + offset = offset || checkRng.text.replace('\r\n', ' ').length; + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng); + + // Get the length of the text to find where the endpoint is relative to it's container + offset = checkRng.text.replace('\r\n', ' ').length; + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var node, parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root && parent.nodeType !== 9) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else { + // Empty node selection for example
    |
    + marker = doc.createTextNode('\uFEFF'); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { + if (startOffset == endOffset - 1) { + try { + ctrlRng = body.createControlRange(); + ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.select(); + return; + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); + + +/* + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), + soFar = selector, ret, cur, pop, i; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return (/h\d/i).test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return (/input|select|textarea|button/i).test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } + + return ret; +}; + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
    "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +Sizzle.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +window.tinymce.dom.Sizzle = Sizzle; + +})(); + + +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + + tinymce.create('tinymce.dom.EventUtils', { + EventUtils : function() { + this.inits = []; + this.events = []; + }, + + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; + + if (n instanceof Array) { + r = []; + + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); + + return r; + } + + o = DOM.get(o); + + if (!o) + return; + + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; + + e = e || window.event; + + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; + + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } + + if (!s) + return f(e); + + return f.call(s, e); + }; + + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; + } + + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); + + return cb; + } + + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); + + t._add(o, n, cb); + + return f; + }, + + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; + + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); + + return r; + } + + o = DOM.get(o); + + each(a, function(e, i) { + if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { + a.splice(i, 1); + t._remove(o, n, e.cfunc); + s = true; + return false; + } + }); + + return s; + }, + + clear : function(o) { + var t = this, a = t.events, i, e; + + if (o) { + o = DOM.get(o); + + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; + + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } + }, + + cancel : function(e) { + if (!e) + return false; + + this.stop(e); + + return this.prevent(e); + }, + + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; + + return false; + }, + + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + + return false; + }, + + destroy : function() { + var t = this; + + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); + + t.events = []; + t = null; + }, + + _add : function(o, n, f) { + if (o.attachEvent) + o.attachEvent('on' + n, f); + else if (o.addEventListener) + o.addEventListener(n, f, false); + else + o['on' + n] = f; + }, + + _remove : function(o, n, f) { + if (o) { + try { + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; + } catch (ex) { + // Might fail with permission denined on IE so we just ignore that + } + } + }, + + _pageInit : function(win) { + var t = this; + + // Keep it from running more than once + if (t.domLoaded) + return; + + t.domLoaded = true; + + each(t.inits, function(c) { + c(); + }); + + t.inits = []; + }, + + _wait : function(win) { + var t = this, doc = win.document; + + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } + + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); + + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; + + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; + } + + t._pageInit(win); + })(); + } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } + + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, + + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, + + stopPropagation : function() { + this.cancelBubble = true; + } + } + }); + + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); + + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; + + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; + + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); + + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/) + , function(k) { + t[k] = function() { + var a = [id], i; + + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); + + a = dom[k].apply(dom, a); + t.update(k); + + return a; + }; + }); + + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, + + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, + + getSize : function() { + var n = dom.get(t.id); + + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, + + moveBy : function(x, y) { + var p = t.getXY(); + + t.moveTo(p.x + x, p.y + y); + }, + + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, + + resizeBy : function(w, h) { + var s = t.getSize(); + + t.resizeTo(s.w + w, s.h + h); + }, + + update : function(k) { + var b; + + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; + + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; + + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } + + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); + + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); + } + } + }); + }; +})(tinymce); + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + + // Add events + each([ + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); + + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); + + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); + + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, + + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); + }, + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); + + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + + if (r.cloneContents) { + n = r.cloneContents(); + + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); + + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; + + if (/\s+$/.test(e.innerHTML)) + wa = ' '; + + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; + }, + + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; + + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; + + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; + + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); + + if (doc.body.childNodes.length == 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); + + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } + } + + // Move to caret marker + caretNode = self.dom.get('__caret'); + + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); + + // Remove the caret position + self.dom.remove('__caret'); + + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } + + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); + } else + rng.pasteHTML(content); + } + + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, + + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; + + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); + + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); + + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; + } + } + + return startElement; + } else { + startElement = rng.startContainer; + + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; + + return startElement; + } + }, + + getEnd : function() { + var t = this, r = t.getRng(), e, eo; + + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); + + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); + + if (e && e.nodeName == 'BODY') + return e.lastChild || e; + + return e; + } else { + e = r.endContainer; + eo = r.endOffset; + + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; + + if (e && e.nodeType == 3) + return e.parentNode; + + return e; + } + }, + + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + + function findIndex(name, element) { + var index = 0; + + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); + + return index; + }; + + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } + + point.push(offset); + } else { + childNodes = container.childNodes; + + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } + + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } + + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); + + return point; + }; + + bookmark.start = getPoint(rng, true); + + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); + + return bookmark; + }; + + if (t.tridentSel) + return t.tridentSel.getBookmark(type); + + return getLocation(); + } + + // Handle simple range + if (type) + return {rng : t.getRng()}; + + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; + + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); + + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) == 0) + rng2.move('character', -1); + + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; + + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; + + // W3C method + rng2 = rng.cloneRange(); + + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } + + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); + + return {id : id}; + }, + + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; + + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); + + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + + if (point) { + offset = point[0]; + + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; + + if (point[i] > children.length - 1) + return; + + node = children[point[i]]; + } + + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); + + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); + + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } + + return true; + }; + + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); + + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } + + endContainer = node; + endOffset = idx; + } + + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; + + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); + + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); + + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; + + function addBogus(node) { + // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly + if (dom.isBlock(node) && !node.innerHTML) + node.innerHTML = !isIE ? '
    ' : ' '; + + return node; + }; + + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); + + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); + } + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) + t.setRng(bookmark.rng); + } + }, + + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; + + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); + + return; + } + + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); + + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; + + setPoint(node, 1); + setPoint(node); + } + + t.setRng(rng); + } + + return node; + }, + + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); + + if (!r || r.item) + return false; + + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; + + return !s || r.collapsed; + }, + + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; + + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } + + rng.collapse(!!to_start); + self.setRng(rng); + }, + + getSel : function() { + var t = this, w = this.win; + + return w.getSelection ? w.getSelection() : w.document.selection; + }, + + getRng : function(w3c) { + var t = this, s, r, elm, doc = t.win.document; + + // Found tridentSel object then we need to use that one + if (w3c && t.tridentSel) + return t.tridentSel.getRangeAt(0); + + try { + if (s = t.getSel()) + r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } + + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + r = doc.createRange(); + r.setStartBefore(elm); + r.setEndAfter(elm); + } + + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!r) + r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; + } + } + + return r; + }, + + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); + + if (s) { + t.explicitRange = r; + + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } + + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } + + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } + }, + + setNode : function(n) { + var t = this; + + t.setContent(t.dom.getOuterHTML(n)); + + return n; + }, + + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; + + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); + + if (rng.setStart) { + elm = rng.commonAncestorContainer; + + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } + + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + } + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } + + if (start && start === end) + return start; + } + } + + if (elm && elm.nodeType == 3) + return elm.parentNode; + + return elm; + } + + return rng.item ? rng.item(0) : rng.parentElement(); + }, + + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; + + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); + + if (sb) + bl.push(sb); + + if (sb && eb && sb != eb) { + n = sb; + + var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot()); + while ((n = walker.next()) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } + + if (eb && sb != eb) + bl.push(eb); + + return bl; + }, + + normalize : function() { + var self = this, rng, normalized; + + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; + + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node; + + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; + + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = container.body; + offset = 0; + } + + // If the container is body try move it into the closest text node or position + // TODO: Add more logic here to handle element selection cases + if (container === body) { + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes()) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new tinymce.dom.TreeWalker(container, body); + do { + // Found a text node use that position + if (node.nodeType === 3) { + offset = start ? 0 : node.nodeValue.length - 1; + container = node; + break; + } + + // Found a BR element that we can place the caret before + if (node.nodeName === 'BR') { + offset = dom.nodeIndex(node); + container = node.parentNode; + break; + } + } while (node = (start ? walker.next() : walker.prev())); + + normalized = true; + } + } + } + + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; + + rng = self.getRng(); + + // Normalize the end points + normalizeEndPoint(true); + + if (rng.collapsed) + normalizeEndPoint(); + + // Set the selection if it was normalized + if (normalized) { + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng); + } + }, + + destroy : function(s) { + var t = this; + + t.win = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } + + return rng; + }; + + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; + + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); + + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); + + pointRng.select(); + } + } else + endSelection(); + } + + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); + + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); + + dom.unbind(doc, 'mouseup', endSelection); + dom.unbind(doc, 'mousemove', selectionChange); + startRng = started = 0; + }; + + // Detect when user selects outside BODY + dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { + if (e.target.nodeName === 'HTML') { + if (started) + endSelection(); + + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; + + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); + + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + tinymce.dom.Serializer = function(settings, dom, schema) { + var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; + + // Support the old apply_source_formatting option + if (!settings.apply_source_formatting) + settings.indent = false; + + settings.remove_trailing_brs = true; + + // Default DOM and Schema if they are undefined + dom = dom || tinymce.DOM; + schema = schema || new tinymce.html.Schema(settings); + settings.entity_encoding = settings.entity_encoding || 'named'; + + onPreProcess = new tinymce.util.Dispatcher(self); + + onPostProcess = new tinymce.util.Dispatcher(self); + + htmlParser = new tinymce.html.DomParser(settings, schema); + + // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed + htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; + + while (i--) { + node = nodes[i]; + + value = node.attributes.map[internalName]; + if (value !== undef) { + // Set external name to internal value and remove internal + node.attr(name, value.length > 0 ? value : null); + node.attr(internalName, null); + } else { + // No internal attribute found then convert the value we have in the DOM + value = node.attributes.map[name]; + + if (name === "style") + value = dom.serializeStyle(dom.parseStyle(value), node.name); + else if (urlConverter) + value = urlConverter.call(urlConverterScope, value, name, node.name); + + node.attr(name, value.length > 0 ? value : null); + } + } + }); + + // Remove internal classes mceItem<..> + htmlParser.addAttributeFilter('class', function(nodes, name) { + var i = nodes.length, node, value; + + while (i--) { + node = nodes[i]; + value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, ''); + node.attr('class', value.length > 0 ? value : null); + } + }); + + // Remove bookmark elements + htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) + node.remove(); + } + }); + + // Force script into CDATA sections and remove the mce- prefix also add comments around styles + htmlParser.addNodeFilter('script,style', function(nodes, name) { + var i = nodes.length, node, value; + + function trim(value) { + return value.replace(/()/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); + }; + + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; + + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); + + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); + + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); + + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); + + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; + + while (i--) { + node = nodes[i]; + parentNode = node.parent; + + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } + } + }); + } + + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); + } + }); + + // Return public methods + return { + schema : schema, + + addNodeFilter : htmlParser.addNodeFilter, + + addAttributeFilter : htmlParser.addAttributeFilter, + + onPreProcess : onPreProcess, + + onPostProcess : onPostProcess, + + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; + + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); + + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; + + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } + + args = args || {}; + args.format = args.format || 'html'; + + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } + + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); + + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args) + ); + + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF|\u200B/g, ''); + + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); + + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; + + args.node = null; + + return args.content; + }, + + addRules : function(rules) { + schema.addValidElements(rules); + }, + + setRules : function(rules) { + schema.setValidElements(rules); + } + }; + }; +})(tinymce); +(function(tinymce) { + tinymce.dom.ScriptLoader = function(settings) { + var QUEUED = 0, + LOADING = 1, + LOADED = 2, + states = {}, + queue = [], + scriptLoadedCallbacks = {}, + queueLoadedCallbacks = [], + loading = 0, + undefined; + + function loadScript(url, callback) { + var t = this, dom = tinymce.DOM, elm, uri, loc, id; + + // Execute callback when script is loaded + function done() { + dom.remove(id); + + if (elm) + elm.onreadystatechange = elm.onload = elm = null; + + callback(); + }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; + + id = dom.uniqueId(); + + if (tinymce.isIE6) { + uri = new tinymce.util.URI(url); + loc = location; + + // If script is from same domain and we + // use IE 6 then use XHR since it's more reliable + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { + tinymce.util.XHR.send({ + url : tinymce._addVer(uri.getURI()), + success : function(content) { + // Create new temp script element + var script = dom.create('script', { + type : 'text/javascript' + }); + + // Evaluate script in global scope + script.text = content; + document.getElementsByTagName('head')[0].appendChild(script); + dom.remove(script); + + done(); + }, + + error : error + }); + + return; + } + } + + // Create new script element + elm = dom.create('script', { + id : id, + type : 'text/javascript', + src : tinymce._addVer(url) + }); + + // Add onload listener for non IE browsers since IE9 + // fires onload event before the script is parsed and executed + if (!tinymce.isIE) + elm.onload = done; + + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; + + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } + + // Most browsers support this feature so we report errors + // for those at least to help users track their missing plugins etc + // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option + /*elm.onerror = function() { + alert('Failed to load: ' + url); + };*/ + + // Add script to document + (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); + }; + + this.isDone = function(url) { + return states[url] == LOADED; + }; + + this.markDone = function(url) { + states[url] = LOADED; + }; + + this.add = this.load = function(url, callback, scope) { + var item, state = states[url]; + + // Add url to load queue + if (state == undefined) { + queue.push(url); + states[url] = QUEUED; + } + + if (callback) { + // Store away callback for later execution + if (!scriptLoadedCallbacks[url]) + scriptLoadedCallbacks[url] = []; + + scriptLoadedCallbacks[url].push({ + func : callback, + scope : scope || this + }); + } + }; + + this.loadQueue = function(callback, scope) { + this.loadScripts(queue, callback, scope); + }; + + this.loadScripts = function(scripts, callback, scope) { + var loadScripts; + + function execScriptLoadedCallbacks(url) { + // Execute URL callback functions + tinymce.each(scriptLoadedCallbacks[url], function(callback) { + callback.func.call(callback.scope); + }); + + scriptLoadedCallbacks[url] = undefined; + }; + + queueLoadedCallbacks.push({ + func : callback, + scope : scope || this + }); + + loadScripts = function() { + var loadingScripts = tinymce.grep(scripts); + + // Current scripts has been handled + scripts.length = 0; + + // Load scripts that needs to be loaded + tinymce.each(loadingScripts, function(url) { + // Script is already loaded then execute script callbacks directly + if (states[url] == LOADED) { + execScriptLoadedCallbacks(url); + return; + } + + // Is script not loading then start loading it + if (states[url] != LOADING) { + states[url] = LOADING; + loading++; + + loadScript(url, function() { + states[url] = LOADED; + loading--; + + execScriptLoadedCallbacks(url); + + // Load more scripts if they where added by the recently loaded script + loadScripts(); + }); + } + }); + + // No scripts are currently loading then execute all pending queue loaded callbacks + if (!loading) { + tinymce.each(queueLoadedCallbacks, function(callback) { + callback.func.call(callback.scope); + }); + + queueLoadedCallbacks.length = 0; + } + }; + + loadScripts(); + }; + }; + + // Global script loader + tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); +})(tinymce); + +tinymce.dom.TreeWalker = function(start_node, root_node) { + var node = start_node; + + function findSibling(node, start_name, sibling_name, shallow) { + var sibling, parent; + + if (node) { + // Walk into nodes if it has a start + if (!shallow && node[start_name]) + return node[start_name]; + + // Return the sibling if it has one + if (node != root_node) { + sibling = node[sibling_name]; + if (sibling) + return sibling; + + // Walk up the parents to look for siblings + for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { + sibling = parent[sibling_name]; + if (sibling) + return sibling; + } + } + } + }; + + this.current = function() { + return node; + }; + + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; + + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); + }; +}; + +(function(tinymce) { + tinymce.dom.RangeUtils = function(dom) { + var INVISIBLE_CHAR = '\uFEFF'; + + this.walk = function(rng, callback) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, + ancestor, startPoint, + endPoint, node, parent, siblings, nodes; + + // Handle table cell selection the table plugin enables + // you to fake select table cells and perform formatting actions on them + nodes = dom.select('td.mceSelected,th.mceSelected'); + if (nodes.length > 0) { + tinymce.each(nodes, function(node) { + callback([node]); + }); + + return; + } + + function exclude(nodes) { + var node; + + // First node is excluded + node = nodes[0]; + if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { + nodes.splice(0, 1); + } + + // Last node is excluded + node = nodes[nodes.length - 1]; + if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { + nodes.splice(nodes.length - 1, 1); + } + + return nodes; + }; + + function collectSiblings(node, name, end_node) { + var siblings = []; + + for (; node && node != end_node; node = node[name]) + siblings.push(node); + + return siblings; + }; + + function findEndPoint(node, root) { + do { + if (node.parentNode == root) + return node; + + node = node.parentNode; + } while(node); + }; + + function walkBoundary(start_node, end_node, next) { + var siblingName = next ? 'nextSibling' : 'previousSibling'; + + for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { + parent = node.parentNode; + siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); + + if (siblings.length) { + if (!next) + siblings.reverse(); + + callback(exclude(siblings)); + } + } + }; + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) + startContainer = startContainer.childNodes[startOffset]; + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; + + // Same container + if (startContainer == endContainer) + return callback(exclude([startContainer])); + + // Find common ancestor and end points + ancestor = dom.findCommonAncestor(startContainer, endContainer); + + // Process left side + for (node = startContainer; node; node = node.parentNode) { + if (node === endContainer) + return walkBoundary(startContainer, ancestor, true); + + if (node === ancestor) + break; + } + + // Process right side + for (node = endContainer; node; node = node.parentNode) { + if (node === startContainer) + return walkBoundary(endContainer, ancestor); + + if (node === ancestor) + break; + } + + // Find start/end point + startPoint = findEndPoint(startContainer, ancestor) || startContainer; + endPoint = findEndPoint(endContainer, ancestor) || endContainer; + + // Walk left leaf + walkBoundary(startContainer, startPoint, true); + + // Walk the middle from start to end point + siblings = collectSiblings( + startPoint == startContainer ? startPoint : startPoint.nextSibling, + 'nextSibling', + endPoint == endContainer ? endPoint.nextSibling : endPoint + ); + + if (siblings.length) + callback(exclude(siblings)); + + // Walk right leaf + walkBoundary(endContainer, endPoint); + }; + + this.split = function(rng) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset; + + function splitText(node, offset) { + return node.splitText(offset); + }; + + // Handle single text node + if (startContainer == endContainer && startContainer.nodeType == 3) { + if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { + endContainer = splitText(startContainer, startOffset); + startContainer = endContainer.previousSibling; + + if (endOffset > startOffset) { + endOffset = endOffset - startOffset; + startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + startOffset = 0; + } else { + endOffset = 0; + } + } + } else { + // Split startContainer text node if needed + if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { + startContainer = splitText(startContainer, startOffset); + startOffset = 0; + } + + // Split endContainer text node if needed + if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { + endContainer = splitText(endContainer, endOffset).previousSibling; + endOffset = endContainer.nodeValue.length; + } + } + + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + }; + + }; + + tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { + if (rng1 && rng2) { + // Compare native IE ranges + if (rng1.item || rng1.duplicate) { + // Both are control ranges and the selected element matches + if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) + return true; + + // Both are text ranges and the range matches + if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) + return true; + } else { + // Compare w3c ranges + return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; + } + } + + return false; + }; +})(tinymce); + +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.KeyboardNavigation', { + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + t.destroy = function() { + each(items, function(item) { + dom.unbind(dom.get(item.id), 'focus', itemFocussed); + dom.unbind(dom.get(item.id), 'blur', itemBlurred); + }); + + dom.unbind(dom.get(root), 'focus', rootFocussed); + dom.unbind(dom.get(root), 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + if (excludeFromTabOrder) { + dom.bind(item.id, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + dom.setAttrib(item.id, 'tabindex', tabindex); + dom.bind(dom.get(item.id), 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + dom.bind(dom.get(root), 'focus', rootFocussed); + dom.bind(dom.get(root), 'keydown', rootKeydown); + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten class names + var DOM = tinymce.DOM, is = tinymce.is; + + tinymce.create('tinymce.ui.Control', { + Control : function(id, s, editor) { + this.id = id; + this.settings = s = s || {}; + this.rendered = false; + this.onRender = new tinymce.util.Dispatcher(this); + this.classPrefix = ''; + this.scope = s.scope || this; + this.disabled = 0; + this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); + }, + + setDisabled : function(s) { + if (s != this.disabled) { + this.setAriaProperty('disabled', s); + + this.setState('Disabled', s); + this.setState('Enabled', !s); + this.disabled = s; + } + }, + + isDisabled : function() { + return this.disabled; + }, + + setActive : function(s) { + if (s != this.active) { + this.setState('Active', s); + this.active = s; + this.setAriaProperty('pressed', s); + } + }, + + isActive : function() { + return this.active; + }, + + setState : function(c, s) { + var n = DOM.get(this.id); + + c = this.classPrefix + c; + + if (s) + DOM.addClass(n, c); + else + DOM.removeClass(n, c); + }, + + isRendered : function() { + return this.rendered; + }, + + renderHTML : function() { + }, + + renderTo : function(n) { + DOM.setHTML(n, this.renderHTML()); + }, + + postRender : function() { + var t = this, b; + + // Set pending states + if (is(t.disabled)) { + b = t.disabled; + t.disabled = -1; + t.setDisabled(b); + } + + if (is(t.active)) { + b = t.active; + t.active = -1; + t.setActive(b); + } + }, + + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + + destroy : function() { + tinymce.dom.Event.clear(this.id); + } + }); +})(tinymce); +tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { + Container : function(id, s, editor) { + this.parent(id, s, editor); + + this.controls = []; + + this.lookup = {}; + }, + + add : function(c) { + this.lookup[c.id] = c; + this.controls.push(c); + + return c; + }, + + get : function(n) { + return this.lookup[n]; + } +}); + + +tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + this.setDisabled(true); + }, + + renderHTML : function() { + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); + } +}); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { + MenuItem : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceMenuItem'; + }, + + setSelected : function(s) { + this.setState('Selected', s); + this.setAriaProperty('checked', !!s); + this.selected = s; + }, + + isSelected : function() { + return this.selected; + }, + + postRender : function() { + var t = this; + + t.parent(); + + // Set pending state + if (is(t.selected)) + t.setSelected(t.selected); + } + }); +})(tinymce); + +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; + + tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { + Menu : function(id, s) { + var t = this; + + t.parent(id, s); + t.items = {}; + t.collapsed = false; + t.menuCount = 0; + t.onAddItem = new tinymce.util.Dispatcher(this); + }, + + expand : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.expand) + o.expand(); + }, 'items', t); + } + + t.collapsed = false; + }, + + collapse : function(d) { + var t = this; + + if (d) { + walk(t, function(o) { + if (o.collapse) + o.collapse(); + }, 'items', t); + } + + t.collapsed = true; + }, + + isCollapsed : function() { + return this.collapsed; + }, + + add : function(o) { + if (!o.settings) + o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); + + this.onAddItem.dispatch(this, o); + + return this.items[o.id] = o; + }, + + addSeparator : function() { + return this.add({separator : true}); + }, + + addMenu : function(o) { + if (!o.collapse) + o = this.createMenu(o); + + this.menuCount++; + + return this.add(o); + }, + + hasMenus : function() { + return this.menuCount !== 0; + }, + + remove : function(o) { + delete this.items[o.id]; + }, + + removeAll : function() { + var t = this; + + walk(t, function(o) { + if (o.removeAll) + o.removeAll(); + else + o.remove(); + + o.destroy(); + }, 'items', t); + + t.items = {}; + }, + + createMenu : function(o) { + var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); + + m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); + + return m; + } + }); +})(tinymce); +(function(tinymce) { + var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; + + tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { + DropMenu : function(id, s) { + s = s || {}; + s.container = s.container || DOM.doc.body; + s.offset_x = s.offset_x || 0; + s.offset_y = s.offset_y || 0; + s.vp_offset_x = s.vp_offset_x || 0; + s.vp_offset_y = s.vp_offset_y || 0; + + if (is(s.icons) && !s.icons) + s['class'] += ' mceNoIcons'; + + this.parent(id, s); + this.onShowMenu = new tinymce.util.Dispatcher(this); + this.onHideMenu = new tinymce.util.Dispatcher(this); + this.classPrefix = 'mceMenu'; + }, + + createMenu : function(s) { + var t = this, cs = t.settings, m; + + s.container = s.container || cs.container; + s.parent = t; + s.constrain = s.constrain || cs.constrain; + s['class'] = s['class'] || cs['class']; + s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; + s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; + m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); + + m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); + + return m; + }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, + + update : function() { + var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; + + tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth; + th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight; + + if (!DOM.boxModel) + t.element.setStyles({width : tw + 2, height : th + 2}); + else + t.element.setStyles({width : tw, height : th}); + + if (s.max_width) + DOM.setStyle(co, 'width', tw); + + if (s.max_height) { + DOM.setStyle(co, 'height', th); + + if (tb.clientHeight < s.max_height) + DOM.setStyle(co, 'overflow', 'hidden'); + } + }, + + showMenu : function(x, y, px) { + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; + + t.collapse(1); + + if (t.isMenuVisible) + return; + + if (!t.rendered) { + co = DOM.add(t.settings.container, t.renderNode()); + + each(t.items, function(o) { + o.postRender(); + }); + + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + } else + co = DOM.get('menu_' + t.id); + + // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug + if (!tinymce.isOpera) + DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); + + DOM.show(co); + t.update(); + + x += s.offset_x || 0; + y += s.offset_y || 0; + vp.w -= 4; + vp.h -= 4; + + // Move inside viewport if not submenu + if (s.constrain) { + w = co.clientWidth - ot; + h = co.clientHeight - ot; + mx = vp.x + vp.w; + my = vp.y + vp.h; + + if ((x + s.vp_offset_x + w) > mx) + x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); + + if ((y + s.vp_offset_y + h) > my) + y = Math.max(0, (my - s.vp_offset_y) - h); + } + + DOM.setStyles(co, {left : x , top : y}); + t.element.update(); + + t.isMenuVisible = 1; + t.mouseClickFunc = Event.add(co, 'click', function(e) { + var m; + + e = e.target; + + if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { + m = t.items[e.id]; + + if (m.isDisabled()) + return; + + dm = t; + + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + + if (m.settings.onclick) + m.settings.onclick(e); + + return Event.cancel(e); // Cancel to fix onbeforeunload problem + } + }); + + if (t.hasMenus()) { + t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { + var m, r, mi; + + e = e.target; + if (e && (e = DOM.getParent(e, 'tr'))) { + m = t.items[e.id]; + + if (t.lastMenu) + t.lastMenu.collapse(1); + + if (m.isDisabled()) + return; + + if (e && DOM.hasClass(e, cp + 'ItemSub')) { + //p = DOM.getPos(s.container); + r = DOM.getRect(e); + m.showMenu((r.x + r.w - ot), r.y - ot, r.x); + t.lastMenu = m; + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); + } + } + }); + } + + Event.add(co, 'keydown', t._keyHandler, t); + + t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + t._setupKeyboardNav(); + } + }, + + hideMenu : function(c) { + var t = this, co = DOM.get('menu_' + t.id), e; + + if (!t.isMenuVisible) + return; + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + DOM.hide(co); + t.isMenuVisible = 0; + + if (!c) + t.collapse(1); + + if (t.element) + t.element.hide(); + + if (e = DOM.get(t.id)) + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); + + t.onHideMenu.dispatch(t); + }, + + add : function(o) { + var t = this, co; + + o = t.parent(o); + + if (t.isRendered && (co = DOM.get('menu_' + t.id))) + t._add(DOM.select('tbody', co)[0], o); + + return o; + }, + + collapse : function(d) { + this.parent(d); + this.hideMenu(1); + }, + + remove : function(o) { + DOM.remove(o.id); + this.destroy(); + + return this.parent(o); + }, + + destroy : function() { + var t = this, co = DOM.get('menu_' + t.id); + + if (t.keyboardNav) t.keyboardNav.destroy(); + Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); + Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); + + if (t.element) + t.element.remove(); + + DOM.remove(co); + }, + + renderNode : function() { + var t = this, s = t.settings, n, tb, co, w; + + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); + + if (s.menu_line) + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); + +// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); + tb = DOM.add(n, 'tbody'); + + each(t.items, function(o) { + t._add(tb, o); + }); + + t.rendered = true; + + return w; + }, + + // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.select('#menu_' + t.id)[0]; + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, + + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; + } + }, + + _add : function(tb, o) { + var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; + + if (s.separator) { + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); + + if (n = ro.previousSibling) + DOM.addClass(n, 'mceLast'); + + return; + } + + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } + + DOM.addClass(it, s['class']); +// n = DOM.add(n, 'span', {'class' : 'item'}); + + ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); + + if (s.icon_src) + DOM.add(ic, 'img', {src : s.icon_src}); + + n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); + + if (o.settings.style) + DOM.setAttrib(n, 'style', o.settings.style); + + if (tb.childNodes.length == 1) + DOM.addClass(ro, 'mceFirst'); + + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) + DOM.addClass(ro, 'mceFirst'); + + if (o.collapse) + DOM.addClass(ro, cp + 'ItemSub'); + + if (n = ro.previousSibling) + DOM.removeClass(n, 'mceLast'); + + DOM.addClass(ro, 'mceLast'); + } + }); +})(tinymce); +(function(tinymce) { + var DOM = tinymce.DOM; + + tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { + Button : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceButton'; + }, + + renderHTML : function() { + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + l; + else + h += '' + (l ? '' + l + '' : ''); + + h += ''; + h += ''; + return h; + }, + + postRender : function() { + var t = this, s = t.settings; + + tinymce.dom.Event.add(t.id, 'click', function(e) { + if (!t.isDisabled()) + return s.onclick.call(s.scope, e); + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { + ListBox : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.items = []; + + t.onChange = new Dispatcher(t); + + t.onPostRender = new Dispatcher(t); + + t.onAdd = new Dispatcher(t); + + t.onRenderMenu = new tinymce.util.Dispatcher(this); + + t.classPrefix = 'mceListBox'; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + var t = this, e, o, label; + + if (idx != t.selectedIndex) { + e = DOM.get(t.id + '_text'); + label = DOM.get(t.id + '_voiceDesc'); + o = t.items[idx]; + + if (o) { + t.selectedValue = o.value; + t.selectedIndex = idx; + DOM.setHTML(e, DOM.encode(o.title)); + DOM.setHTML(label, t.settings.title + " - " + o.title); + DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); + } else { + DOM.setHTML(e, DOM.encode(t.settings.title)); + DOM.setHTML(label, DOM.encode(t.settings.title)); + DOM.addClass(e, 'mceTitle'); + t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); + } + e = 0; + } + }, + + add : function(n, v, o) { + var t = this; + + o = o || {}; + o = tinymce.extend(o, { + title : n, + value : v + }); + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = ''; + h += ''; + h += ''; + h += ''; + + return h; + }, + + showMenu : function() { + var t = this, p2, e = DOM.get(this.id), m; + + if (t.isDisabled() || t.items.length == 0) + return; + + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus + + // Select in menu + if (t.oldID) + m.items[t.oldID].setSelected(0); + + each(t.items, function(o) { + if (o.value === t.selectedValue) { + m.items[o.id].setSelected(1); + t.oldID = o.id; + } + }); + + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); + + //DOM.get(t.id + '_text').focus(); + }, + + hideMenu : function(e) { + var t = this; + + if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + t.menu.hideMenu(); + } + } + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : t.classPrefix + 'Menu mceNoIcons', + max_width : 150, + max_height : 150 + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + m.add({ + title : t.settings.title, + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + + each(t.items, function(o) { + // No value then treat it as a title + if (o.value === undefined) { + m.add({ + title : o.title, + role : "option", + 'class' : 'mceMenuItemTitle', + onclick : function() { + if (t.settings.onselect('') !== false) + t.select(''); // Must be runned after + } + }); + } else { + o.id = DOM.uniqueId(); + o.role= "option"; + o.onclick = function() { + if (t.settings.onselect(o.value) !== false) + t.select(o.value); // Must be runned after + }; + + m.add(o); + } + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + postRender : function() { + var t = this, cp = t.classPrefix; + + Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { + if (!t._focused) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { + // Fake select on enter + v = t.selectedValue; + t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); + t.settings.onselect(v); + } + }); + } + + t._focused = 1; + }); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); + }); + } + + t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); + Event.clear(this.id + '_open'); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; + + tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { + NativeListBox : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceNativeListBox'; + }, + + setDisabled : function(s) { + DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); + }, + + isDisabled : function() { + return DOM.get(this.id).disabled; + }, + + select : function(va) { + var t = this, fv, f; + + if (va == undefined) + return t.selectByIndex(-1); + + // Is string or number make function selector + if (va && va.call) + f = va; + else { + f = function(v) { + return v == va; + }; + } + + // Do we need to do something? + if (va != t.selectedValue) { + // Find item + each(t.items, function(o, i) { + if (f(o.value)) { + fv = 1; + t.selectByIndex(i); + return false; + } + }); + + if (!fv) + t.selectByIndex(-1); + } + }, + + selectByIndex : function(idx) { + DOM.get(this.id).selectedIndex = idx + 1; + this.selectedValue = this.items[idx] ? this.items[idx].value : null; + }, + + add : function(n, v, a) { + var o, t = this; + + a = a || {}; + a.value = v; + + if (t.isRendered()) + DOM.add(DOM.get(this.id), 'option', a, n); + + o = { + title : n, + value : v, + attribs : a + }; + + t.items.push(o); + t.onAdd.dispatch(t, o); + }, + + getLength : function() { + return this.items.length; + }, + + renderHTML : function() { + var h, t = this; + + h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); + + each(t.items, function(it) { + h += DOM.createHTML('option', {value : it.value}, it.title); + }); + + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); + return h; + }, + + postRender : function() { + var t = this, ch, changeListenerAdded = true; + + t.rendered = true; + + function onChange(e) { + var v = t.items[e.target.selectedIndex - 1]; + + if (v && (v = v.value)) { + t.onChange.dispatch(t, v); + + if (t.settings.onselect) + t.settings.onselect(v); + } + }; + + Event.add(t.id, 'change', onChange); + + // Accessibility keyhandler + Event.add(t.id, 'keydown', function(e) { + var bf; + + Event.remove(t.id, 'change', ch); + changeListenerAdded = false; + + bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; + Event.add(t.id, 'change', onChange); + Event.remove(t.id, 'blur', bf); + }); + + //prevent default left and right keys on chrome - so that the keyboard navigation is used. + if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { + return Event.prevent(e); + } + + if (e.keyCode == 13 || e.keyCode == 32) { + onChange(e); + return Event.cancel(e); + } + }); + + t.onPostRender.dispatch(t, DOM.get(t.id)); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); + + this.onRenderMenu = new tinymce.util.Dispatcher(this); + + s.menu_container = s.menu_container || DOM.doc.body; + }, + + showMenu : function() { + var t = this, p1, p2, e = DOM.get(t.id), m; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + p1 = DOM.getPos(t.settings.menu_container); + p2 = DOM.getPos(e); + + m = t.menu; + m.settings.offset_x = p2.x; + m.settings.offset_y = p2.y; + m.settings.vp_offset_x = p2.x; + m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; + m.showMenu(0, e.clientHeight); + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.setState('Selected', 1); + + t.isMenuVisible = 1; + }, + + renderMenu : function() { + var t = this, m; + + m = t.settings.control_manager.createDropMenu(t.id + '_menu', { + menu_line : 1, + 'class' : this.classPrefix + 'Menu', + icons : t.settings.icons + }); + + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); + + t.onRenderMenu.dispatch(t, m); + t.menu = m; + }, + + hideMenu : function(e) { + var t = this; + + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceMenu')) { + t.setState('Selected', 0); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + if (t.menu) + t.menu.hideMenu(); + } + + t.isMenuVisible = 0; + }, + + postRender : function() { + var t = this, s = t.settings; + + Event.add(t.id, 'click', function() { + if (!t.isDisabled()) { + if (s.onclick) + s.onclick(t.value); + + t.showMenu(); + } + }); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); + this.classPrefix = 'mceSplitButton'; + }, + + renderHTML : function() { + var h, t = this, s = t.settings, h1; + + h = ''; + + if (s.image) + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); + else + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); + + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + + h += ''; + h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); + }, + + postRender : function() { + var t = this, s = t.settings, activate; + + if (s.onclick) { + activate = function(evt) { + if (!t.isDisabled()) { + s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } + }); + } + + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); + + // Old IE doesn't have hover on all elements + if (tinymce.isIE6 || !DOM.boxModel) { + Event.add(t.id, 'mouseover', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.addClass(t.id, 'mceSplitButtonHover'); + }); + + Event.add(t.id, 'mouseout', function() { + if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) + DOM.removeClass(t.id, 'mceSplitButtonHover'); + }); + } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); + Event.clear(this.id); + } + }); +})(tinymce); + +(function(tinymce) { + var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; + + tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { + ColorSplitButton : function(id, s, ed) { + var t = this; + + t.parent(id, s, ed); + + t.settings = s = tinymce.extend({ + colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', + grid_width : 8, + default_color : '#888888' + }, t.settings); + + t.onShowMenu = new tinymce.util.Dispatcher(t); + + t.onHideMenu = new tinymce.util.Dispatcher(t); + + t.value = s.default_color; + }, + + showMenu : function() { + var t = this, r, p, e, p2; + + if (t.isDisabled()) + return; + + if (!t.isMenuRendered) { + t.renderMenu(); + t.isMenuRendered = true; + } + + if (t.isMenuVisible) + return t.hideMenu(); + + e = DOM.get(t.id); + DOM.show(t.id + '_menu'); + DOM.addClass(e, 'mceSplitButtonSelected'); + p2 = DOM.getPos(e); + DOM.setStyles(t.id + '_menu', { + left : p2.x, + top : p2.y + e.clientHeight, + zIndex : 200000 + }); + e = 0; + + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + t.onShowMenu.dispatch(t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.isMenuVisible = 1; + }, + + hideMenu : function(e) { + var t = this; + + if (t.isMenuVisible) { + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } + + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + } + }, + + renderMenu : function() { + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; + + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); + DOM.add(m, 'span', {'class' : 'mceMenuLine'}); + + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); + tb = DOM.add(n, 'tbody'); + + // Generate color grid + i = 0; + each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { + c = c.replace(/^#/, ''); + + if (!i--) { + tr = DOM.add(tb, 'tr'); + i = s.grid_width - 1; + } + + n = DOM.add(tr, 'td'); + n = DOM.add(n, 'a', { + role : 'option', + href : 'javascript:;', + style : { + backgroundColor : '#' + c + }, + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c + }); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } + }); + + if (s.more_colors_func) { + n = DOM.add(tb, 'tr'); + n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); + n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + + Event.add(n, 'click', function(e) { + s.more_colors_func.call(s.more_colors_scope || this); + return Event.cancel(e); // Cancel to fix onbeforeunload problem + }); + } + + DOM.addClass(m, 'mceColorSplitMenu'); + + new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); + + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = DOM.getParent(e.target, 'a', tb); + + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) + t.setColor(c); + + return Event.cancel(e); // Prevent IE auto save warning + }); + + return w; + }, + + setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + displayColor : function(c) { + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); + + t.value = c; + }, + + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_menu'); + Event.clear(this.id + '_more'); + DOM.remove(this.id + '_menu'); + } + }); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + var t = this; + dom.get(t.id).focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + //Move focus if webkit so that navigation back will read the item. + if (tinymce.isWebKit) { + dom.get(t.editor.id+"_ifr").focus(); + } + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; +tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) + c += ' mceToolbarEndButton'; + else if (co.SplitButton) + c += ' mceToolbarEndSplitButton'; + else if (co.ListBox) + c += ' mceToolbarEndListBox'; + + h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); + + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); + } +}); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; + + tinymce.create('tinymce.AddOnManager', { + AddOnManager : function() { + var self = this; + + self.items = []; + self.urls = {}; + self.lookup = {}; + self.onAdd = new Dispatcher(self); + }, + + get : function(n) { + if (this.lookup[n]) { + return this.lookup[n].instance; + } else { + return undefined; + } + }, + + dependencies : function(n) { + var result; + if (this.lookup[n]) { + result = this.lookup[n].dependencies; + } + return result || []; + }, + + requireLangPack : function(n) { + var s = tinymce.settings; + + if (s && s.language && s.language_load !== false) + tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); + }, + + add : function(id, o, dependencies) { + this.items.push(o); + this.lookup[id] = {instance:o, dependencies:dependencies}; + this.onAdd.dispatch(this, id, o); + + return o; + }, + createUrl: function(baseUrl, dep) { + if (typeof dep === "object") { + return dep + } else { + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; + } + }, + + addComponents: function(pluginName, scripts) { + var pluginUrl = this.urls[pluginName]; + tinymce.each(scripts, function(script){ + tinymce.ScriptLoader.add(pluginUrl+"/"+script); + }); + }, + + load : function(n, u, cb, s) { + var t = this, url = u; + + function loadDependencies() { + var dependencies = t.dependencies(n); + tinymce.each(dependencies, function(dep) { + var newUrl = t.createUrl(u, dep); + t.load(newUrl.resource, newUrl, undefined, undefined); + }); + if (cb) { + if (s) { + cb.call(s); + } else { + cb.call(tinymce.ScriptLoader); + } + } + } + + if (t.urls[n]) + return; + if (typeof u === "object") + url = u.prefix + u.resource + u.suffix; + + if (url.indexOf('/') != 0 && url.indexOf('://') == -1) + url = tinymce.baseURL + '/' + url; + + t.urls[n] = url.substring(0, url.lastIndexOf('/')); + + if (t.lookup[n]) { + loadDependencies(); + } else { + tinymce.ScriptLoader.add(url, loadDependencies, s); + } + } + }); + + // Create plugin and theme managers + tinymce.PluginManager = new tinymce.AddOnManager(); + tinymce.ThemeManager = new tinymce.AddOnManager(); +}(tinymce)); + +(function(tinymce) { + // Shorten names + var each = tinymce.each, extend = tinymce.extend, + DOM = tinymce.DOM, Event = tinymce.dom.Event, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + explode = tinymce.explode, + Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + + tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + tinymce.onBeforeUnload = new Dispatcher(tinymce); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + tinymce.onBeforeUnload.dispatch(tinymce, e); + }); + + tinymce.onAddEditor = new Dispatcher(tinymce); + + tinymce.onRemoveEditor = new Dispatcher(tinymce); + + tinymce.EditorManager = extend(tinymce, { + editors : [], + + i18n : {}, + + activeEditor : null, + + init : function(s) { + var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; + + function execCallback(se, n, s) { + var f = se[n]; + + if (!f) + return; + + if (tinymce.is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + } + + return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); + }; + + s = extend({ + theme : "simple", + language : "en" + }, s); + + t.settings = s; + + // Legacy call + Event.add(document, 'init', function() { + var l, co; + + execCallback(s, 'onpageload'); + + switch (s.mode) { + case "exact": + l = s.elements || ''; + + if(l.length > 0) { + each(explode(l), function(v) { + if (DOM.get(v)) { + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } else { + each(document.forms, function(f) { + each(f.elements, function(e) { + if (e.name === v) { + v = 'mce_editor_' + instanceCounter++; + DOM.setAttrib(e, 'id', v); + + ed = new tinymce.Editor(v, s); + el.push(ed); + ed.render(1); + } + }); + }); + } + }); + } + break; + + case "textareas": + case "specific_textareas": + function hasClass(n, c) { + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); + }; + + each(DOM.select('textarea'), function(v) { + if (s.editor_deselector && hasClass(v, s.editor_deselector)) + return; + + if (!s.editor_selector || hasClass(v, s.editor_selector)) { + // Can we use the name + e = DOM.get(v.name); + if (!v.id && !e) + v.id = v.name; + + // Generate unique name if missing or already exists + if (!v.id || t.get(v.id)) + v.id = DOM.uniqueId(); + + ed = new tinymce.Editor(v.id, s); + el.push(ed); + ed.render(1); + } + }); + break; + } + + // Call onInit when all editors are initialized + if (s.oninit) { + l = co = 0; + + each(el, function(ed) { + co++; + + if (!ed.initialized) { + // Wait for it + ed.onInit.add(function() { + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } else + l++; + + // All done + if (l == co) + execCallback(s, 'oninit'); + }); + } + }); + }, + + get : function(id) { + if (id === undefined) + return this.editors; + + return this.editors[id]; + }, + + getInstanceById : function(id) { + return this.get(id); + }, + + add : function(editor) { + var self = this, editors = self.editors; + + // Add named and index editor instance + editors[editor.id] = editor; + editors.push(editor); + + self._setActive(editor); + self.onAddEditor.dispatch(self, editor); + + + return editor; + }, + + remove : function(editor) { + var t = this, i, editors = t.editors; + + // Not in the collection + if (!editors[editor.id]) + return null; + + delete editors[editor.id]; + + for (i = 0; i < editors.length; i++) { + if (editors[i] == editor) { + editors.splice(i, 1); + break; + } + } + + // Select another editor since the active one was removed + if (t.activeEditor == editor) + t._setActive(editors[0]); + + editor.destroy(); + t.onRemoveEditor.dispatch(t, editor); + + return editor; + }, + + execCommand : function(c, u, v) { + var t = this, ed = t.get(v), w; + + // Manager commands + switch (c) { + case "mceFocus": + ed.focus(); + return true; + + case "mceAddEditor": + case "mceAddControl": + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + + return true; + + case "mceAddFrameControl": + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE) { + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + w.attachEvent('onunload', clr); + } + + v.page_window = null; + + return true; + + case "mceRemoveEditor": + case "mceRemoveControl": + if (ed) + ed.remove(); + + return true; + + case 'mceToggleEditor': + if (!ed) { + t.execCommand('mceAddControl', 0, v); + return true; + } + + if (ed.isHidden()) + ed.show(); + else + ed.hide(); + + return true; + } + + // Run command on active editor + if (t.activeEditor) + return t.activeEditor.execCommand(c, u, v); + + return false; + }, + + execInstanceCommand : function(id, c, u, v) { + var ed = this.get(id); + + if (ed) + return ed.execCommand(c, u, v); + + return false; + }, + + triggerSave : function() { + each(this.editors, function(e) { + e.save(); + }); + }, + + addI18n : function(p, o) { + var lo, i18n = this.i18n; + + if (!tinymce.is(p, 'string')) { + each(p, function(o, lc) { + each(o, function(o, g) { + each(o, function(o, k) { + if (g === 'common') + i18n[lc + '.' + k] = o; + else + i18n[lc + '.' + g + '.' + k] = o; + }); + }); + }); + } else { + each(o, function(o, k) { + i18n[p + '.' + k] = o; + }); + } + }, + + // Private methods + + _setActive : function(editor) { + this.selectedInstance = this.activeEditor = editor; + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten these names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, + Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko, + isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, + ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, + inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; + + tinymce.create('tinymce.Editor', { + Editor : function(id, s) { + var t = this; + + t.id = t.editorId = id; + + t.execCommands = {}; + t.queryStateCommands = {}; + t.queryValueCommands = {}; + + t.isNotDirty = false; + + t.plugins = {}; + + // Add events to the editor + each([ + 'onPreInit', + + 'onBeforeRenderUI', + + 'onPostRender', + + 'onInit', + + 'onRemove', + + 'onActivate', + + 'onDeactivate', + + 'onClick', + + 'onEvent', + + 'onMouseUp', + + 'onMouseDown', + + 'onDblClick', + + 'onKeyDown', + + 'onKeyUp', + + 'onKeyPress', + + 'onContextMenu', + + 'onSubmit', + + 'onReset', + + 'onPaste', + + 'onPreProcess', + + 'onPostProcess', + + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent', + + 'onLoadContent', + + 'onSaveContent', + + 'onNodeChange', + + 'onChange', + + 'onBeforeExecCommand', + + 'onExecCommand', + + 'onUndo', + + 'onRedo', + + 'onVisualAid', + + 'onSetProgressState' + ], function(e) { + t[e] = new Dispatcher(t); + }); + + t.settings = s = extend({ + id : id, + language : 'en', + docs_language : 'en', + theme : 'simple', + skin : 'default', + delta_width : 0, + delta_height : 0, + popup_css : '', + plugins : '', + document_base_url : tinymce.documentBaseURL, + add_form_submit_trigger : 1, + submit_patch : 1, + add_unload_trigger : 1, + convert_urls : 1, + relative_urls : 1, + remove_script_host : 1, + table_inline_editing : 0, + object_resizing : 1, + cleanup : 1, + accessibility_focus : 1, + custom_shortcuts : 1, + custom_undo_redo_keyboard_shortcuts : 1, + custom_undo_redo_restore_selection : 1, + custom_undo_redo : 1, + doctype : tinymce.isIE6 ? '' : '', // Use old doctype on IE 6 to avoid horizontal scroll + visual_table_class : 'mceItemTable', + visual : 1, + font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', + font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size + apply_source_formatting : 1, + directionality : 'ltr', + forced_root_block : 'p', + hidden_input : 1, + padd_empty_editor : 1, + render_ui : 1, + init_theme : 1, + force_p_newlines : 1, + indentation : '30px', + keep_styles : 1, + fix_table_elements : 1, + inline_styles : 1, + convert_fonts_to_spans : true, + indent : 'simple', + indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr', + validate : true, + entity_encoding : 'named', + url_converter : t.convertURL, + url_converter_scope : t, + ie7_compat : true + }, s); + + t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, { + base_uri : tinyMCE.baseURI + }); + + t.baseURI = tinymce.baseURI; + + t.contentCSS = []; + + // Call setup + t.execCallback('setup', t); + }, + + render : function(nst) { + var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; + + // Page is not loaded yet, wait for it + if (!Event.domLoaded) { + Event.add(document, 'init', function() { + t.render(); + }); + return; + } + + tinyMCE.settings = s; + + // Element not found, then skip initialization + if (!t.getElement()) + return; + + // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff + // here since the browser says it has contentEditable support but there is no visible + // caret We will remove this check ones Apple implements full contentEditable support + if (tinymce.isIDevice && !tinymce.isIOS5) + return; + + // Add hidden input for non input elements inside form elements + if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) + DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); + + if (tinymce.WindowManager) + t.windowManager = new tinymce.WindowManager(t); + + if (s.encoding == 'xml') { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = DOM.encode(o.content); + }); + } + + if (s.add_form_submit_trigger) { + t.onSubmit.addToTop(function() { + if (t.initialized) { + t.save(); + t.isNotDirty = 1; + } + }); + } + + if (s.add_unload_trigger) { + t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { + if (t.initialized && !t.destroyed && !t.isHidden()) + t.save({format : 'raw', no_events : true}); + }); + } + + tinymce.addUnload(t.destroy, t); + + if (s.submit_patch) { + t.onBeforeRenderUI.add(function() { + var n = t.getElement().form; + + if (!n) + return; + + // Already patched + if (n._mceOldSubmit) + return; + + // Check page uses id="submit" or name="submit" for it's submit button + if (!n.submit.nodeType && !n.submit.length) { + t.formElement = n; + n._mceOldSubmit = n.submit; + n.submit = function() { + // Save all instances + tinymce.triggerSave(); + t.isNotDirty = 1; + + return t.formElement._mceOldSubmit(t.formElement); + }; + } + + n = null; + }); + } + + // Load scripts + function loadScripts() { + if (s.language && s.language_load !== false) + sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); + + if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) + ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); + + each(explode(s.plugins), function(p) { + if (p &&!PluginManager.urls[p]) { + if (p.charAt(0) == '-') { + p = p.substr(1, p.length); + var dependencies = PluginManager.dependencies(p); + each(dependencies, function(dep) { + var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; + var dep = PluginManager.createUrl(defaultSettings, dep); + PluginManager.load(dep.resource, dep); + + }); + } else { + // Skip safari plugin, since it is removed as of 3.3b1 + if (p == 'safari') { + return; + } + PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); + } + } + }); + + // Init when que is loaded + sl.loadQueue(function() { + if (!t.removed) + t.init(); + }); + }; + + loadScripts(); + }, + + init : function() { + var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; + + tinymce.add(t); + + s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); + + if (s.theme) { + s.theme = s.theme.replace(/-/, ''); + o = ThemeManager.get(s.theme); + t.theme = new o(); + + if (t.theme.init && s.init_theme) + t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); + } + function initPlugin(p) { + var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; + if (c && tinymce.inArray(initializedPlugins,p) === -1) { + each(PluginManager.dependencies(p), function(dep){ + initPlugin(dep); + }); + po = new c(t, u); + + t.plugins[p] = po; + + if (po.init) { + po.init(t, u); + initializedPlugins.push(p); + } + } + } + + // Create all plugins + each(explode(s.plugins.replace(/\-/g, '')), initPlugin); + + // Setup popup CSS path(s) + if (s.popup_css !== false) { + if (s.popup_css) + s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); + else + s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); + } + + if (s.popup_css_add) + s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); + + t.controlManager = new tinymce.ControlManager(t); + + if (s.custom_undo_redo) { + t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.beforeChange(); + }); + + t.onExecCommand.add(function(ed, cmd, ui, val, a) { + if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) + t.undoManager.add(); + }); + } + + t.onExecCommand.add(function(ed, c) { + // Don't refresh the select lists until caret move + if (!/^(FontName|FontSize)$/.test(c)) + t.nodeChanged(); + }); + + // Remove ghost selections on images and tables in Gecko + if (isGecko) { + function repaint(a, o) { + if (!o || !o.initial) + t.execCommand('mceRepaint'); + }; + + t.onUndo.add(repaint); + t.onRedo.add(repaint); + t.onSetContent.add(repaint); + } + + // Enables users to override the control factory + t.onBeforeRenderUI.dispatch(t, t.controlManager); + + // Measure box + if (s.render_ui) { + w = s.width || e.style.width || e.offsetWidth; + h = s.height || e.style.height || e.offsetHeight; + t.orgDisplay = e.style.display; + re = /^[0-9\.]+(|px)$/i; + + if (re.test('' + w)) + w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100); + + if (re.test('' + h)) + h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100); + + // Render UI + o = t.theme.renderUI({ + targetNode : e, + width : w, + height : h, + deltaWidth : s.delta_width, + deltaHeight : s.delta_height + }); + + t.editorContainer = o.editorContainer; + } + + + // User specified a document.domain value + if (document.domain && location.hostname != document.domain) + tinymce.relaxedDomain = document.domain; + + // Resize editor + DOM.setStyles(o.sizeContainer || o.editorContainer, { + width : w, + height : h + }); + + // Load specified content CSS last + if (s.content_css) { + tinymce.each(explode(s.content_css), function(u) { + t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); + }); + } + + h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); + if (h < 100) + h = 100; + + t.iframeHTML = s.doctype + ''; + + // We only need to override paths if we have to + // IE has a bug where it remove site absolute urls to relative ones if this is specified + if (s.document_base_url != tinymce.documentBaseURL) + t.iframeHTML += ''; + + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + + t.iframeHTML += ''; + + // Load the CSS by injecting them into the HTML this will reduce "flicker" + for (i = 0; i < t.contentCSS.length; i++) { + t.iframeHTML += ''; + } + + bi = s.body_id || 'tinymce'; + if (bi.indexOf('=') != -1) { + bi = t.getParam('body_id', '', 'hash'); + bi = bi[t.id] || bi; + } + + bc = s.body_class || ''; + if (bc.indexOf('=') != -1) { + bc = t.getParam('body_class', '', 'hash'); + bc = bc[t.id] || ''; + } + + t.iframeHTML += '
    '; + + // Domain relaxing enabled, then set document domain + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { + // We need to write the contents here in IE since multiple writes messes up refresh button and back button + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; + } + + // Create iframe + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { + id : t.id + "_ifr", + src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 + frameBorder : '0', + allowTransparency : "true", + title : s.aria_label, + style : { + width : '100%', + height : h, + display : 'block' // Important for Gecko to render the iframe correctly + } + }); + + t.contentAreaContainer = o.iframeContainer; + DOM.get(o.editorContainer).style.display = t.orgDisplay; + DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); + + if (!tinymce.relaxedDomain || !u) + t.setupIframe(); + + e = n = o = null; // Cleanup + }, + + setupIframe : function() { + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; + + // Setup iframe body + if (!isIE || !tinymce.relaxedDomain) { + d.open(); + d.write(t.iframeHTML); + d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; + } + + // It will not steal focus while setting contentEditable + b = t.getBody(); + b.disabled = true; + + if (!s.readonly) + b.contentEditable = true; + + b.disabled = false; + + t.schema = new tinymce.html.Schema(s); + + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { + keep_values : true, + url_converter : t.convertURL, + url_converter_scope : t, + hex_colors : s.force_hex_style_colors, + class_filter : s.class_filter, + update_styles : 1, + fix_ie_paragraphs : 1, + schema : t.schema + }); + + t.parser = new tinymce.html.DomParser(s, t.schema); + + // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. + if (!t.settings.allow_html_in_named_anchor) { + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + } + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value, internalName; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + internalName = 'data-mce-' + name; + + // Add internal attribute if we need to we don't on a refresh of the document + if (!node.attributes.map[internalName]) { + if (name === "style") + node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr(internalName, t.convertURL(value, name, node.name)); + } + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); + } + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); + + t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); + + t.formatter = new tinymce.Formatter(this); + + // Register default formats + t.formatter.register({ + alignleft : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} + ], + + aligncenter : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} + ], + + alignright : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} + ], + + alignfull : [ + {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}} + ], + + bold : [ + {inline : 'strong', remove : 'all'}, + {inline : 'span', styles : {fontWeight : 'bold'}}, + {inline : 'b', remove : 'all'} + ], + + italic : [ + {inline : 'em', remove : 'all'}, + {inline : 'span', styles : {fontStyle : 'italic'}}, + {inline : 'i', remove : 'all'} + ], + + underline : [ + {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, + {inline : 'u', remove : 'all'} + ], + + strikethrough : [ + {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, + {inline : 'strike', remove : 'all'} + ], + + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, + fontname : {inline : 'span', styles : {fontFamily : '%value'}}, + fontsize : {inline : 'span', styles : {fontSize : '%value'}}, + fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, + blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, + + link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, + onmatch : function(node) { + return true; + }, + + onformat : function(elm, fmt, vars) { + each(vars, function(value, key) { + t.dom.setAttrib(elm, key, value); + }); + } + }, + + removeformat : [ + {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, + {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, + {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} + ] + }); + + // Register default block formats + each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { + t.formatter.register(name, {block : name, remove : 'all'}); + }); + + // Register user defined formats + t.formatter.register(t.settings.formats); + + t.undoManager = new tinymce.UndoManager(t); + + // Pass through + t.undoManager.onAdd.add(function(um, l) { + if (um.hasUndo()) + return t.onChange.dispatch(t, l, um); + }); + + t.undoManager.onUndo.add(function(um, l) { + return t.onUndo.dispatch(t, l, um); + }); + + t.undoManager.onRedo.add(function(um, l) { + return t.onRedo.dispatch(t, l, um); + }); + + t.forceBlocks = new tinymce.ForceBlocks(t, { + forced_root_block : s.forced_root_block + }); + + t.editorCommands = new tinymce.EditorCommands(t); + + // Pass through + t.serializer.onPreProcess.add(function(se, o) { + return t.onPreProcess.dispatch(t, o, se); + }); + + t.serializer.onPostProcess.add(function(se, o) { + return t.onPostProcess.dispatch(t, o, se); + }); + + t.onPreInit.dispatch(t); + + if (!s.gecko_spellcheck) + t.getBody().spellcheck = 0; + + if (!s.readonly) + t._addEvents(); + + t.controlManager.onPostRender.dispatch(t, t.controlManager); + t.onPostRender.dispatch(t); + + t.quirks = new tinymce.util.Quirks(this); + + if (s.directionality) + t.getBody().dir = s.directionality; + + if (s.nowrap) + t.getBody().style.whiteSpace = "nowrap"; + + if (s.handle_node_change_callback) { + t.onNodeChange.add(function(ed, cm, n) { + t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); + }); + } + + if (s.save_callback) { + t.onSaveContent.add(function(ed, o) { + var h = t.execCallback('save_callback', t.id, o.content, t.getBody()); + + if (h) + o.content = h; + }); + } + + if (s.onchange_callback) { + t.onChange.add(function(ed, l) { + t.execCallback('onchange_callback', t, l); + }); + } + + if (s.protect) { + t.onBeforeSetContent.add(function(ed, o) { + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } + }); + } + + if (s.convert_newlines_to_brs) { + t.onBeforeSetContent.add(function(ed, o) { + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
    '); + }); + } + + if (s.preformatted) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^\s*/, ''); + o.content = o.content.replace(/<\/pre>\s*$/, ''); + + if (o.set) + o.content = '
    ' + o.content + '
    '; + }); + } + + if (s.verify_css_classes) { + t.serializer.attribValueFilter = function(n, v) { + var s, cl; + + if (n == 'class') { + // Build regexp for classes + if (!t.classesRE) { + cl = t.dom.getClasses(); + + if (cl.length > 0) { + s = ''; + + each (cl, function(o) { + s += (s ? '|' : '') + o['class']; + }); + + t.classesRE = new RegExp('(' + s + ')', 'gi'); + } + } + + return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : ''; + } + + return v; + }; + } + + if (s.cleanup_callback) { + t.onBeforeSetContent.add(function(ed, o) { + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + }); + + t.onPreProcess.add(function(ed, o) { + if (o.set) + t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); + + if (o.get) + t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); + }); + + t.onPostProcess.add(function(ed, o) { + if (o.set) + o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); + + if (o.get) + o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o); + }); + } + + if (s.save_callback) { + t.onGetContent.add(function(ed, o) { + if (o.save) + o.content = t.execCallback('save_callback', t.id, o.content, t.getBody()); + }); + } + + if (s.handle_event_callback) { + t.onEvent.add(function(ed, e, o) { + if (t.execCallback('handle_event_callback', e, ed, o) === false) + Event.cancel(e); + }); + } + + // Add visual aids when new contents is added + t.onSetContent.add(function() { + t.addVisual(t.getBody()); + }); + + // Remove empty contents + if (s.padd_empty_editor) { + t.onPostProcess.add(function(ed, o) { + o.content = o.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
    [\r\n]*)$/, ''); + }); + } + + if (isGecko) { + // Fix gecko link bug, when a link is placed at the end of block elements there is + // no way to move the caret behind the link. This fix adds a bogus br element after the link + function fixLinks(ed, o) { + each(ed.dom.select('a'), function(n) { + var pn = n.parentNode; + + if (ed.dom.isBlock(pn) && pn.lastChild === n) + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); + }); + }; + + t.onExecCommand.add(function(ed, cmd) { + if (cmd === 'CreateLink') + fixLinks(ed); + }); + + t.onSetContent.add(t.selection.onSetContent.add(fixLinks)); + } + + t.load({initial : true, format : 'html'}); + t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); + t.initialized = true; + + t.onInit.dispatch(t); + t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc()); + t.execCallback('init_instance_callback', t); + t.focus(true); + t.nodeChanged({initial : 1}); + + // Load specified content CSS last + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); + + // Handle auto focus + if (s.auto_focus) { + setTimeout(function () { + var ed = tinymce.get(s.auto_focus); + + ed.selection.select(ed.getBody(), 1); + ed.selection.collapse(1); + ed.getBody().focus(); + ed.getWin().focus(); + }, 100); + } + + e = null; + }, + + + focus : function(sf) { + var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc(); + + if (!sf) { + // Get selected control element + ieRng = selection.getRng(); + if (ieRng.item) { + controlElm = ieRng.item(0); + } + + t._refreshContentEditable(); + selection.normalize(); + + // Is not content editable + if (!ce) + t.getWin().focus(); + + // Focus the body as well since it's contentEditable + if (tinymce.isGecko) { + t.getBody().focus(); + } + + // Restore selected control element + // This is needed when for example an image is selected within a + // layer a call to focus will then remove the control selection + if (controlElm && controlElm.ownerDocument == doc) { + ieRng = doc.body.createControlRange(); + ieRng.addElement(controlElm); + ieRng.select(); + } + + } + + if (tinymce.activeEditor != t) { + if ((oed = tinymce.activeEditor) != null) + oed.onDeactivate.dispatch(oed, t); + + t.onActivate.dispatch(t, oed); + } + + tinymce._setActive(t); + }, + + execCallback : function(n) { + var t = this, f = t.settings[n], s; + + if (!f) + return; + + // Look through lookup + if (t.callbackLookup && (s = t.callbackLookup[n])) { + f = s.func; + s = s.scope; + } + + if (is(f, 'string')) { + s = f.replace(/\.\w+$/, ''); + s = s ? tinymce.resolve(s) : 0; + f = tinymce.resolve(f); + t.callbackLookup = t.callbackLookup || {}; + t.callbackLookup[n] = {func : f, scope : s}; + } + + return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); + }, + + translate : function(s) { + var c = this.settings.language || 'en', i18n = tinymce.i18n; + + if (!s) + return ''; + + return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) { + return i18n[c + '.' + b] || '{#' + b + '}'; + }); + }, + + getLang : function(n, dv) { + return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); + }, + + getParam : function(n, dv, ty) { + var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; + + if (ty === 'hash') { + o = {}; + + if (is(v, 'string')) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { + v = v.split('='); + + if (v.length > 1) + o[tr(v[0])] = tr(v[1]); + else + o[tr(v[0])] = tr(v); + }); + } else + o = v; + + return o; + } + + return v; + }, + + nodeChanged : function(o) { + var t = this, s = t.selection, n = s.getStart() || t.getBody(); + + // Fix for bug #1896577 it seems that this can not be fired while the editor is loading + if (t.initialized) { + o = o || {}; + n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state + + // Get parents and add them to object + o.parents = []; + t.dom.getParent(n, function(node) { + if (node.nodeName == 'BODY') + return true; + + o.parents.push(node); + }); + + t.onNodeChange.dispatch( + t, + o ? o.controlManager || t.controlManager : t.controlManager, + n, + s.isCollapsed(), + o + ); + } + }, + + addButton : function(n, s) { + var t = this; + + t.buttons = t.buttons || {}; + t.buttons[n] = s; + }, + + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; + }, + + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; + }, + + addShortcut : function(pa, desc, cmd_func, sc) { + var t = this, c; + + if (!t.settings.custom_shortcuts) + return false; + + t.shortcuts = t.shortcuts || {}; + + if (is(cmd_func, 'string')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c, false, null); + }; + } + + if (is(cmd_func, 'object')) { + c = cmd_func; + + cmd_func = function() { + t.execCommand(c[0], c[1], c[2]); + }; + } + + each(explode(pa), function(pa) { + var o = { + func : cmd_func, + scope : sc || this, + desc : desc, + alt : false, + ctrl : false, + shift : false + }; + + each(explode(pa, '+'), function(v) { + switch (v) { + case 'alt': + case 'ctrl': + case 'shift': + o[v] = true; + break; + + default: + o.charCode = v.charCodeAt(0); + o.keyCode = v.toUpperCase().charCodeAt(0); + } + }); + + t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; + }); + + return true; + }, + + execCommand : function(cmd, ui, val, a) { + var t = this, s = 0, o, st; + + if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) + t.focus(); + + o = {}; + t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o); + if (o.terminate) + return false; + + // Command callback + if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Registred commands + if (o = t.execCommands[cmd]) { + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } + } + + // Plugin commands + each(t.plugins, function(p) { + if (p.execCommand && p.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + s = 1; + return false; + } + }); + + if (s) + return true; + + // Theme commands + if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Editor commands + if (t.editorCommands.execCommand(cmd, ui, val)) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return true; + } + + // Browser commands + t.getDoc().execCommand(cmd, ui, val); + t.onExecCommand.dispatch(t, cmd, ui, val, a); + }, + + queryCommandState : function(cmd) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryStateCommands[cmd]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandState(cmd); + if (o !== -1) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandState(cmd); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + queryCommandValue : function(c) { + var t = this, o, s; + + // Is hidden then return undefined + if (t._isHidden()) + return; + + // Registred commands + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } + + // Registred commands + o = t.editorCommands.queryCommandValue(c); + if (is(o)) + return o; + + // Browser commands + try { + return this.getDoc().queryCommandValue(c); + } catch (ex) { + // Fails sometimes see bug: 1896577 + } + }, + + show : function() { + var t = this; + + DOM.show(t.getContainer()); + DOM.hide(t.id); + t.load(); + }, + + hide : function() { + var t = this, d = t.getDoc(); + + // Fixed bug where IE has a blinking cursor left from the editor + if (isIE && d) + d.execCommand('SelectAll'); + + // We must save before we hide so Safari doesn't crash + t.save(); + DOM.hide(t.getContainer()); + DOM.setStyle(t.id, 'display', t.orgDisplay); + }, + + isHidden : function() { + return !DOM.isHidden(this.id); + }, + + setProgressState : function(b, ti, o) { + this.onSetProgressState.dispatch(this, b, ti, o); + + return b; + }, + + load : function(o) { + var t = this, e = t.getElement(), h; + + if (e) { + o = o || {}; + o.load = true; + + // Double encode existing entities in the value + h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); + o.element = e; + + if (!o.no_events) + t.onLoadContent.dispatch(t, o); + + o.element = e = null; + + return h; + } + }, + + save : function(o) { + var t = this, e = t.getElement(), h, f; + + if (!e || !t.initialized) + return; + + o = o || {}; + o.save = true; + + // Add undo level will trigger onchange event + if (!o.no_events) { + t.undoManager.typing = false; + t.undoManager.add(); + } + + o.element = e; + h = o.content = t.getContent(o); + + if (!o.no_events) + t.onSaveContent.dispatch(t, o); + + h = o.content; + + if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { + e.innerHTML = h; + + // Update hidden form element + if (f = DOM.getParent(t.id, 'form')) { + each(f.elements, function(e) { + if (e.name == t.id) { + e.value = h; + return false; + } + }); + } + } else + e.value = h; + + o.element = e = null; + + return h; + }, + + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(), forcedRootBlockName; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; + + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; + + // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content + // It will also be impossible to place the caret in the editor unless there is a BR element present + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + forcedRootBlockName = self.settings.forced_root_block; + if (forcedRootBlockName) + content = '<' + forcedRootBlockName + '>
    '; + else + content = '
    '; + + body.innerHTML = content; + self.selection.select(body, true); + self.selection.collapse(true); + return; + } + + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); + } + + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); + + self.selection.normalize(); + + return args.content; + }, + + getContent : function(args) { + var self = this, content; + + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; + + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); + + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); + + args.content = tinymce.trim(content); + + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); + + return args.content; + }, + + isDirty : function() { + var self = this; + + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; + }, + + getContainer : function() { + var t = this; + + if (!t.container) + t.container = DOM.get(t.editorContainer || t.id + '_parent'); + + return t.container; + }, + + getContentAreaContainer : function() { + return this.contentAreaContainer; + }, + + getElement : function() { + return DOM.get(this.settings.content_element || this.id); + }, + + getWin : function() { + var t = this, e; + + if (!t.contentWindow) { + e = DOM.get(t.id + "_ifr"); + + if (e) + t.contentWindow = e.contentWindow; + } + + return t.contentWindow; + }, + + getDoc : function() { + var t = this, w; + + if (!t.contentDocument) { + w = t.getWin(); + + if (w) + t.contentDocument = w.document; + } + + return t.contentDocument; + }, + + getBody : function() { + return this.bodyElement || this.getDoc().body; + }, + + convertURL : function(u, n, e) { + var t = this, s = t.settings; + + // Use callback instead + if (s.urlconverter_callback) + return t.execCallback('urlconverter_callback', u, e, true, n); + + // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs + if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0) + return u; + + // Convert to relative + if (s.relative_urls) + return t.documentBaseURI.toRelative(u); + + // Convert to absolute + u = t.documentBaseURI.toAbsolute(u, s.remove_script_host); + + return u; + }, + + addVisual : function(e) { + var t = this, s = t.settings; + + e = e || t.getBody(); + + if (!is(t.hasVisual)) + t.hasVisual = s.visual; + + each(t.dom.select('table,a', e), function(e) { + var v; + + switch (e.nodeName) { + case 'TABLE': + v = t.dom.getAttrib(e, 'border'); + + if (!v || v == '0') { + if (t.hasVisual) + t.dom.addClass(e, s.visual_table_class); + else + t.dom.removeClass(e, s.visual_table_class); + } + + return; + + case 'A': + v = t.dom.getAttrib(e, 'name'); + + if (v) { + if (t.hasVisual) + t.dom.addClass(e, 'mceItemAnchor'); + else + t.dom.removeClass(e, 'mceItemAnchor'); + } + + return; + } + }); + + t.onVisualAid.dispatch(t, e, t.hasVisual); + }, + + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + tinymce.remove(t); + DOM.remove(e); + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme && t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + + // Internal functions + + _addEvents : function() { + // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset + var t = this, i, s = t.settings, dom = t.dom, lo = { + mouseup : 'onMouseUp', + mousedown : 'onMouseDown', + click : 'onClick', + keyup : 'onKeyUp', + keydown : 'onKeyDown', + keypress : 'onKeyPress', + submit : 'onSubmit', + reset : 'onReset', + contextmenu : 'onContextMenu', + dblclick : 'onDblClick', + paste : 'onPaste' // Doesn't work in all browsers yet + }; + + function eventHandler(e, o) { + var ty = e.type; + + // Don't fire events when it's removed + if (t.removed) + return; + + // Generic event handler + if (t.onEvent.dispatch(t, e, o) !== false) { + // Specific event handler + t[lo[e.fakeType || e.type]].dispatch(t, e, o); + } + }; + + // Add DOM events + each(lo, function(v, k) { + switch (k) { + case 'contextmenu': + dom.bind(t.getDoc(), k, eventHandler); + break; + + case 'paste': + dom.bind(t.getBody(), k, function(e) { + eventHandler(e); + }); + break; + + case 'submit': + case 'reset': + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + break; + + default: + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + } + }); + + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + t.focus(true); + }); + + + // Fixes bug where a specified document_base_uri could result in broken images + // This will also fix drag drop of images in Gecko + if (tinymce.isGecko) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + var v; + + e = e.target; + + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) + e.src = t.documentBaseURI.toAbsolute(v); + }); + } + + // Set various midas options in Gecko + if (isGecko) { + function setOpts() { + var t = this, d = t.getDoc(), s = t.settings; + + if (isGecko && !s.readonly) { + t._refreshContentEditable(); + + try { + // Try new Gecko method + d.execCommand("styleWithCSS", 0, false); + } catch (ex) { + // Use old method + if (!t._isHidden()) + try {d.execCommand("useCSS", 0, true);} catch (ex) {} + } + + if (!s.table_inline_editing) + try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {} + + if (!s.object_resizing) + try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {} + } + }; + + t.onBeforeExecCommand.add(setOpts); + t.onMouseDown.add(setOpts); + } + + // Add node change handlers + t.onMouseUp.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); + t.onKeyUp.add(function(ed, e) { + var c = e.keyCode; + + if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey) + t.nodeChanged(); + }); + + + // Add block quote deletion handler + t.onKeyDown.add(function(ed, e) { + // Was the BACKSPACE key pressed? + if (e.keyCode != 8) + return; + + var n = ed.selection.getRng().startContainer; + var offset = ed.selection.getRng().startOffset; + + while (n && n.nodeType && n.nodeType != 1 && n.parentNode) + n = n.parentNode; + + // Is the cursor at the beginning of a blockquote? + if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) { + // Remove the blockquote + ed.formatter.toggle('blockquote', null, n.parentNode); + + // Move the caret to the beginning of n + var rng = ed.selection.getRng(); + rng.setStart(n, 0); + rng.setEnd(n, 0); + ed.selection.setRng(rng); + ed.selection.collapse(false); + } + }); + + + + // Add reset handler + t.onReset.add(function() { + t.setContent(t.startContent, {format : 'raw'}); + }); + + // Add shortcuts + if (s.custom_shortcuts) { + if (s.custom_undo_redo_keyboard_shortcuts) { + t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo'); + t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo'); + } + + // Add default shortcuts for gecko + t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold'); + t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic'); + t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline'); + + // BlockFormat shortcuts keys + for (i=1; i<=6; i++) + t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); + + t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); + t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); + t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); + + function find(e) { + var v = null; + + if (!e.altKey && !e.ctrlKey && !e.metaKey) + return v; + + each(t.shortcuts, function(o) { + if (tinymce.isMac && o.ctrl != e.metaKey) + return; + else if (!tinymce.isMac && o.ctrl != e.ctrlKey) + return; + + if (o.alt != e.altKey) + return; + + if (o.shift != e.shiftKey) + return; + + if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) { + v = o; + return false; + } + }); + + return v; + }; + + t.onKeyUp.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyPress.add(function(ed, e) { + var o = find(e); + + if (o) + return Event.cancel(e); + }); + + t.onKeyDown.add(function(ed, e) { + var o = find(e); + + if (o) { + o.func.call(o.scope); + return Event.cancel(e); + } + }); + } + + if (tinymce.isIE) { + // Fix so resize will only update the width and height attributes not the styles of an image + // It will also block mceItemNoResize items + dom.bind(t.getDoc(), 'controlselect', function(e) { + var re = t.resizeInfo, cb; + + e = e.target; + + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + + if (re) + dom.unbind(re.node, re.ev, re.cb); + + if (!dom.hasClass(e, 'mceItemNoResize')) { + ev = 'resizeend'; + cb = dom.bind(e, ev, function(e) { + var v; + + e = e.target; + + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); + } + + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); + } + }); + } else { + ev = 'resizestart'; + cb = dom.bind(e, 'resizestart', Event.cancel, Event); + } + + re = t.resizeInfo = { + node : e, + ev : ev, + cb : cb + }; + }); + } + + if (tinymce.isOpera) { + t.onClick.add(function(ed, e) { + Event.prevent(e); + }); + } + + // Add custom undo/redo handlers + if (s.custom_undo_redo) { + function addUndo() { + t.undoManager.typing = false; + t.undoManager.add(); + }; + + dom.bind(t.getDoc(), 'focusout', function(e) { + if (!t.removed && t.undoManager.typing) + addUndo(); + }); + + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + + t.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; + + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey) + addUndo(); + }); + + t.onKeyDown.add(function(ed, e) { + var keyCode = e.keyCode, sel; + + if (keyCode == 8) { + sel = t.getDoc().selection; + + // Fix IE control + backspace browser bug + if (sel && sel.createRange && sel.createRange().item) { + t.undoManager.beforeChange(); + ed.dom.remove(sel.createRange().item(0)); + addUndo(); + + return Event.cancel(e); + } + } + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + + if (t.undoManager.typing) + addUndo(); + + return; + } + + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + }); + + t.onMouseDown.add(function() { + if (t.undoManager.typing) + addUndo(); + }); + } + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + + if (target !== t.getBody()) { + t.dom.setAttrib(target, "style", null); + + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + } + }; + } + + function isSelectionAcrossElements() { + var s = t.selection; + + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } + + t.onKeyPress.add(function(ed, e) { + var applyAttributes; + + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); + + return Event.cancel(e); + } + }); + + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; + + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); + + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); + } + }); + } + }, + + _refreshContentEditable : function() { + var self = this, body, parent; + + // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again + if (self._isHidden()) { + body = self.getBody(); + parent = body.parentNode; + + parent.removeChild(body); + parent.appendChild(body); + + body.focus(); + } + }, + + _isHidden : function() { + var s; + + if (!isGecko) + return 0; + + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); + } + }); +})(tinymce); + +(function(tinymce) { + // Added for compression purposes + var each = tinymce.each, undefined, TRUE = true, FALSE = false; + + tinymce.EditorCommands = function(editor) { + var dom = editor.dom, + selection = editor.selection, + commands = {state: {}, exec : {}, value : {}}, + settings = editor.settings, + formatter = editor.formatter, + bookmark; + + function execCommand(command, ui, value) { + var func; + + command = command.toLowerCase(); + if (func = commands.exec[command]) { + func(command, ui, value); + return TRUE; + } + + return FALSE; + }; + + function queryCommandState(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.state[command]) + return func(command); + + return -1; + }; + + function queryCommandValue(command) { + var func; + + command = command.toLowerCase(); + if (func = commands.value[command]) + return func(command); + + return FALSE; + }; + + function addCommands(command_list, type) { + type = type || 'exec'; + + each(command_list, function(callback, command) { + each(command.toLowerCase().split(','), function(command) { + commands[type][command] = callback; + }); + }); + }; + + // Expose public methods + tinymce.extend(this, { + execCommand : execCommand, + queryCommandState : queryCommandState, + queryCommandValue : queryCommandValue, + addCommands : addCommands + }); + + // Private methods + + function execNativeCommand(command, ui, value) { + if (ui === undefined) + ui = FALSE; + + if (value === undefined) + value = null; + + return editor.getDoc().execCommand(command, ui, value); + }; + + function isFormatMatch(name) { + return formatter.match(name); + }; + + function toggleFormat(name, value) { + formatter.toggle(name, value ? {value : value} : undefined); + }; + + function storeSelection(type) { + bookmark = selection.getBookmark(type); + }; + + function restoreSelection() { + selection.moveToBookmark(bookmark); + }; + + // Add execCommand overrides + addCommands({ + // Ignore these, added for compatibility + 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, + + // Add undo manager logic + 'mceEndUndoLevel,mceAddUndoLevel' : function() { + editor.undoManager.add(); + }, + + 'Cut,Copy,Paste' : function(command) { + var doc = editor.getDoc(), failed; + + // Try executing the native command + try { + execNativeCommand(command); + } catch (ex) { + // Command failed + failed = TRUE; + } + + // Present alert message about clipboard access not being available + if (failed || !doc.queryCommandSupported(command)) { + if (tinymce.isGecko) { + editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { + if (state) + open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); + }); + } else + editor.windowManager.alert(editor.getLang('clipboard_no_support')); + } + }, + + // Override unlink command + unlink : function(command) { + if (selection.isCollapsed()) + selection.select(selection.getNode()); + + execNativeCommand(command); + selection.collapse(FALSE); + }, + + // Override justify commands to use the text formatter engine + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + var align = command.substring(7); + + // Remove all other alignments first + each('left,center,right,full'.split(','), function(name) { + if (align != name) + formatter.remove('align' + name); + }); + + toggleFormat('align' + align); + execCommand('mceRepaint'); + }, + + // Override list commands to fix WebKit bug + 'InsertUnorderedList,InsertOrderedList' : function(command) { + var listElm, listParent; + + execNativeCommand(command); + + // WebKit produces lists within block elements so we need to split them + // we will replace the native list creation logic to custom logic later on + // TODO: Remove this when the list creation logic is removed + listElm = dom.getParent(selection.getNode(), 'ol,ul'); + if (listElm) { + listParent = listElm.parentNode; + + // If list is within a text block then split that block + if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { + storeSelection(); + dom.split(listParent, listElm); + restoreSelection(); + } + } + }, + + // Override commands to use the text formatter engine + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + toggleFormat(command); + }, + + // Override commands to use the text formatter engine + 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { + toggleFormat(command, value); + }, + + FontSize : function(command, ui, value) { + var fontClasses, fontSizes; + + // Convert font size 1-7 to styles + if (value >= 1 && value <= 7) { + fontSizes = tinymce.explode(settings.font_size_style_values); + fontClasses = tinymce.explode(settings.font_size_classes); + + if (fontClasses) + value = fontClasses[value - 1] || value; + else + value = fontSizes[value - 1] || value; + } + + toggleFormat(command, value); + }, + + RemoveFormat : function(command) { + formatter.remove(command); + }, + + mceBlockQuote : function(command) { + toggleFormat('blockquote'); + }, + + FormatBlock : function(command, ui, value) { + return toggleFormat(value || 'p'); + }, + + mceCleanup : function() { + var bookmark = selection.getBookmark(); + + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); + + selection.moveToBookmark(bookmark); + }, + + mceRemoveNode : function(command, ui, value) { + var node = value || selection.getNode(); + + // Make sure that the body node isn't removed + if (node != editor.getBody()) { + storeSelection(); + editor.dom.remove(node, TRUE); + restoreSelection(); + } + }, + + mceSelectNodeDepth : function(command, ui, value) { + var counter = 0; + + dom.getParent(selection.getNode(), function(node) { + if (node.nodeType == 1 && counter++ == value) { + selection.select(node); + return FALSE; + } + }, editor.getBody()); + }, + + mceSelectNode : function(command, ui, value) { + selection.select(value); + }, + + mceInsertContent : function(command, ui, value) { + var parser, serializer, parentNode, rootNode, fragment, args, + marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; + + // Setup parser and serializer + parser = editor.parser; + serializer = new tinymce.html.Serializer({}, editor.schema); + bookmarkHtml = '\uFEFF'; + + // Run beforeSetContent handlers on the HTML to be inserted + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Replace the caret marker with a span bookmark element + value = value.replace(/\{\$caret\}/, bookmarkHtml); + + // Insert node maker where we will insert the new HTML and get it's parent + if (!selection.isCollapsed()) + editor.getDoc().execCommand('Delete', false, null); + + parentNode = selection.getNode(); + + // Parse the fragment within the context of the parent node + args = {context : parentNode.nodeName.toLowerCase()}; + fragment = parser.parse(value, args); + + // Move the caret to a more suitable location + node = fragment.lastChild; + if (node.attr('id') == 'mce_marker') { + marker = node; + + for (node = node.prev; node; node = node.walk(true)) { + if (node.type == 3 || !dom.isBlock(node.name)) { + node.parent.insert(marker, node, node.name === 'br'); + break; + } + } + } + + // If parser says valid we can insert the contents into that parent + if (!args.invalid) { + value = serializer.serialize(fragment); + + // Check if parent is empty or only has one BR element then set the innerHTML of that parent + node = parentNode.firstChild; + node2 = parentNode.lastChild; + if (!node || (node === node2 && node.nodeName === 'BR')) + dom.setHTML(parentNode, value); + else + selection.setContent(value); + } else { + // If the fragment was invalid within that context then we need + // to parse and process the parent it's inserted into + + // Insert bookmark node and get the parent + selection.setContent(bookmarkHtml); + parentNode = editor.selection.getNode(); + rootNode = editor.getBody(); + + // Opera will return the document node when selection is in root + if (parentNode.nodeType == 9) + parentNode = node = rootNode; + else + node = parentNode; + + // Find the ancestor just before the root element + while (node !== rootNode) { + parentNode = node; + node = node.parentNode; + } + + // Get the outer/inner HTML depending on if we are in the root and parser and serialize that + value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); + value = serializer.serialize( + parser.parse( + // Need to replace by using a function since $ in the contents would otherwise be a problem + value.replace(//i, function() { + return serializer.serialize(fragment); + }) + ) + ); + + // Set the inner/outer HTML depending on if we are in the root or not + if (parentNode == rootNode) + dom.setHTML(rootNode, value); + else + dom.setOuterHTML(parentNode, value); + } + + marker = dom.get('mce_marker'); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + nodeRect = dom.getRect(marker); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); + viewportBodyElement.scrollLeft = nodeRect.x; + viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; + } + + // Move selection before marker and remove it + rng = dom.createRng(); + + // If previous sibling is a text node set the selection to the end of that node + node = marker.previousSibling; + if (node && node.nodeType == 3) { + rng.setStart(node, node.nodeValue.length); + } else { + // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node + rng.setStartBefore(marker); + rng.setEndBefore(marker); + } + + // Remove the marker node and set the new range + dom.remove(marker); + selection.setRng(rng); + + // Dispatch after event and add any visual elements needed + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); + }, + + mceInsertRawHTML : function(command, ui, value) { + selection.setContent('tiny_mce_marker'); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); + }, + + mceSetContent : function(command, ui, value) { + editor.setContent(value); + }, + + 'Indent,Outdent' : function(command) { + var intentValue, indentUnit, value; + + // Setup indent level + intentValue = settings.indentation; + indentUnit = /[a-z%]+$/i.exec(intentValue); + intentValue = parseInt(intentValue); + + if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { + each(selection.getSelectedBlocks(), function(element) { + if (command == 'outdent') { + value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); + dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); + } else + dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); + }); + } else + execNativeCommand(command); + }, + + mceRepaint : function() { + var bookmark; + + if (tinymce.isGecko) { + try { + storeSelection(TRUE); + + if (selection.getSel()) + selection.getSel().selectAllChildren(editor.getBody()); + + selection.collapse(TRUE); + restoreSelection(); + } catch (ex) { + // Ignore + } + } + }, + + mceToggleFormat : function(command, ui, value) { + formatter.toggle(value); + }, + + InsertHorizontalRule : function() { + editor.execCommand('mceInsertContent', false, '
    '); + }, + + mceToggleVisualAid : function() { + editor.hasVisual = !editor.hasVisual; + editor.addVisual(); + }, + + mceReplaceContent : function(command, ui, value) { + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + }, + + mceInsertLink : function(command, ui, value) { + var anchor; + + if (typeof(value) == 'string') + value = {href : value}; + + anchor = dom.getParent(selection.getNode(), 'a'); + + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + + // Remove existing links if there could be child links or that the href isn't specified + if (!anchor || !value.href) { + formatter.remove('link'); + } + + // Apply new link to selection + if (value.href) { + formatter.apply('link', value, anchor); + } + }, + + selectAll : function() { + var root = dom.getRoot(), rng = dom.createRng(); + + rng.setStart(root, 0); + rng.setEnd(root, root.childNodes.length); + + editor.selection.setRng(rng); + } + }); + + // Add queryCommandState overrides + addCommands({ + // Override justify commands + 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { + return isFormatMatch('align' + command.substring(7)); + }, + + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { + return isFormatMatch(command); + }, + + mceBlockQuote : function() { + return isFormatMatch('blockquote'); + }, + + Outdent : function() { + var node; + + if (settings.inline_styles) { + if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + + if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) + return TRUE; + } + + return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); + }, + + 'InsertUnorderedList,InsertOrderedList' : function(command) { + return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); + } + }, 'state'); + + // Add queryCommandValue overrides + addCommands({ + 'FontSize,FontName' : function(command) { + var value = 0, parent; + + if (parent = dom.getParent(selection.getNode(), 'span')) { + if (command == 'fontsize') + value = parent.style.fontSize; + else + value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); + } + + return value; + } + }, 'value'); + + // Add undo manager logic + if (settings.custom_undo_redo) { + addCommands({ + Undo : function() { + editor.undoManager.undo(); + }, + + Redo : function() { + editor.undoManager.redo(); + } + }); + } + }; +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher; + + tinymce.UndoManager = function(editor) { + var self, index = 0, data = [], beforeBookmark; + + function getContent() { + return tinymce.trim(editor.getContent({format : 'raw', no_events : 1})); + }; + + return self = { + typing : false, + + onAdd : new Dispatcher(self), + + onUndo : new Dispatcher(self), + + onRedo : new Dispatcher(self), + + beforeChange : function() { + beforeBookmark = editor.selection.getBookmark(2, true); + }, + + add : function(level) { + var i, settings = editor.settings, lastLevel; + + level = level || {}; + level.content = getContent(); + + // Add undo level if needed + lastLevel = data[index]; + if (lastLevel && lastLevel.content == level.content) + return null; + + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = beforeBookmark; + + // Time to compress + if (settings.custom_undo_redo_levels) { + if (data.length > settings.custom_undo_redo_levels) { + for (i = 0; i < data.length - 1; i++) + data[i] = data[i + 1]; + + data.length--; + index = data.length; + } + } + + // Get a non intrusive normalized bookmark + level.bookmark = editor.selection.getBookmark(2, true); + + // Crop array if needed + if (index < data.length - 1) + data.length = index + 1; + + data.push(level); + index = data.length - 1; + + self.onAdd.dispatch(self, level); + editor.isNotDirty = 0; + + return level; + }, + + undo : function() { + var level, i; + + if (self.typing) { + self.add(); + self.typing = false; + } + + if (index > 0) { + level = data[--index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.beforeBookmark); + + self.onUndo.dispatch(self, level); + } + + return level; + }, + + redo : function() { + var level; + + if (index < data.length - 1) { + level = data[++index]; + + editor.setContent(level.content, {format : 'raw'}); + editor.selection.moveToBookmark(level.bookmark); + + self.onRedo.dispatch(self, level); + } + + return level; + }, + + clear : function() { + data = []; + index = 0; + self.typing = false; + }, + + hasUndo : function() { + return index > 0 || this.typing; + }, + + hasRedo : function() { + return index < data.length - 1 && !this.typing; + } + }; + }; +})(tinymce); + +(function(tinymce) { + // Shorten names + var Event = tinymce.dom.Event, + isIE = tinymce.isIE, + isGecko = tinymce.isGecko, + isOpera = tinymce.isOpera, + each = tinymce.each, + extend = tinymce.extend, + TRUE = true, + FALSE = false; + + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + + // Checks if the selection/caret is at the end of the specified block element + function isAtEnd(rng, par) { + var rng2 = par.ownerDocument.createRange(); + + rng2.setStart(rng.endContainer, rng.endOffset); + rng2.setEndAfter(par); + + // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element + return rng2.cloneContents().textContent.length == 0; + }; + + function splitList(selection, dom, li) { + var listBlock, block; + + if (dom.isEmpty(li)) { + listBlock = dom.getParent(li, 'ul,ol'); + + if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { + dom.split(listBlock, li); + block = dom.create('p', 0, '
    '); + dom.replace(block, li); + selection.select(block, 1); + } + + return FALSE; + } + + return TRUE; + }; + + tinymce.create('tinymce.ForceBlocks', { + ForceBlocks : function(ed) { + var t = this, s = ed.settings, elm; + + t.editor = ed; + t.dom = ed.dom; + elm = (s.forced_root_block || 'p').toLowerCase(); + s.element = elm.toUpperCase(); + + ed.onPreInit.add(t.setup, t); + }, + + setup : function() { + var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements(); + + // Force root blocks + if (s.forced_root_block) { + function addRootBlocks() { + var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF; + + if (!node || node.nodeType !== 1) + return; + + // Check if node is wrapped in block + while (node != rootNode) { + if (blockElements[node.nodeName]) + return; + + node = node.parentNode; + } + + // Get current selection + rng = selection.getRng(); + if (rng.setStart) { + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + } else { + // Force control range into text range + if (rng.item) { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rng.item(0)); + } + + tmpRng = rng.duplicate(); + tmpRng.collapse(true); + startOffset = tmpRng.move('character', offset) * -1; + + if (!tmpRng.collapsed) { + tmpRng = rng.duplicate(); + tmpRng.collapse(false); + endOffset = (tmpRng.move('character', offset) * -1) - startOffset; + } + } + + // Wrap non block elements and text nodes + for (node = rootNode.firstChild; node; node) { + if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { + if (!rootBlockNode) { + rootBlockNode = dom.create(s.forced_root_block); + node.parentNode.insertBefore(rootBlockNode, node); + } + + tempNode = node; + node = node.nextSibling; + rootBlockNode.appendChild(tempNode); + } else { + rootBlockNode = null; + node = node.nextSibling; + } + } + + if (rng.setStart) { + rng.setStart(startContainer, startOffset); + rng.setEnd(endContainer, endOffset); + selection.setRng(rng); + } else { + try { + rng = ed.getDoc().body.createTextRange(); + rng.moveToElementText(rootNode); + rng.collapse(true); + rng.moveStart('character', startOffset); + + if (endOffset > 0) + rng.moveEnd('character', endOffset); + + rng.select(); + } catch (ex) { + // Ignore + } + } + + ed.nodeChanged(); + }; + + ed.onKeyUp.add(addRootBlocks); + ed.onClick.add(addRootBlocks); + } + + if (s.force_br_newlines) { + // Force IE to produce BRs on enter + if (isIE) { + ed.onKeyPress.add(function(ed, e) { + var n; + + if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') { + selection.setContent('
    ', {format : 'raw'}); + n = dom.get('__'); + n.removeAttribute('id'); + selection.select(n); + selection.collapse(); + return Event.cancel(e); + } + }); + } + } + + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + selection.collapse(true); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } + + if (isGecko) { + ed.onKeyDown.add(function(ed, e) { + if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) + t.backspaceDelete(e, e.keyCode == 8); + }); + } + } + + // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973 + if (tinymce.isWebKit) { + function insertBr(ed) { + var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h; + + // Insert BR element + rng.insertNode(br = dom.create('br')); + + // Place caret after BR + rng.setStartAfter(br); + rng.setEndAfter(br); + selection.setRng(rng); + + // Could not place caret after BR then insert an nbsp entity and move the caret + if (selection.getSel().focusNode == br.previousSibling) { + selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br)); + selection.collapse(TRUE); + } + + // Create a temporary DIV after the BR and get the position as it + // seems like getPos() returns 0 for text nodes and BR elements. + dom.insertAfter(div, br); + divYPos = dom.getPos(div).y; + dom.remove(div); + + // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117 + if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port. + ed.getWin().scrollTo(0, divYPos); + }; + + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) { + insertBr(ed); + Event.cancel(e); + } + }); + } + + // IE specific fixes + if (isIE) { + // Replaces IE:s auto generated paragraphs with the specified element name + if (s.element != 'P') { + ed.onKeyPress.add(function(ed, e) { + t.lastElm = selection.getNode().nodeName; + }); + + ed.onKeyUp.add(function(ed, e) { + var bl, n = selection.getNode(), b = ed.getBody(); + + if (b.childNodes.length === 1 && n.nodeName == 'P') { + n = dom.rename(n, s.element); + selection.select(n); + selection.collapse(); + ed.nodeChanged(); + } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') { + bl = dom.getParent(n, 'p'); + + if (bl) { + dom.rename(bl, s.element); + ed.nodeChanged(); + } + } + }); + } + } + }, + + getParentBlock : function(n) { + var d = this.dom; + + return d.getParent(n, d.isBlock); + }, + + insertPara : function(e) { + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + + ed.undoManager.beforeChange(); + + // If root blocks are forced then use Operas default behavior since it's really good +// Removed due to bug: #1853816 +// if (se.forced_root_block && isOpera) +// return TRUE; + + // Setup before range + rb = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + rb.setStart(s.anchorNode, s.anchorOffset); + rb.collapse(TRUE); + + // Setup after range + ra = d.createRange(); + + // If is before the first block element and in body, then move it into first block element + ra.setStart(s.focusNode, s.focusOffset); + ra.collapse(TRUE); + + // Setup start/end points + dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0; + sn = dir ? s.anchorNode : s.focusNode; + so = dir ? s.anchorOffset : s.focusOffset; + en = dir ? s.focusNode : s.anchorNode; + eo = dir ? s.focusOffset : s.anchorOffset; + + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + if (sn.firstChild.nodeName == 'BR') + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + if (sn.childNodes.length == 0) { + ed.dom.add(sn, se.element, null, '
    '); + aft = ed.dom.add(sn, se.element, null, '
    '); + } else { + n = sn.innerHTML; + sn.innerHTML = ''; + ed.dom.add(sn, se.element, null, n); + aft = ed.dom.add(sn, se.element, null, '
    '); + } + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return FALSE; + } + + // If the caret is in an invalid location in FF we need to move it into the first block + if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { + sn = en = sn.firstChild; + so = eo = 0; + rb = d.createRange(); + rb.setStart(sn, 0); + ra = d.createRange(); + ra.setStart(en, 0); + } + + // If the body is totally empty add a BR element this might happen on webkit + if (!d.body.hasChildNodes()) { + d.body.appendChild(dom.create('br')); + } + + // Never use body as start or end node + sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + sn = sn.nodeName == "BODY" ? sn.firstChild : sn; + en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes + en = en.nodeName == "BODY" ? en.firstChild : en; + + // Get start and end blocks + sb = t.getParentBlock(sn); + eb = t.getParentBlock(en); + bn = sb ? sb.nodeName : se.element; // Get block name to create + + // Return inside list use default browser behavior + if (n = t.dom.getParent(sb, 'li,pre')) { + if (n.nodeName == 'LI') + return splitList(ed.selection, t.dom, n); + + return TRUE; + } + + // If caption or absolute layers then always generate new blocks within + if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + sb = null; + } + + // If caption or absolute layers then always generate new blocks within + if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) { + bn = se.element; + eb = null; + } + + // Use P instead + if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) { + bn = se.element; + sb = eb = null; + } + + // Setup new before and after blocks + bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn); + aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn); + + // Remove id from after clone + aft.removeAttribute('id'); + + // Is header and cursor is at the end, then force paragraph under + if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb)) + aft = ed.dom.create(se.element); + + // Find start chop node + n = sc = sn; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + sc = n; + } while ((n = n.previousSibling ? n.previousSibling : n.parentNode)); + + // Find end chop node + n = ec = en; + do { + if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName)) + break; + + ec = n; + } while ((n = n.nextSibling ? n.nextSibling : n.parentNode)); + + // Place first chop part into before block element + if (sc.nodeName == bn) + rb.setStart(sc, 0); + else + rb.setStartBefore(sc); + + rb.setEnd(sn, so); + bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Place secnd chop part within new block element + try { + ra.setEndAfter(ec); + } catch(ex) { + //console.debug(s.focusNode, s.focusOffset); + } + + ra.setStart(en, eo); + aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari + + // Create range around everything + r = d.createRange(); + if (!sc.previousSibling && sc.parentNode.nodeName == bn) { + r.setStartBefore(sc.parentNode); + } else { + if (rb.startContainer.nodeName == bn && rb.startOffset == 0) + r.setStartBefore(rb.startContainer); + else + r.setStart(rb.startContainer, rb.startOffset); + } + + if (!ec.nextSibling && ec.parentNode.nodeName == bn) + r.setEndAfter(ec.parentNode); + else + r.setEnd(ra.endContainer, ra.endOffset); + + // Delete and replace it with new block elements + r.deleteContents(); + + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + + // Never wrap blocks in blocks + if (bef.firstChild && bef.firstChild.nodeName == bn) + bef.innerHTML = bef.firstChild.innerHTML; + + if (aft.firstChild && aft.firstChild.nodeName == bn) + aft.innerHTML = aft.firstChild.innerHTML; + + function appendStyles(e, en) { + var nl = [], nn, n, i; + + e.innerHTML = ''; + + // Make clones of style elements + if (se.keep_styles) { + n = en; + do { + // We only want style specific elements + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) { + nn = n.cloneNode(FALSE); + dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique + nl.push(nn); + } + } while (n = n.parentNode); + } + + // Append style elements to aft + if (nl.length > 0) { + for (i = nl.length - 1, nn = e; i >= 0; i--) + nn = nn.appendChild(nl[i]); + + // Padd most inner style element + nl[0].innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + return nl[0]; // Move caret to most inner element + } else + e.innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there + }; + + // Padd empty blocks + if (dom.isEmpty(bef)) + appendStyles(bef, sn); + + // Fill empty afterblook with current style + if (dom.isEmpty(aft)) + car = appendStyles(aft, en); + + // Opera needs this one backwards for older versions + if (isOpera && parseFloat(opera.version()) < 9.5) { + r.insertNode(bef); + r.insertNode(aft); + } else { + r.insertNode(aft); + r.insertNode(bef); + } + + // Normalize + aft.normalize(); + bef.normalize(); + + // Move cursor and scroll into view + ed.selection.select(aft, true); + ed.selection.collapse(true); + + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + //ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + 25 > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ + } + + ed.undoManager.add(); + + return FALSE; + }, + + backspaceDelete : function(e, bs) { + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } + + // The caret sometimes gets stuck in Gecko if you delete empty paragraphs + // This workaround removes the element by hand and moves the caret to the previous element + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + + if (n) { + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + + return Event.cancel(e); + } + } + } + } + }); +})(tinymce); + +(function(tinymce) { + // Shorten names + var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; + + tinymce.create('tinymce.ControlManager', { + ControlManager : function(ed, s) { + var t = this, i; + + s = s || {}; + t.editor = ed; + t.controls = {}; + t.onAdd = new tinymce.util.Dispatcher(t); + t.onPostRender = new tinymce.util.Dispatcher(t); + t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; + + t.onPostRender.add(function() { + each(t.controls, function(c) { + c.postRender(); + }); + }); + }, + + get : function(id) { + return this.controls[this.prefix + id] || this.controls[id]; + }, + + setActive : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setActive(s); + + return c; + }, + + setDisabled : function(id, s) { + var c = null; + + if (c = this.get(id)) + c.setDisabled(s); + + return c; + }, + + add : function(c) { + var t = this; + + if (c) { + t.controls[c.id] = c; + t.onAdd.dispatch(c, t); + } + + return c; + }, + + createControl : function(n) { + var c, t = this, ed = t.editor; + + each(ed.plugins, function(p) { + if (p.createControl) { + c = p.createControl(n, t); + + if (c) + return false; + } + }); + + switch (n) { + case "|": + case "separator": + return t.createSeparator(); + } + + if (!c && ed.buttons && (c = ed.buttons[n])) + return t.createButton(n, c); + + return t.add(c); + }, + + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; + + s = extend({ + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus + }, s); + + s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; + if (v = ed.getParam('skin_variant')) + s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); + + id = t.prefix + id; + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); + c.onAddItem.add(function(c, o) { + var s = o.settings; + + s.title = ed.getLang(s.title, s.title); + + if (!s.onclick) { + s.onclick = function(v) { + if (s.cmd) + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + }); + + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + + function useNativeListForAccessibility(ed) { + return ed.settings.use_accessible_selects && !tinymce.isGecko + } + + if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) + c = new tinymce.ui.NativeListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s, ed); + } + + t.controls[id] = c; + + // Fix focus problem in Safari + if (tinymce.isWebKit) { + c.onPostRender.add(function(c, n) { + // Store bookmark on mousedown + Event.add(n, 'mousedown', function() { + ed.bookmark = ed.selection.getBookmark(1); + }); + + // Restore on focus, since it might be lost + Event.add(n, 'focus', function() { + ed.selection.moveToBookmark(ed.bookmark); + ed.bookmark = null; + }); + }); + } + + if (c.hideMenu) + ed.onMouseDown.add(c.hideMenu, c); + + return t.add(c); + }, + + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.label = ed.translate(s.label); + s.scope = s.scope || ed; + + if (!s.onclick && !s.menu_button) { + s.onclick = function() { + ed.execCommand(s.cmd, s.ui || false, s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + unavailable_prefix : ed.getLang('unavailable', ''), + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + + if (s.menu_button) { + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s, ed); + } + + return t.add(c); + }, + + createMenuButton : function(id, s, cc) { + s = s || {}; + s.menu_button = 1; + + return this.createButton(id, s, cc); + }, + + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + scope : s.scope, + control_manager : t + }, s); + + id = t.prefix + id; + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s, ed)); + ed.onMouseDown.add(c.hideMenu, c); + + return c; + }, + + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls, bm; + + if (t.get(id)) + return null; + + s.title = ed.translate(s.title); + s.scope = s.scope || ed; + + if (!s.onclick) { + s.onclick = function(v) { + if (tinymce.isIE) + bm = ed.selection.getBookmark(1); + + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + if (!s.onselect) { + s.onselect = function(v) { + ed.execCommand(s.cmd, s.ui || false, v || s.value); + }; + } + + s = extend({ + title : s.title, + 'class' : 'mce_' + id, + 'menu_class' : ed.getParam('skin') + 'Skin', + scope : s.scope, + more_colors_title : ed.getLang('more_colors') + }, s); + + id = t.prefix + id; + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s, ed); + ed.onMouseDown.add(c.hideMenu, c); + + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + + // Fix for bug #1897785, #1898007 + if (tinymce.isIE) { + c.onShowMenu.add(function() { + // IE 8 needs focus in order to store away a range with the current collapsed caret location + ed.focus(); + bm = ed.selection.getBookmark(1); + }); + + c.onHideMenu.add(function() { + if (bm) { + ed.selection.moveToBookmark(bm); + bm = 0; + } + }); + } + + return t.add(c); + }, + + createToolbar : function(id, s, cc) { + var c, t = this, cls; + + id = t.prefix + id; + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, + + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; + } + }); +})(tinymce); + +(function(tinymce) { + var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; + + tinymce.create('tinymce.WindowManager', { + WindowManager : function(ed) { + var t = this; + + t.editor = ed; + t.onOpen = new Dispatcher(t); + t.onClose = new Dispatcher(t); + t.params = {}; + t.features = {}; + }, + + open : function(s, p) { + var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; + + // Default some options + s = s || {}; + p = p || {}; + sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window + sh = isOpera ? vp.h : screen.height; + s.name = s.name || 'mc_' + new Date().getTime(); + s.width = parseInt(s.width || 320); + s.height = parseInt(s.height || 240); + s.resizable = true; + s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); + s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); + p.inline = false; + p.mce_width = s.width; + p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; + + if (mo) { + if (isIE) { + s.center = true; + s.help = false; + s.dialogWidth = s.width + 'px'; + s.dialogHeight = s.height + 'px'; + s.scroll = s.scrollbars || false; + } + } + + // Build features string + each(s, function(v, k) { + if (tinymce.is(v, 'boolean')) + v = v ? 'yes' : 'no'; + + if (!/^(name|url)$/.test(k)) { + if (isIE && mo) + f += (f ? ';' : '') + k + ':' + v; + else + f += (f ? ',' : '') + k + '=' + v; + } + }); + + t.features = s; + t.params = p; + t.onOpen.dispatch(t, s, p); + + u = s.url || s.file; + u = tinymce._addVer(u); + + try { + if (isIE && mo) { + w = 1; + window.showModalDialog(u, window, f); + } else + w = window.open(u, s.name, f); + } catch (ex) { + // Ignore + } + + if (!w) + alert(t.editor.getLang('popup_blocked')); + }, + + close : function(w) { + w.close(); + this.onClose.dispatch(this); + }, + + createInstance : function(cl, a, b, c, d, e) { + var f = tinymce.resolve(cl); + + return new f(a, b, c, d, e); + }, + + confirm : function(t, cb, s, w) { + w = w || window; + + cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); + }, + + alert : function(tx, cb, s, w) { + var t = this; + + w = w || window; + w.alert(t._decode(t.editor.getLang(tx, tx))); + + if (cb) + cb.call(s || t); + }, + + resizeBy : function(dw, dh, win) { + win.resizeBy(dw, dh); + }, + + // Internal functions + + _decode : function(s) { + return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); + } + }); +}(tinymce)); +(function(tinymce) { + tinymce.Formatter = function(ed) { + var formats = {}, + each = tinymce.each, + dom = ed.dom, + selection = ed.selection, + TreeWalker = tinymce.dom.TreeWalker, + rangeUtils = new tinymce.dom.RangeUtils(dom), + isValid = ed.schema.isValidChild, + isBlock = dom.isBlock, + forcedRootBlock = ed.settings.forced_root_block, + nodeIndex = dom.nodeIndex, + INVISIBLE_CHAR = '\uFEFF', + MCE_ATTR_RE = /^(src|href|style)$/, + FALSE = false, + TRUE = true, + undefined; + + function isArray(obj) { + return obj instanceof Array; + }; + + function getParents(node, selector) { + return dom.getParents(node, selector, dom.getRoot()); + }; + + function isCaretNode(node) { + return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline'); + }; + + // Public functions + + function get(name) { + return name ? formats[name] : formats; + }; + + function register(name, format) { + if (name) { + if (typeof(name) !== 'string') { + each(name, function(format, name) { + register(name, format); + }); + } else { + // Force format into array and add it to internal collection + format = format.length ? format : [format]; + + each(format, function(format) { + // Set deep to false by default on selector formats this to avoid removing + // alignment on images inside paragraphs when alignment is changed on paragraphs + if (format.deep === undefined) + format.deep = !format.selector; + + // Default to true + if (format.split === undefined) + format.split = !format.selector || format.inline; + + // Default to true + if (format.remove === undefined && format.selector && !format.inline) + format.remove = 'none'; + + // Mark format as a mixed format inline + block level + if (format.selector && format.inline) { + format.mixed = true; + format.block_expand = true; + } + + // Split classes if needed + if (typeof(format.classes) === 'string') + format.classes = format.classes.split(/\s+/); + }); + + formats[name] = format; + } + } + }; + + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + + function apply(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); + + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node; + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1 || container.nodeValue === "") { + container = container.nodeType == 1 ? container.childNodes[offset] : container; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } + } + } + } + + return rng; + }; + + function setElementFormat(elm, fmt) { + fmt = fmt || format; + + if (elm) { + if (fmt.onformat) { + fmt.onformat(elm, fmt, vars, node); + } + + each(fmt.styles, function(value, name) { + dom.setStyle(elm, name, replaceVars(value, vars)); + }); + + each(fmt.attributes, function(value, name) { + dom.setAttrib(elm, name, replaceVars(value, vars)); + }); + + each(fmt.classes, function(value) { + value = replaceVars(value, vars); + + if (!dom.hasClass(elm, value)) + dom.addClass(elm, value); + }); + } + }; + function adjustSelectionToVisibleSelection() { + function findSelectionEnd(start, end) { + var walker = new TreeWalker(end); + for (node = walker.current(); node; node = walker.prev()) { + if (node.childNodes.length > 1 || node == start) { + return node; + } + } + }; + + // Adjust selection so that a end container with a end offset of zero is not included in the selection + // as this isn't visible to the user. + var rng = ed.selection.getRng(); + var start = rng.startContainer; + var end = rng.endContainer; + + if (start != end && rng.endOffset == 0) { + var newEnd = findSelectionEnd(start, end); + var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; + + rng.setEnd(newEnd, endOffset); + } + + return rng; + } + + function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ + var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; + + // find the index of the first child list. + each(node.childNodes, function(n, index) { + if (n.nodeName === "UL" || n.nodeName === "OL") { + listIndex = index; + list = n; + return false; + } + }); + + // get the index of the bookmarks + each(node.childNodes, function(n, index) { + if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { + if (n.id == bookmark.id + "_start") { + startIndex = index; + } else if (n.id == bookmark.id + "_end") { + endIndex = index; + } + } + }); + + // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally + if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { + each(tinymce.grep(node.childNodes), process); + return 0; + } else { + currentWrapElm = wrapElm.cloneNode(FALSE); + + // create a list of the nodes on the same side of the list as the selection + each(tinymce.grep(node.childNodes), function(n, index) { + if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { + nodes.push(n); + n.parentNode.removeChild(n); + } + }); + + // insert the wrapping element either before or after the list. + if (startIndex < listIndex) { + node.insertBefore(currentWrapElm, list); + } else if (startIndex > listIndex) { + node.insertBefore(currentWrapElm, list.nextSibling); + } + + // add the new nodes to the list. + newWrappers.push(currentWrapElm); + + each(nodes, function(node) { + currentWrapElm.appendChild(node); + }); + + return currentWrapElm; + } + }; + + function applyRngStyle(rng, bookmark, node_specific) { + var newWrappers = [], wrapName, wrapElm; + + // Setup wrapper element + wrapName = format.inline || format.block; + wrapElm = dom.create(wrapName); + setElementFormat(wrapElm); + + rangeUtils.walk(rng, function(nodes) { + var currentWrapElm; + + function process(node) { + var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found; + + // Stop wrapping on br elements + if (isEq(nodeName, 'br')) { + currentWrapElm = 0; + + // Remove any br elements when we wrap things + if (format.block) + dom.remove(node); + + return; + } + + // If node is wrapper type + if (format.wrapper && matchNode(node, name, vars)) { + currentWrapElm = 0; + return; + } + + // Can we rename the block + if (format.block && !format.wrapper && isTextBlock(nodeName)) { + node = dom.rename(node, wrapName); + setElementFormat(node); + newWrappers.push(node); + currentWrapElm = 0; + return; + } + + // Handle selector patterns + if (format.selector) { + // Look for matching formats + each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + + if (dom.is(node, format.selector) && !isCaretNode(node)) { + setElementFormat(node, format); + found = true; + } + }); + + // Continue processing if a selector match wasn't found and a inline element is defined + if (!format.inline || found) { + currentWrapElm = 0; + return; + } + } + + // Is it valid to wrap this item + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && node.id !== '_mce_caret') { + // Start wrapping + if (!currentWrapElm) { + // Wrap the node + currentWrapElm = wrapElm.cloneNode(FALSE); + node.parentNode.insertBefore(currentWrapElm, node); + newWrappers.push(currentWrapElm); + } + + currentWrapElm.appendChild(node); + } else if (nodeName == 'li' && bookmark) { + // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. + currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); + } else { + // Start a new wrapper for possible children + currentWrapElm = 0; + + each(tinymce.grep(node.childNodes), process); + + // End the last wrapper + currentWrapElm = 0; + } + }; + + // Process siblings from range + each(nodes, process); + }); + + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + + // Cleanup + each(newWrappers, function(node) { + var childCount; + + function getChildCount(node) { + var count = 0; + + each(node.childNodes, function(node) { + if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) + count++; + }); + + return count; + }; + + function mergeStyles(node) { + var child, clone; + + each(node.childNodes, function(node) { + if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { + child = node; + return FALSE; // break loop + } + }); + + // If child was found and of the same type as the current node + if (child && matchName(child, format)) { + clone = child.cloneNode(FALSE); + setElementFormat(clone); + + dom.replace(clone, node, TRUE); + dom.remove(child, 1); + } + + return clone || node; + }; + + childCount = getChildCount(node); + + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { + dom.remove(node, 1); + return; + } + + if (format.inline || format.wrapper) { + // Merges the current node with it's children of similar type to reduce the number of elements + if (!format.exact && childCount === 1) + node = mergeStyles(node); + + // Remove/merge children + each(formatList, function(format) { + // Merge all children of similar type will move styles from child to parent + // this: text + // will become: text + each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + + removeFormat(format, vars, child, format.exact ? child : null); + }); + }); + + // Remove child if direct parent is of same type + if (matchNode(node.parentNode, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + + // Look for parent with similar style format + if (format.merge_with_parents) { + dom.getParent(node.parentNode, function(parent) { + if (matchNode(parent, name, vars)) { + dom.remove(node, 1); + node = 0; + return TRUE; + } + }); + } + + // Merge next and previous siblings if they are similar texttext becomes texttext + if (node && format.merge_siblings !== false) { + node = mergeSiblings(getNonWhiteSpaceSibling(node), node); + node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); + } + } + }); + }; + + if (format) { + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + applyRngStyle(expandRng(rng, formatList), null, true); + } else { + applyRngStyle(node, null, true); + } + } else { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + + // Apply formatting to selection + ed.selection.setRng(adjustSelectionToVisibleSelection()); + bookmark = selection.getBookmark(); + applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + + selection.moveToBookmark(bookmark); + selection.setRng(moveStart(selection.getRng(TRUE))); + ed.nodeChanged(); + } else + performCaretAction('apply', name, vars); + } + } + }; + + function remove(name, vars, node) { + var formatList = get(name), format = formatList[0], bookmark, i, rng; + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + + // Merges the styles for each node + function process(node) { + var children, i, l; + + // Grab the children first since the nodelist might be changed + children = tinymce.grep(node.childNodes); + + // Process current node + for (i = 0, l = formatList.length; i < l; i++) { + if (removeFormat(formatList[i], vars, node, node)) + break; + } + + // Process the children + if (format.deep) { + for (i = 0, l = children.length; i < l; i++) + process(children[i]); + } + }; + + function findFormatRoot(container) { + var formatRoot; + + // Find format root + each(getParents(container.parentNode).reverse(), function(parent) { + var format; + + // Find format root element + if (!formatRoot && parent.id != '_start' && parent.id != '_end') { + // Is the node matching the format we are looking for + format = matchNode(parent, name, vars); + if (format && format.split !== false) + formatRoot = parent; + } + }); + + return formatRoot; + }; + + function wrapAndSplit(format_root, container, target, split) { + var parent, clone, lastClone, firstClone, i, formatRootParent; + + // Format root found then clone formats and split it + if (format_root) { + formatRootParent = format_root.parentNode; + + for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { + clone = parent.cloneNode(FALSE); + + for (i = 0; i < formatList.length; i++) { + if (removeFormat(formatList[i], vars, clone, clone)) { + clone = 0; + break; + } + } + + // Build wrapper node + if (clone) { + if (lastClone) + clone.appendChild(lastClone); + + if (!firstClone) + firstClone = clone; + + lastClone = clone; + } + } + + // Never split block elements if the format is mixed + if (split && (!format.mixed || !isBlock(format_root))) + container = dom.split(format_root, container); + + // Wrap container in cloned formats + if (lastClone) { + target.parentNode.insertBefore(lastClone, target); + firstClone.appendChild(target); + } + } + + return container; + }; + + function splitToFormatRoot(container) { + return wrapAndSplit(findFormatRoot(container), container, container, true); + }; + + function unwrap(start) { + var node = dom.get(start ? '_start' : '_end'), + out = node[start ? 'firstChild' : 'lastChild']; + + // If the end is placed within the start the result will be removed + // So this checks if the out node is a bookmark node if it is it + // checks for another more suitable node + if (isBookmarkNode(out)) + out = out[start ? 'firstChild' : 'lastChild']; + + dom.remove(node, true); + + return out; + }; + + function removeRngStyle(rng) { + var startContainer, endContainer; + + rng = expandRng(rng, formatList, TRUE); + + if (format.split) { + startContainer = getContainer(rng, TRUE); + endContainer = getContainer(rng); + + if (startContainer != endContainer) { + // Wrap start/end nodes in span element since these might be cloned/moved + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); + + // Split start/end + splitToFormatRoot(startContainer); + splitToFormatRoot(endContainer); + + // Unwrap start/end to get real elements again + startContainer = unwrap(TRUE); + endContainer = unwrap(); + } else + startContainer = endContainer = splitToFormatRoot(startContainer); + + // Update range positions since they might have changed after the split operations + rng.startContainer = startContainer.parentNode; + rng.startOffset = nodeIndex(startContainer); + rng.endContainer = endContainer.parentNode; + rng.endOffset = nodeIndex(endContainer) + 1; + } + + // Remove items between start/end + rangeUtils.walk(rng, function(nodes) { + each(nodes, function(node) { + process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } + }); + }); + }; + + // Handle node + if (node) { + if (node.nodeType) { + rng = dom.createRng(); + rng.setStartBefore(node); + rng.setEndAfter(node); + removeRngStyle(rng); + } else { + removeRngStyle(node); + } + + return; + } + + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + bookmark = selection.getBookmark(); + removeRngStyle(selection.getRng(TRUE)); + selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (format.inline && match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + + ed.nodeChanged(); + } else + performCaretAction('remove', name, vars); + + // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width + if (tinymce.isWebKit) { + ed.execCommand('mceCleanup'); + } + }; + + function toggle(name, vars, node) { + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) + remove(name, vars, node); + else + apply(name, vars, node); + }; + + function matchNode(node, name, vars, similar) { + var formatList = get(name), format, i, classes; + + function matchItems(node, format, item_name) { + var key, value, items = format[item_name], i; + + // Custom match + if (format.onmatch) { + return format.onmatch(node, format, item_name); + } + + // Check all items + if (items) { + // Non indexed object + if (items.length === undefined) { + for (key in items) { + if (items.hasOwnProperty(key)) { + if (item_name === 'attributes') + value = dom.getAttrib(node, key); + else + value = getStyle(node, key); + + if (similar && !value && !format.exact) + return; + + if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) + return; + } + } + } else { + // Only one match needed for indexed arrays + for (i = 0; i < items.length; i++) { + if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) + return format; + } + } + } + + return format; + }; + + if (formatList && node) { + // Check each format in list + for (i = 0; i < formatList.length; i++) { + format = formatList[i]; + + // Name name, attributes, styles and classes + if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { + // Match classes + if (classes = format.classes) { + for (i = 0; i < classes.length; i++) { + if (!dom.hasClass(node, classes[i])) + return; + } + } + + return format; + } + } + } + }; + + function match(name, vars, node) { + var startNode; + + function matchParents(node) { + // Find first node with similar format settings + node = dom.getParent(node, function(node) { + return !!matchNode(node, name, vars, true); + }); + + // Do an exact check on the similar format element + return matchNode(node, name, vars); + }; + + // Check specified node + if (node) + return matchParents(node); + + // Check selected node + node = selection.getNode(); + if (matchParents(node)) + return TRUE; + + // Check start node if it's different + startNode = selection.getStart(); + if (startNode != node) { + if (matchParents(startNode)) + return TRUE; + } + + return FALSE; + }; + + function matchAll(names, vars) { + var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; + + // Check start of selection for formats + startElement = selection.getStart(); + dom.getParent(startElement, function(node) { + var i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + + if (!checkedMap[name] && matchNode(node, name, vars)) { + checkedMap[name] = true; + matchedFormatNames.push(name); + } + } + }); + + return matchedFormatNames; + }; + + function canApply(name) { + var formatList = get(name), startNode, parents, i, x, selector; + + if (formatList) { + startNode = selection.getStart(); + parents = getParents(startNode); + + for (x = formatList.length - 1; x >= 0; x--) { + selector = formatList[x].selector; + + // Format is not selector based, then always return TRUE + if (!selector) + return TRUE; + + for (i = parents.length - 1; i >= 0; i--) { + if (dom.is(parents[i], selector)) + return TRUE; + } + } + } + + return FALSE; + }; + + // Expose to public + tinymce.extend(this, { + get : get, + register : register, + apply : apply, + remove : remove, + toggle : toggle, + match : match, + matchAll : matchAll, + matchNode : matchNode, + canApply : canApply + }); + + // Private functions + + function matchName(node, format) { + // Check for inline match + if (isEq(node, format.inline)) + return TRUE; + + // Check for block match + if (isEq(node, format.block)) + return TRUE; + + // Check for selector match + if (format.selector) + return dom.is(node, format.selector); + }; + + function isEq(str1, str2) { + str1 = str1 || ''; + str2 = str2 || ''; + + str1 = '' + (str1.nodeName || str1); + str2 = '' + (str2.nodeName || str2); + + return str1.toLowerCase() == str2.toLowerCase(); + }; + + function getStyle(node, name) { + var styleVal = dom.getStyle(node, name); + + // Force the format to hex + if (name == 'color' || name == 'backgroundColor') + styleVal = dom.toHex(styleVal); + + // Opera will return bold as 700 + if (name == 'fontWeight' && styleVal == 700) + styleVal = 'bold'; + + return '' + styleVal; + }; + + function replaceVars(value, vars) { + if (typeof(value) != "string") + value = value(vars); + else if (vars) { + value = value.replace(/%(\w+)/g, function(str, name) { + return vars[name] || str; + }); + } + + return value; + }; + + function isWhiteSpaceNode(node) { + return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); + }; + + function wrap(node, name, attrs) { + var wrapper = dom.create(name, attrs); + + node.parentNode.insertBefore(wrapper, node); + wrapper.appendChild(node); + + return wrapper; + }; + + function expandRng(rng, format, remove) { + var startContainer = rng.startContainer, + startOffset = rng.startOffset, + endContainer = rng.endContainer, + endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint; + + // This function walks up the tree if there is no siblings before/after the node + function findParentContainer(start) { + var container, parent, child, sibling, siblingName; + + container = parent = start ? startContainer : endContainer; + siblingName = start ? 'previousSibling' : 'nextSibling'; + root = dom.getRoot(); + + // If it's a text node and the offset is inside the text + if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { + if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { + return container; + } + } + + for (;;) { + // Stop expanding on block elements or root depending on format + if (parent == root || (!format[0].block_expand && isBlock(parent))) + return parent; + + // Walk left/right + for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { + if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { + return parent; + } + } + + // Check if we can move up are we at root level or body level + parent = parent.parentNode; + } + + return container; + }; + + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + + // If index based start position then resolve it + if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { + lastIdx = startContainer.childNodes.length - 1; + startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + // If index based end position then resolve it + if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { + lastIdx = endContainer.childNodes.length - 1; + endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; + + if (endContainer.nodeType == 3) + endOffset = endContainer.nodeValue.length; + } + + // Exclude bookmark nodes if possible + if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { + startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; + startContainer = startContainer.nextSibling || startContainer; + + if (startContainer.nodeType == 3) + startOffset = 0; + } + + if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { + endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; + endContainer = endContainer.previousSibling || endContainer; + + if (endContainer.nodeType == 3) + endOffset = endContainer.length; + } + + if (format[0].inline) { + if (rng.collapsed) { + function findWordEndPoint(container, offset, start) { + var walker, node, pos, lastTextNode; + + function findSpace(node, offset) { + var pos, pos2, str = node.nodeValue; + + if (typeof(offset) == "undefined") { + offset = start ? str.length : 0; + } + + if (start) { + pos = str.lastIndexOf(' ', offset); + pos2 = str.lastIndexOf('\u00a0', offset); + pos = pos > pos2 ? pos : pos2; + + // Include the space on remove to avoid tag soup + if (pos !== -1 && !remove) { + pos++; + } + } else { + pos = str.indexOf(' ', offset); + pos2 = str.indexOf('\u00a0', offset); + pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; + } + + return pos; + }; + + if (container.nodeType === 3) { + pos = findSpace(container, offset); + + if (pos !== -1) { + return {container : container, offset : pos}; + } + + lastTextNode = container; + } + + // Walk the nodes inside the block + walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); + while (node = walker[start ? 'prev' : 'next']()) { + if (node.nodeType === 3) { + lastTextNode = node; + pos = findSpace(node); + + if (pos !== -1) { + return {container : node, offset : pos}; + } + } else if (isBlock(node)) { + break; + } + } + + if (lastTextNode) { + if (start) { + offset = 0; + } else { + offset = lastTextNode.length; + } + + return {container: lastTextNode, offset: offset}; + } + } + + // Expand left to closest word boundery + endPoint = findWordEndPoint(startContainer, startOffset, true); + if (endPoint) { + startContainer = endPoint.container; + startOffset = endPoint.offset; + } + + // Expand right to closest word boundery + endPoint = findWordEndPoint(endContainer, endOffset); + if (endPoint) { + endContainer = endPoint.container; + endOffset = endPoint.offset; + } + } + + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + // TODO: Figure out why this is in here + //endContainer = leaf.node.previousSibling; + } + } + } + } + + // Move start/end point up the tree if the leaves are sharp and if we are in different containers + // Example * becomes !: !

    *texttext*

    ! + // This will reduce the number of wrapper elements that needs to be created + // Move start point up the tree + if (format[0].inline || format[0].block_expand) { + if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { + startContainer = findParentContainer(true); + } + + if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { + endContainer = findParentContainer(); + } + } + + // Expand start/end container to matching selector + if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { + function findSelectorEndPoint(container, sibling_name) { + var parents, i, y, curFormat; + + if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) + container = container[sibling_name]; + + parents = getParents(container); + for (i = 0; i < parents.length; i++) { + for (y = 0; y < format.length; y++) { + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) + return parents[i]; + } + } + + return container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); + endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); + } + + // Expand start/end container to matching block element or text node + if (format[0].block || format[0].selector) { + function findBlockEndPoint(container, sibling_name, sibling_name2) { + var node; + + // Expand to block of similar type + if (!format[0].wrapper) + node = dom.getParent(container, format[0].block); + + // Expand to first wrappable block element or any block element + if (!node) + node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); + + // Exclude inner lists from wrapping + if (node && format[0].wrapper) + node = getParents(node, 'ul,ol').reverse()[0] || node; + + // Didn't find a block element look for first/last wrappable element + if (!node) { + node = container; + + while (node[sibling_name] && !isBlock(node[sibling_name])) { + node = node[sibling_name]; + + // Break on BR but include it will be removed later on + // we can't remove it now since we need to check if it can be wrapped + if (isEq(node, 'br')) + break; + } + } + + return node || container; + }; + + // Find new startContainer/endContainer if there is better one + startContainer = findBlockEndPoint(startContainer, 'previousSibling'); + endContainer = findBlockEndPoint(endContainer, 'nextSibling'); + + // Non block element then try to expand up the leaf + if (format[0].block) { + if (!isBlock(startContainer)) + startContainer = findParentContainer(true); + + if (!isBlock(endContainer)) + endContainer = findParentContainer(); + } + } + + // Setup index for startContainer + if (startContainer.nodeType == 1) { + startOffset = nodeIndex(startContainer); + startContainer = startContainer.parentNode; + } + + // Setup index for endContainer + if (endContainer.nodeType == 1) { + endOffset = nodeIndex(endContainer) + 1; + endContainer = endContainer.parentNode; + } + + // Return new range like object + return { + startContainer : startContainer, + startOffset : startOffset, + endContainer : endContainer, + endOffset : endOffset + }; + } + + function removeFormat(format, vars, node, compare_node) { + var i, attrs, stylesModified; + + // Check if node matches format + if (!matchName(node, format)) + return FALSE; + + // Should we compare with format attribs and styles + if (format.remove != 'all') { + // Remove styles + each(format.styles, function(value, name) { + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(getStyle(compare_node, name), value)) + dom.setStyle(node, name, ''); + + stylesModified = 1; + }); + + // Remove style attribute if it's empty + if (stylesModified && dom.getAttrib(node, 'style') == '') { + node.removeAttribute('style'); + node.removeAttribute('data-mce-style'); + } + + // Remove attributes + each(format.attributes, function(value, name) { + var valueOut; + + value = replaceVars(value, vars); + + // Indexed array + if (typeof(name) === 'number') { + name = value; + compare_node = 0; + } + + if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { + // Keep internal classes + if (name == 'class') { + value = dom.getAttrib(node, name); + if (value) { + // Build new class value where everything is removed except the internal prefixed classes + valueOut = ''; + each(value.split(/\s+/), function(cls) { + if (/mce\w+/.test(cls)) + valueOut += (valueOut ? ' ' : '') + cls; + }); + + // We got some internal classes left + if (valueOut) { + dom.setAttrib(node, name, valueOut); + return; + } + } + } + + // IE6 has a bug where the attribute doesn't get removed correctly + if (name == "class") + node.removeAttribute('className'); + + // Remove mce prefixed attributes + if (MCE_ATTR_RE.test(name)) + node.removeAttribute('data-mce-' + name); + + node.removeAttribute(name); + } + }); + + // Remove classes + each(format.classes, function(value) { + value = replaceVars(value, vars); + + if (!compare_node || dom.hasClass(compare_node, value)) + dom.removeClass(node, value); + }); + + // Check for non internal attributes + attrs = dom.getAttribs(node); + for (i = 0; i < attrs.length; i++) { + if (attrs[i].nodeName.indexOf('_') !== 0) + return FALSE; + } + } + + // Remove the inline child if it's empty for example or + if (format.remove != 'none') { + removeNode(node, format); + return TRUE; + } + }; + + function removeNode(node, format) { + var parentNode = node.parentNode, rootBlockElm; + + if (format.block) { + if (!forcedRootBlock) { + function find(node, next, inc) { + node = getNonWhiteSpaceSibling(node, next, inc); + + return !node || (node.nodeName == 'BR' || isBlock(node)); + }; + + // Append BR elements if needed before we remove the block + if (isBlock(node) && !isBlock(parentNode)) { + if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) + node.insertBefore(dom.create('br'), node.firstChild); + + if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) + node.appendChild(dom.create('br')); + } + } else { + // Wrap the block in a forcedRootBlock if we are at the root of document + if (parentNode == dom.getRoot()) { + if (!format.list_block || !isEq(node, format.list_block)) { + each(tinymce.grep(node.childNodes), function(node) { + if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { + if (!rootBlockElm) + rootBlockElm = wrap(node, forcedRootBlock); + else + rootBlockElm.appendChild(node); + } else + rootBlockElm = 0; + }); + } + } + } + } + + // Never remove nodes that isn't the specified inline element if a selector is specified too + if (format.selector && format.inline && !isEq(format.inline, node)) + return; + + dom.remove(node, 1); + }; + + function getNonWhiteSpaceSibling(node, next, inc) { + if (node) { + next = next ? 'nextSibling' : 'previousSibling'; + + for (node = inc ? node : node[next]; node; node = node[next]) { + if (node.nodeType == 1 || !isWhiteSpaceNode(node)) + return node; + } + } + }; + + function isBookmarkNode(node) { + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; + }; + + function mergeSiblings(prev, next) { + var marker, sibling, tmpSibling; + + function compareElements(node1, node2) { + // Not the same name + if (node1.nodeName != node2.nodeName) + return FALSE; + + function getAttribs(node) { + var attribs = {}; + + each(dom.getAttribs(node), function(attr) { + var name = attr.nodeName.toLowerCase(); + + // Don't compare internal attributes or style + if (name.indexOf('_') !== 0 && name !== 'style') + attribs[name] = dom.getAttrib(node, name); + }); + + return attribs; + }; + + function compareObjects(obj1, obj2) { + var value, name; + + for (name in obj1) { + // Obj1 has item obj2 doesn't have + if (obj1.hasOwnProperty(name)) { + value = obj2[name]; + + // Obj2 doesn't have obj1 item + if (value === undefined) + return FALSE; + + // Obj2 item has a different value + if (obj1[name] != value) + return FALSE; + + // Delete similar value + delete obj2[name]; + } + } + + // Check if obj 2 has something obj 1 doesn't have + for (name in obj2) { + // Obj2 has item obj1 doesn't have + if (obj2.hasOwnProperty(name)) + return FALSE; + } + + return TRUE; + }; + + // Attribs are not the same + if (!compareObjects(getAttribs(node1), getAttribs(node2))) + return FALSE; + + // Styles are not the same + if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) + return FALSE; + + return TRUE; + }; + + // Check if next/prev exists and that they are elements + if (prev && next) { + function findElementSibling(node, sibling_name) { + for (sibling = node; sibling; sibling = sibling[sibling_name]) { + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) + return node; + + if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) + return sibling; + } + + return node; + }; + + // If previous sibling is empty then jump over it + prev = findElementSibling(prev, 'previousSibling'); + next = findElementSibling(next, 'nextSibling'); + + // Compare next and previous nodes + if (compareElements(prev, next)) { + // Append nodes between + for (sibling = prev.nextSibling; sibling && sibling != next;) { + tmpSibling = sibling; + sibling = sibling.nextSibling; + prev.appendChild(tmpSibling); + } + + // Remove next node + dom.remove(next); + + // Move children into prev node + each(tinymce.grep(next.childNodes), function(node) { + prev.appendChild(node); + }); + + return prev; + } + } + + return next; + }; + + function isTextBlock(name) { + return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); + }; + + function getContainer(rng, start) { + var container, offset, lastIdx, walker; + + container = rng[start ? 'startContainer' : 'endContainer']; + offset = rng[start ? 'startOffset' : 'endOffset']; + + if (container.nodeType == 1) { + lastIdx = container.childNodes.length - 1; + + if (!start && offset) + offset--; + + container = container.childNodes[offset > lastIdx ? lastIdx : offset]; + } + + // If start text node is excluded then walk to the next node + if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { + container = new TreeWalker(container, ed.getBody()).next() || container; + } + + // If end text node is excluded then walk to the previous node + if (container.nodeType === 3 && !start && offset == 0) { + container = new TreeWalker(container, ed.getBody()).prev() || container; + } + + return container; + }; + + function performCaretAction(type, name, vars) { + var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; + + // Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container + invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR; + + // Creates a caret container bogus element + function createCaretContainer(fill) { + var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); + + if (fill) { + caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar)); + } + + return caretContainer; + }; + + function isCaretContainerEmpty(node, nodes) { + while (node) { + if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) { + return false; + } + + // Collect nodes + if (nodes && node.nodeType === 1) { + nodes.push(node); + } + + node = node.firstChild; + } + + return true; + }; + + // Returns any parent caret container element + function getParentCaretContainer(node) { + while (node) { + if (node.id === caretContainerId) { + return node; + } + + node = node.parentNode; + } + }; + + // Finds the first text node in the specified node + function findFirstTextNode(node) { + var walker; + + if (node) { + walker = new TreeWalker(node, node); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType === 3) { + return node; + } + } + } + }; + + // Removes the caret container for the specified node or all on the current document + function removeCaretContainer(node, move_caret) { + var child, rng; + + if (!node) { + node = getParentCaretContainer(selection.getStart()); + + if (!node) { + while (node = dom.get(caretContainerId)) { + removeCaretContainer(node, false); + } + } + } else { + rng = selection.getRng(true); + + if (isCaretContainerEmpty(node)) { + if (move_caret !== false) { + rng.setStartBefore(node); + rng.setEndBefore(node); + } + + dom.remove(node); + } else { + child = findFirstTextNode(node); + child = child.deleteData(0, 1); + dom.remove(node, 1); + } + + selection.setRng(rng); + } + }; + + // Applies formatting to the caret postion + function applyCaretFormat() { + var rng, caretContainer, textNode, offset, bookmark, container, text; + + rng = selection.getRng(true); + offset = rng.startOffset; + container = rng.startContainer; + text = container.nodeValue; + + caretContainer = getParentCaretContainer(selection.getStart()); + if (caretContainer) { + textNode = findFirstTextNode(caretContainer); + } + + // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character + if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name)); + rng = rangeUtils.split(rng); + + // Apply the format to the range + apply(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + if (!caretContainer || textNode.nodeValue !== invisibleChar) { + caretContainer = createCaretContainer(true); + textNode = caretContainer.firstChild; + + rng.insertNode(caretContainer); + offset = 1; + + apply(name, vars, caretContainer); + } else { + apply(name, vars, caretContainer); + } + + // Move selection to text node + selection.setCursorLocation(textNode, offset); + } + }; + + function removeCaretFormat() { + var rng = selection.getRng(true), container, offset, bookmark, + hasContentAfter, node, formatNode, parents = [], i, caretContainer; + + container = rng.startContainer; + offset = rng.startOffset; + node = container; + + if (container.nodeType == 3) { + if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) { + hasContentAfter = true; + } + + node = node.parentNode; + } + + while (node) { + if (matchNode(node, name, vars)) { + formatNode = node; + break; + } + + if (node.nextSibling) { + hasContentAfter = true; + } + + parents.push(node); + node = node.parentNode; + } + + // Node doesn't have the specified format + if (!formatNode) { + return; + } + + // Is there contents after the caret then remove the format on the element + if (hasContentAfter) { + // Get bookmark of caret position + bookmark = selection.getBookmark(); + + // Collapse bookmark range (WebKit) + rng.collapse(true); + + // Expand the range to the closest word and split it at those points + rng = expandRng(rng, get(name), true); + rng = rangeUtils.split(rng); + + // Remove the format from the range + remove(name, vars, rng); + + // Move selection back to caret position + selection.moveToBookmark(bookmark); + } else { + caretContainer = createCaretContainer(); + + node = caretContainer; + for (i = parents.length - 1; i >= 0; i--) { + node.appendChild(parents[i].cloneNode(false)); + node = node.firstChild; + } + + // Insert invisible character into inner most format element + node.appendChild(dom.doc.createTextNode(invisibleChar)); + node = node.firstChild; + + // Insert caret container after the formated node + dom.insertAfter(caretContainer, formatNode); + + // Move selection to text node + selection.setCursorLocation(node, 1); + } + }; + + // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements + ed.onBeforeGetContent.addToTop(function() { + var nodes = [], i; + + if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { + // Mark children + i = nodes.length; + while (i--) { + dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); + } + } + }); + + // Remove caret container on mouse up and on key up + tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { + ed[name].addToTop(function() { + removeCaretContainer(); + }); + }); + + // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys + ed.onKeyDown.addToTop(function(ed, e) { + var keyCode = e.keyCode; + + if (keyCode == 8 || keyCode == 37 || keyCode == 39) { + removeCaretContainer(getParentCaretContainer(selection.getStart())); + } + }); + + // Do apply or remove caret format + if (type == "apply") { + applyCaretFormat(); + } else { + removeCaretFormat(); + } + }; + }; +})(tinymce); + +tinymce.onAddEditor.add(function(tinymce, ed) { + var filters, fontSizes, dom, settings = ed.settings; + + if (settings.inline_styles) { + fontSizes = tinymce.explode(settings.font_size_legacy_values); + + function replaceWithSpan(node, styles) { + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); + }; + + filters = { + font : function(dom, node) { + replaceWithSpan(node, { + backgroundColor : node.style.backgroundColor, + color : node.color, + fontFamily : node.face, + fontSize : fontSizes[parseInt(node.size) - 1] + }); + }, + + u : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'underline' + }); + }, + + strike : function(dom, node) { + replaceWithSpan(node, { + textDecoration : 'line-through' + }); + } + }; + + function convert(editor, params) { + dom = editor.dom; + + if (settings.convert_fonts_to_spans) { + tinymce.each(dom.select('font,u,strike', params.node), function(node) { + filters[node.nodeName.toLowerCase()](ed.dom, node); + }); + } + }; + + ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); + + ed.onInit.add(function() { + ed.selection.onSetContent.add(convert); + }); + } +}); + diff --git a/js/tiny_mce/utils/editable_selects.js b/js/tiny_mce/utils/editable_selects.js index fd943c0f..4d9ffe27 100644 --- a/js/tiny_mce/utils/editable_selects.js +++ b/js/tiny_mce/utils/editable_selects.js @@ -1,70 +1,70 @@ -/** - * editable_selects.js - * - * Copyright 2009, Moxiecode Systems AB - * Released under LGPL License. - * - * License: http://tinymce.moxiecode.com/license - * Contributing: http://tinymce.moxiecode.com/contributing - */ - -var TinyMCE_EditableSelects = { - editSelectElm : null, - - init : function() { - var nl = document.getElementsByTagName("select"), i, d = document, o; - - for (i=0; i'; - h += ' '; - - return h; -} - -function updateColor(img_id, form_element_id) { - document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value; -} - -function setBrowserDisabled(id, state) { - var img = document.getElementById(id); - var lnk = document.getElementById(id + "_link"); - - if (lnk) { - if (state) { - lnk.setAttribute("realhref", lnk.getAttribute("href")); - lnk.removeAttribute("href"); - tinyMCEPopup.dom.addClass(img, 'disabled'); - } else { - if (lnk.getAttribute("realhref")) - lnk.setAttribute("href", lnk.getAttribute("realhref")); - - tinyMCEPopup.dom.removeClass(img, 'disabled'); - } - } -} - -function getBrowserHTML(id, target_form_element, type, prefix) { - var option = prefix + "_" + type + "_browser_callback", cb, html; - - cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback")); - - if (!cb) - return ""; - - html = ""; - html += ''; - html += ' '; - - return html; -} - -function openBrowser(img_id, target_form_element, type, option) { - var img = document.getElementById(img_id); - - if (img.className != "mceButtonDisabled") - tinyMCEPopup.openBrowser(target_form_element, type, option); -} - -function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { - if (!form_obj || !form_obj.elements[field_name]) - return; - - var sel = form_obj.elements[field_name]; - - var found = false; - for (var i=0; i'; + h += ' '; + + return h; +} + +function updateColor(img_id, form_element_id) { + document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value; +} + +function setBrowserDisabled(id, state) { + var img = document.getElementById(id); + var lnk = document.getElementById(id + "_link"); + + if (lnk) { + if (state) { + lnk.setAttribute("realhref", lnk.getAttribute("href")); + lnk.removeAttribute("href"); + tinyMCEPopup.dom.addClass(img, 'disabled'); + } else { + if (lnk.getAttribute("realhref")) + lnk.setAttribute("href", lnk.getAttribute("realhref")); + + tinyMCEPopup.dom.removeClass(img, 'disabled'); + } + } +} + +function getBrowserHTML(id, target_form_element, type, prefix) { + var option = prefix + "_" + type + "_browser_callback", cb, html; + + cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback")); + + if (!cb) + return ""; + + html = ""; + html += ''; + html += ' '; + + return html; +} + +function openBrowser(img_id, target_form_element, type, option) { + var img = document.getElementById(img_id); + + if (img.className != "mceButtonDisabled") + tinyMCEPopup.openBrowser(target_form_element, type, option); +} + +function selectByValue(form_obj, field_name, value, add_custom, ignore_case) { + if (!form_obj || !form_obj.elements[field_name]) + return; + + if (!value) + value = ""; + + var sel = form_obj.elements[field_name]; + + var found = false; + for (var i=0; i parseInt(v)) - st = this.mark(f, n); - } - } - - return st; - }, - - hasClass : function(n, c, d) { - return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className); - }, - - getNum : function(n, c) { - c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0]; - c = c.replace(/[^0-9]/g, ''); - - return c; - }, - - addClass : function(n, c, b) { - var o = this.removeClass(n, c); - n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c; - }, - - removeClass : function(n, c) { - c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' '); - return n.className = c != ' ' ? c : ''; - }, - - tags : function(f, s) { - return f.getElementsByTagName(s); - }, - - mark : function(f, n) { - var s = this.settings; - - this.addClass(n, s.invalid_cls); - this.markLabels(f, n, s.invalid_cls); - - return false; - }, - - markLabels : function(f, n, ic) { - var nl, i; - - nl = this.tags(f, "label"); - for (i=0; i parseInt(v)) + st = this.mark(f, n); + } + } + + return st; + }, + + hasClass : function(n, c, d) { + return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className); + }, + + getNum : function(n, c) { + c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0]; + c = c.replace(/[^0-9]/g, ''); + + return c; + }, + + addClass : function(n, c, b) { + var o = this.removeClass(n, c); + n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c; + }, + + removeClass : function(n, c) { + c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' '); + return n.className = c != ' ' ? c : ''; + }, + + tags : function(f, s) { + return f.getElementsByTagName(s); + }, + + mark : function(f, n) { + var s = this.settings; + + this.addClass(n, s.invalid_cls); + n.setAttribute('aria-invalid', 'true'); + this.markLabels(f, n, s.invalid_cls); + + return false; + }, + + markLabels : function(f, n, ic) { + var nl, i; + + nl = this.tags(f, "label"); + for (i=0; i= 0; + + elements.each(function(currentElement) { + Validation.reset(currentElement); + label = $$('label[for="' + currentElement.id + '"]')[0]; + if (label) { + wildCard = label.down('em') || label.down('span.required'); + if (!that.config.show_all_regions) { + if (regionRequired) { + label.up().show(); + } else { + label.up().hide(); + } + } + } + + if (label && wildCard) { + if (!regionRequired) { + wildCard.hide(); + if (label.hasClassName('required')) { + label.removeClassName('required'); + } + } else if (regionRequired) { + wildCard.show(); + if (!label.hasClassName('required')) { + label.addClassName('required') + } + } + } + + if (!regionRequired) { + if (currentElement.hasClassName('required-entry')) { + currentElement.removeClassName('required-entry'); + } + if ('select' == currentElement.tagName.toLowerCase() && + currentElement.hasClassName('validate-select')) { + currentElement.removeClassName('validate-select'); + } + } else { + if (!currentElement.hasClassName('required-entry')) { + currentElement.addClassName('required-entry'); + } + if ('select' == currentElement.tagName.toLowerCase() && + !currentElement.hasClassName('validate-select')) { + currentElement.addClassName('validate-select'); + } + } + }); + }, + update: function() { if (this.regions[this.countryEl.value]) { var i, option, region, def; + def = this.regionSelectEl.getAttribute('defaultValue'); if (this.regionTextEl) { - def = this.regionTextEl.value.toLowerCase(); + if (!def) { + def = this.regionTextEl.value.toLowerCase(); + } this.regionTextEl.value = ''; } - if (!def) { - def = this.regionSelectEl.getAttribute('defaultValue'); - } this.regionSelectEl.options.length = 1; for (regionId in this.regions[this.countryEl.value]) { @@ -199,7 +259,8 @@ RegionUpdater.prototype = { option = document.createElement('OPTION'); option.value = regionId; - option.text = region.name; + option.text = region.name.stripTags(); + option.title = region.name; if (this.regionSelectEl.options.add) { this.regionSelectEl.options.add(option); @@ -207,7 +268,9 @@ RegionUpdater.prototype = { this.regionSelectEl.appendChild(option); } - if (regionId==def || region.name.toLowerCase()==def || region.code.toLowerCase()==def) { + if (regionId==def || (region.name && region.name.toLowerCase()==def) || + (region.name && region.code.toLowerCase()==def) + ) { this.regionSelectEl.value = regionId; } } @@ -246,6 +309,7 @@ RegionUpdater.prototype = { this.setMarkDisplay(this.regionSelectEl, false); } + this._checkRegionRequired(); // Make Zip and its label required/optional var zipUpdater = new ZipUpdater(this.countryEl.value, this.zipEl); zipUpdater.update(); diff --git a/js/varien/iehover-fix.js b/js/varien/iehover-fix.js index 44bcc6e4..7b29f5da 100644 --- a/js/varien/iehover-fix.js +++ b/js/varien/iehover-fix.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ function toggleMenu(el, over) diff --git a/js/varien/js.js b/js/varien/js.js index 5322bf8c..3f335bf9 100644 --- a/js/varien/js.js +++ b/js/varien/js.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ function popWin(url,win,para) { @@ -198,8 +198,8 @@ function decorateDataList(list) { * Parse SID and produces the correct URL */ function parseSidUrl(baseUrl, urlExt) { - sidPos = baseUrl.indexOf('/?SID='); - sid = ''; + var sidPos = baseUrl.indexOf('/?SID='); + var sid = ''; urlExt = (urlExt != undefined) ? urlExt : ''; if(sidPos > -1) { @@ -219,18 +219,20 @@ function parseSidUrl(baseUrl, urlExt) { */ function formatCurrency(price, format, showPlus){ - precision = isNaN(format.precision = Math.abs(format.precision)) ? 2 : format.precision; - requiredPrecision = isNaN(format.requiredPrecision = Math.abs(format.requiredPrecision)) ? 2 : format.requiredPrecision; + var precision = isNaN(format.precision = Math.abs(format.precision)) ? 2 : format.precision; + var requiredPrecision = isNaN(format.requiredPrecision = Math.abs(format.requiredPrecision)) ? 2 : format.requiredPrecision; //precision = (precision > requiredPrecision) ? precision : requiredPrecision; //for now we don't need this difference so precision is requiredPrecision precision = requiredPrecision; - integerRequired = isNaN(format.integerRequired = Math.abs(format.integerRequired)) ? 1 : format.integerRequired; + var integerRequired = isNaN(format.integerRequired = Math.abs(format.integerRequired)) ? 1 : format.integerRequired; - decimalSymbol = format.decimalSymbol == undefined ? "," : format.decimalSymbol; - groupSymbol = format.groupSymbol == undefined ? "." : format.groupSymbol; - groupLength = format.groupLength == undefined ? 3 : format.groupLength; + var decimalSymbol = format.decimalSymbol == undefined ? "," : format.decimalSymbol; + var groupSymbol = format.groupSymbol == undefined ? "." : format.groupSymbol; + var groupLength = format.groupLength == undefined ? 3 : format.groupLength; + + var s = ''; if (showPlus == undefined || showPlus == true) { s = price < 0 ? "-" : ( showPlus ? "+" : ""); @@ -238,10 +240,9 @@ function formatCurrency(price, format, showPlus){ s = ''; } - i = parseInt(price = Math.abs(+price || 0).toFixed(precision)) + ""; - pad = (i.length < integerRequired) ? (integerRequired - i.length) : 0; + var i = parseInt(price = Math.abs(+price || 0).toFixed(precision)) + ""; + var pad = (i.length < integerRequired) ? (integerRequired - i.length) : 0; while (pad) { i = '0' + i; pad--; } - j = (j = i.length) > groupLength ? j % groupLength : 0; re = new RegExp("(\\d{" + groupLength + "})(?=\\d)", "g"); @@ -250,8 +251,8 @@ function formatCurrency(price, format, showPlus){ * when Math.abs(0).toFixed() executed on "0" number. * Result is "0.-0" :( */ - r = (j ? i.substr(0, j) + groupSymbol : "") + i.substr(j).replace(re, "$1" + groupSymbol) + (precision ? decimalSymbol + Math.abs(price - i).toFixed(precision).replace(/-/, 0).slice(2) : "") - + var r = (j ? i.substr(0, j) + groupSymbol : "") + i.substr(j).replace(re, "$1" + groupSymbol) + (precision ? decimalSymbol + Math.abs(price - i).toFixed(precision).replace(/-/, 0).slice(2) : "") + var pattern = ''; if (format.pattern.indexOf('{sign}') == -1) { pattern = s + format.pattern; } else { @@ -398,71 +399,204 @@ Varien.Tabs.prototype = { } } -Varien.DOB = Class.create(); -Varien.DOB.prototype = { - initialize: function(selector, required, format) { - var el = $$(selector)[0]; - this.day = Element.select($(el), '.dob-day input')[0]; - this.month = Element.select($(el), '.dob-month input')[0]; - this.year = Element.select($(el), '.dob-year input')[0]; - this.dob = Element.select($(el), '.dob-full input')[0]; - this.advice = Element.select($(el), '.validation-advice')[0]; +Varien.DateElement = Class.create(); +Varien.DateElement.prototype = { + initialize: function(type, content, required, format) { + if (type == 'id') { + // id prefix + this.day = $(content + 'day'); + this.month = $(content + 'month'); + this.year = $(content + 'year'); + this.full = $(content + 'full'); + this.advice = $(content + 'date-advice'); + } else if (type == 'container') { + // content must be container with data + this.day = content.day; + this.month = content.month; + this.year = content.year; + this.full = content.full; + this.advice = content.advice; + } else { + return; + } + this.required = required; this.format = format; + this.day.addClassName('validate-custom'); this.day.validate = this.validate.bind(this); + this.month.addClassName('validate-custom'); this.month.validate = this.validate.bind(this); + this.year.addClassName('validate-custom'); this.year.validate = this.validate.bind(this); - + + this.setDateRange(false, false); this.year.setAttribute('autocomplete','off'); this.advice.hide(); }, - validate: function() { - var error = false; - - if (this.day.value=='' && this.month.value=='' && this.year.value=='') { + var error = false, + day = parseInt(this.day.value.replace(/^0*/, '')) || 0, + month = parseInt(this.month.value.replace(/^0*/, '')) || 0, + year = parseInt(this.year.value) || 0; + if (!day && !month && !year) { if (this.required) { error = 'This date is a required value.'; } else { - this.dob.value = ''; + this.full.value = ''; } - } else if (this.day.value=='' || this.month.value=='' || this.year.value=='') { + } else if (!day || !month || !year) { error = 'Please enter a valid full date.'; } else { - var date = new Date(); - if (this.day.value<1 || this.day.value>31) { - error = 'Please enter a valid day (1-31).'; - } else if (this.month.value<1 || this.month.value>12) { + var date = new Date, countDaysInMonth = 0, errorType = null; + date.setYear(year);date.setMonth(month-1);date.setDate(32); + countDaysInMonth = 32 - date.getDate(); + if(!countDaysInMonth || countDaysInMonth>31) countDaysInMonth = 31; + + if (day<1 || day>countDaysInMonth) { + errorType = 'day'; + error = 'Please enter a valid day (1-%d).'; + } else if (month<1 || month>12) { + errorType = 'month'; error = 'Please enter a valid month (1-12).'; - } else if (this.year.value<1900 || this.year.value>date.getFullYear()) { - error = 'Please enter a valid year (1900-'+date.getFullYear()+').'; } else { - this.dob.value = this.format.replace(/(%m|%b)/i, this.month.value).replace(/(%d|%e)/i, this.day.value).replace(/%y/i, this.year.value); - var testDOB = this.month.value + '/' + this.day.value + '/'+ this.year.value; - var test = new Date(testDOB); + if(day % 10 == day) this.day.value = '0'+day; + if(month % 10 == month) this.month.value = '0'+month; + this.full.value = this.format.replace(/%[mb]/i, this.month.value).replace(/%[de]/i, this.day.value).replace(/%y/i, this.year.value); + var testFull = this.month.value + '/' + this.day.value + '/'+ this.year.value; + var test = new Date(testFull); if (isNaN(test)) { error = 'Please enter a valid date.'; + } else { + this.setFullDate(test); } } + var valueError = false; + if (!error && !this.validateData()){//(year<1900 || year>curyear) { + errorType = this.validateDataErrorType;//'year'; + valueError = this.validateDataErrorText;//'Please enter a valid year (1900-%d).'; + error = valueError; + } } if (error !== false) { try { - this.advice.innerHTML = Translator.translate(error); + error = Translator.translate(error); } - catch (e) { - this.advice.innerHTML = error; + catch (e) {} + if (!valueError) { + this.advice.innerHTML = error.replace('%d', countDaysInMonth); + } else { + this.advice.innerHTML = this.errorTextModifier(error); } this.advice.show(); return false; } + // fixing elements class + this.day.removeClassName('validation-failed'); + this.month.removeClassName('validation-failed'); + this.year.removeClassName('validation-failed'); + this.advice.hide(); return true; + }, + validateData: function() { + var year = this.fullDate.getFullYear(); + var date = new Date; + this.curyear = date.getFullYear(); + return (year>=1900 && year<=this.curyear); + }, + validateDataErrorType: 'year', + validateDataErrorText: 'Please enter a valid year (1900-%d).', + errorTextModifier: function(text) { + return text.replace('%d', this.curyear); + }, + setDateRange: function(minDate, maxDate) { + this.minDate = minDate; + this.maxDate = maxDate; + }, + setFullDate: function(date) { + this.fullDate = date; } -} +}; + +Varien.DOB = Class.create(); +Varien.DOB.prototype = { + initialize: function(selector, required, format) { + var el = $$(selector)[0]; + var container = {}; + container.day = Element.select(el, '.dob-day input')[0]; + container.month = Element.select(el, '.dob-month input')[0]; + container.year = Element.select(el, '.dob-year input')[0]; + container.full = Element.select(el, '.dob-full input')[0]; + container.advice = Element.select(el, '.validation-advice')[0]; + + new Varien.DateElement('container', container, required, format); + } +}; + +Varien.dateRangeDate = Class.create(); +Varien.dateRangeDate.prototype = Object.extend(new Varien.DateElement(), { + validateData: function() { + var validate = true; + if (this.minDate || this.maxValue) { + if (this.minDate) { + this.minDate = new Date(this.minDate); + this.minDate.setHours(0); + if (isNaN(this.minDate)) { + this.minDate = new Date('1/1/1900'); + } + validate = validate && (this.fullDate >= this.minDate) + } + if (this.maxDate) { + this.maxDate = new Date(this.maxDate) + this.minDate.setHours(0); + if (isNaN(this.maxDate)) { + this.maxDate = new Date(); + } + validate = validate && (this.fullDate <= this.maxDate) + } + if (this.maxDate && this.minDate) { + this.validateDataErrorText = 'Please enter a valid date between %s and %s'; + } else if (this.maxDate) { + this.validateDataErrorText = 'Please enter a valid date less than or equal to %s'; + } else if (this.minDate) { + this.validateDataErrorText = 'Please enter a valid date equal to or greater than %s'; + } else { + this.validateDataErrorText = ''; + } + } + return validate; + }, + validateDataErrorText: 'Date should be between %s and %s', + errorTextModifier: function(text) { + if (this.minDate) { + text = text.sub('%s', this.dateFormat(this.minDate)); + } + if (this.maxDate) { + text = text.sub('%s', this.dateFormat(this.maxDate)); + } + return text; + }, + dateFormat: function(date) { + return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear(); + } +}); + +Varien.FileElement = Class.create(); +Varien.FileElement.prototype = { + initialize: function (id) { + this.fileElement = $(id); + this.hiddenElement = $(id + '_value'); + + this.fileElement.observe('change', this.selectFile.bind(this)); + }, + selectFile: function(event) { + this.hiddenElement.value = this.fileElement.getValue(); + } +}; Validation.addAllThese([ ['validate-custom', ' ', function(v,elm) { @@ -500,6 +634,7 @@ Element.addMethods({ } }); +/* if (!("console" in window) || !("firebug" in console)) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", @@ -509,6 +644,7 @@ if (!("console" in window) || !("firebug" in console)) for (var i = 0; i < names.length; ++i) window.console[names[i]] = function() {} } +*/ /** * Executes event handler on the element. Works with event handlers attached by Prototype, @@ -518,16 +654,51 @@ if (!("console" in window) || !("firebug" in console)) * * @example fireEvent($('my-input', 'click')); */ -function fireEvent(element, event){ - if (document.createEventObject){ - // dispatch for IE +function fireEvent(element, event) { + if (document.createEvent) { + // dispatch for all browsers except IE before version 9 + var evt = document.createEvent("HTMLEvents"); + evt.initEvent(event, true, true ); // event type, bubbling, cancelable + return element.dispatchEvent(evt); + } else { + // dispatch for IE before version 9 var evt = document.createEventObject(); - return element.fireEvent('on'+event,evt) + return element.fireEvent('on' + event, evt) } - else{ - // dispatch for firefox + others - var evt = document.createEvent("HTMLEvents"); - evt.initEvent(event, true, true ); // event type,bubbling,cancelable - return !element.dispatchEvent(evt); +} + +/** + * Returns more accurate results of floating-point modulo division + * E.g.: + * 0.6 % 0.2 = 0.19999999999999996 + * modulo(0.6, 0.2) = 0 + * + * @param dividend + * @param divisor + */ +function modulo(dividend, divisor) +{ + var epsilon = divisor / 10000; + var remainder = dividend % divisor; + + if (Math.abs(remainder - divisor) < epsilon || Math.abs(remainder) < epsilon) { + remainder = 0; } + + return remainder; +} + +/** + * createContextualFragment is not supported in IE9. Adding its support. + */ +if ((typeof Range != "undefined") && !Range.prototype.createContextualFragment) +{ + Range.prototype.createContextualFragment = function(html) + { + var frag = document.createDocumentFragment(), + div = document.createElement("div"); + frag.appendChild(div); + div.outerHTML = html; + return frag; + }; } diff --git a/js/varien/menu.js b/js/varien/menu.js index d82bc1d9..4aa72cb2 100644 --- a/js/varien/menu.js +++ b/js/varien/menu.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/js/varien/payment.js b/js/varien/payment.js index 18de7209..5e51f14a 100644 --- a/js/varien/payment.js +++ b/js/varien/payment.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var paymentForm = Class.create(); @@ -36,7 +36,9 @@ paymentForm.prototype = { method = elements[i].value; } } else { - elements[i].disabled = true; + if((elements[i].type) && ('submit' != elements[i].type.toLowerCase())) { + elements[i].disabled = true; + } } elements[i].setAttribute('autocomplete','off'); } diff --git a/js/varien/telephone.js b/js/varien/telephone.js index 5575b9ed..730ec361 100644 --- a/js/varien/telephone.js +++ b/js/varien/telephone.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ var telephoneElem = Class.create(); diff --git a/js/varien/weee.js b/js/varien/weee.js index ee4435b3..55646c4e 100644 --- a/js/varien/weee.js +++ b/js/varien/weee.js @@ -19,7 +19,7 @@ * * @category Varien * @package js - * @copyright Copyright (c) 2010 Magento Inc. (http://www.magentocommerce.com) + * @copyright Copyright (c) 2012 Magento Inc. (http://www.magentocommerce.com) * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ diff --git a/lib/LinLibertineFont/LinLibertine_Re-4.4.1.ttf b/lib/LinLibertineFont/LinLibertine_Re-4.4.1.ttf new file mode 100644 index 00000000..794d9711 Binary files /dev/null and b/lib/LinLibertineFont/LinLibertine_Re-4.4.1.ttf differ diff --git a/lib/Mage/Archive.php b/lib/Mage/Archive.php new file mode 100644 index 00000000..2e2e78bc --- /dev/null +++ b/lib/Mage/Archive.php @@ -0,0 +1,221 @@ + + */ +class Mage_Archive +{ + + /** + * Archiver is used for compress. + */ + const DEFAULT_ARCHIVER = 'gz'; + + /** + * Default packer for directory. + */ + const TAPE_ARCHIVER = 'tar'; + + /** + * Current archiver is used for compress. + * + * @var Mage_Archiver_Tar|Mage_Archiver_Gz|Mage_Archiver_Bz + */ + protected $_archiver=null; + + /** + * Accessible formats for compress. + * + * @var array + */ + protected $_formats = array( + 'tar' => 'tar', + 'gz' => 'gz', + 'gzip' => 'gz', + 'tgz' => 'tar.gz', + 'tgzip' => 'tar.gz', + 'bz' => 'bz', + 'bzip' => 'bz', + 'bzip2' => 'bz', + 'bz2' => 'bz', + 'tbz' => 'tar.bz', + 'tbzip' => 'tar.bz', + 'tbz2' => 'tar.bz', + 'tbzip2' => 'tar.bz'); + + /** + * Create object of current archiver by $extension. + * + * @param string $extension + * @return Mage_Archiver_Tar|Mage_Archiver_Gz|Mage_Archiver_Bz + */ + protected function _getArchiver($extension) + { + if(array_key_exists(strtolower($extension), $this->_formats)) { + $format = $this->_formats[$extension]; + } else { + $format = self::DEFAULT_ARCHIVER; + } + $class = 'Mage_Archive_'.ucfirst($format); + $this->_archiver = new $class(); + return $this->_archiver; + } + + /** + * Split current format to list of archivers. + * + * @param string $source + * @return array + */ + protected function _getArchivers($source) + { + $ext = pathinfo($source, PATHINFO_EXTENSION); + if(!isset($this->_formats[$ext])) { + return array(); + } + $format = $this->_formats[$ext]; + if ($format) { + $archivers = explode('.', $format); + return $archivers; + } + return array(); + } + + /** + * Pack file or directory to archivers are parsed from extension. + * + * @param string $source + * @param string $destination + * @param boolean $skipRoot skip first level parent + * @return string Path to file + */ + public function pack($source, $destination='packed.tgz', $skipRoot=false) + { + $archivers = $this->_getArchivers($destination); + $interimSource = ''; + for ($i=0; $i_getArchiver($archivers[$i])->pack($source, $packed, $skipRoot); + if ($interimSource && $i < count($archivers)) { + unlink($interimSource); + } + $interimSource = $source; + } + return $source; + } + + /** + * Unpack file from archivers are parsed from extension. + * If $tillTar == true unpack file from archivers till + * meet TAR archiver. + * + * @param string $source + * @param string $destination + * @param bool $tillTar + * @param bool $clearInterm + * @return string Path to file + */ + public function unpack($source, $destination='.', $tillTar=false, $clearInterm = true) + { + $archivers = $this->_getArchivers($source); + $interimSource = ''; + for ($i=count($archivers)-1; $i>=0; $i--) { + if ($tillTar && $archivers[$i] == self::TAPE_ARCHIVER) { + break; + } + if ($i == 0) { + $packed = rtrim($destination, DS) . DS; + } else { + $packed = rtrim($destination, DS) . DS . '~tmp-'. microtime(true) . $archivers[$i-1] . '.' . $archivers[$i-1]; + } + $source = $this->_getArchiver($archivers[$i])->unpack($source, $packed); + + if ($clearInterm && $interimSource && $i >= 0) { + unlink($interimSource); + } + $interimSource = $source; + } + return $source; + } + + /** + * Extract one file from TAR (Tape Archiver). + * + * @param string $file + * @param string $source + * @param string $destination + * @return string Path to file + */ + public function extract($file, $source, $destination='.') + { + $tarFile = $this->unpack($source, $destination, true); + $resFile = $this->_getArchiver(self::TAPE_ARCHIVER)->extract($file, $tarFile, $destination); + if (!$this->isTar($source)) { + unlink($tarFile); + } + return $resFile; + } + + /** + * Check file is archive. + * + * @param string $file + * @return boolean + */ + public function isArchive($file) + { + $archivers = $this->_getArchivers($file); + if (count($archivers)) { + return true; + } + return false; + } + + /** + * Check file is TAR. + * + * @param mixed $file + * @return boolean + */ + public function isTar($file) + { + $archivers = $this->_getArchivers($file); + if (count($archivers)==1 && $archivers[0] == self::TAPE_ARCHIVER) { + return true; + } + return false; + } +} + diff --git a/lib/Mage/Archive/Abstract.php b/lib/Mage/Archive/Abstract.php new file mode 100644 index 00000000..258acb75 --- /dev/null +++ b/lib/Mage/Archive/Abstract.php @@ -0,0 +1,87 @@ + + */ +class Mage_Archive_Abstract +{ + /** + * Write data to file. If file can't be opened - throw exception + * + * @param string $destination + * @param string $data + * @return boolean + * @throws Mage_Exception + */ + protected function _writeFile($destination, $data) + { + $destination = trim($destination); + if(false === file_put_contents($destination, $data)) { + throw new Mage_Exception("Can't write to file: " . $destination); + } + return true; + } + + /** + * Read data from file. If file can't be opened, throw to exception. + * + * @param string $source + * @return string + * @throws Mage_Exception + */ + protected function _readFile($source) + { + $data = ''; + if (is_file($source) && is_readable($source)) { + $data = @file_get_contents($source); + if ($data === false) { + throw new Mage_Exception("Can't get contents from: " . $source); + } + } + return $data; + } + + /** + * Get file name from source (URI) without last extension. + * + * @param string $source + * @param bool $withExtension + * @return mixed|string + */ + public function getFilename($source, $withExtension=false) + { + $file = str_replace(dirname($source) . DS, '', $source); + if (!$withExtension) { + $file = substr($file, 0, strrpos($file, '.')); + } + return $file; + } +} diff --git a/lib/Mage/Archive/Bz.php b/lib/Mage/Archive/Bz.php new file mode 100644 index 00000000..6225207e --- /dev/null +++ b/lib/Mage/Archive/Bz.php @@ -0,0 +1,89 @@ + + */ +class Mage_Archive_Bz extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + + /** + * Pack file by BZIP2 compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination) + { + $fileReader = new Mage_Archive_Helper_File($source); + $fileReader->open('r'); + + $archiveWriter = new Mage_Archive_Helper_File_Bz($destination); + $archiveWriter->open('w'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + $archiveWriter->close(); + + return $destination; + } + + /** + * Unpack file by BZIP2 compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + if (is_dir($destination)) { + $file = $this->getFilename($source); + $destination = $destination . $file; + } + + $archiveReader = new Mage_Archive_Helper_File_Bz($source); + $archiveReader->open('r'); + + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w'); + + while (!$archiveReader->eof()) { + $fileWriter->write($archiveReader->read()); + } + + return $destination; + } + +} diff --git a/lib/Mage/Archive/Gz.php b/lib/Mage/Archive/Gz.php new file mode 100644 index 00000000..08bd836c --- /dev/null +++ b/lib/Mage/Archive/Gz.php @@ -0,0 +1,87 @@ + + */ +class Mage_Archive_Gz extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + /** + * Pack file by GZ compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination) + { + $fileReader = new Mage_Archive_Helper_File($source); + $fileReader->open('r'); + + $archiveWriter = new Mage_Archive_Helper_File_Gz($destination); + $archiveWriter->open('wb9'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + $archiveWriter->close(); + + return $destination; + } + + /** + * Unpack file by GZ compressor. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + if (is_dir($destination)) { + $file = $this->getFilename($source); + $destination = $destination . $file; + } + + $archiveReader = new Mage_Archive_Helper_File_Gz($source); + $archiveReader->open('r'); + + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w'); + + while (!$archiveReader->eof()) { + $fileWriter->write($archiveReader->read()); + } + + return $destination; + } +} diff --git a/lib/Mage/Archive/Helper/File.php b/lib/Mage/Archive/Helper/File.php new file mode 100644 index 00000000..1ef31562 --- /dev/null +++ b/lib/Mage/Archive/Helper/File.php @@ -0,0 +1,274 @@ + +*/ +class Mage_Archive_Helper_File +{ + /** + * Full path to directory where file located + * + * @var string + */ + protected $_fileLocation; + + /** + * File name + * + * @var string + */ + protected $_fileName; + + /** + * Full path (directory + filename) to file + * + * @var string + */ + protected $_filePath; + + /** + * File permissions that will be set if file opened in write mode + * + * @var int + */ + protected $_chmod; + + /** + * File handler + * + * @var pointer + */ + protected $_fileHandler; + + /** + * Set file path via constructor + * + * @param string $filePath + */ + public function __construct($filePath) + { + $pathInfo = pathinfo($filePath); + + $this->_filePath = $filePath; + $this->_fileLocation = isset($pathInfo['dirname']) ? $pathInfo['dirname'] : ''; + $this->_fileName = isset($pathInfo['basename']) ? $pathInfo['basename'] : ''; + } + + /** + * Close file if it's not closed before object destruction + */ + public function __destruct() + { + if ($this->_fileHandler) { + $this->_close(); + } + } + + /** + * Open file + * + * @param string $mode + * @param int $chmod + * @throws Mage_Exception + */ + public function open($mode = 'w+', $chmod = 0666) + { + if ($this->_isWritableMode($mode)) { + if (!is_writable($this->_fileLocation)) { + throw new Mage_Exception('Permission denied to write to ' . $this->_fileLocation); + } + + if (is_file($this->_filePath) && !is_writable($this->_filePath)) { + throw new Mage_Exception("Can't open file " . $this->_fileName . " for writing. Permission denied."); + } + } + + if ($this->_isReadableMode($mode) && (!is_file($this->_filePath) || !is_readable($this->_filePath))) { + if (!is_file($this->_filePath)) { + throw new Mage_Exception('File ' . $this->_filePath . ' does not exist'); + } + + if (!is_readable($this->_filePath)) { + throw new Mage_Exception('Permission denied to read file ' . $this->_filePath); + } + } + + $this->_open($mode); + + $this->_chmod = $chmod; + } + + /** + * Write data to file + * + * @param string $data + */ + public function write($data) + { + $this->_checkFileOpened(); + $this->_write($data); + } + + /** + * Read data from file + * + * @param int $length + * @return string|boolean + */ + public function read($length = 4096) + { + $data = false; + $this->_checkFileOpened(); + if ($length > 0) { + $data = $this->_read($length); + } + + return $data; + } + + /** + * Check whether end of file reached + * + * @return boolean + */ + public function eof() + { + $this->_checkFileOpened(); + return $this->_eof(); + } + + /** + * Close file + */ + public function close() + { + $this->_checkFileOpened(); + $this->_close(); + $this->_fileHandler = false; + @chmod($this->_filePath, $this->_chmod); + } + + /** + * Implementation of file opening + * + * @param string $mode + * @throws Mage_Exception + */ + protected function _open($mode) + { + $this->_fileHandler = @fopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * Implementation of writing data to file + * + * @param string $data + * @throws Mage_Exception + */ + protected function _write($data) + { + $result = @fwrite($this->_fileHandler, $data); + + if (false === $result) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * Implementation of file reading + * + * @param int $length + * @throws Mage_Exception + */ + protected function _read($length) + { + $result = fread($this->_fileHandler, $length); + + if (false === $result) { + throw new Mage_Exception('Failed to read data from ' . $this->_filePath); + } + + return $result; + } + + /** + * Implementation of EOF indicator + * + * @return boolean + */ + protected function _eof() + { + return feof($this->_fileHandler); + } + + /** + * Implementation of file closing + */ + protected function _close() + { + fclose($this->_fileHandler); + } + + /** + * Check whether requested mode is writable mode + * + * @param string $mode + */ + protected function _isWritableMode($mode) + { + return preg_match('/(^[waxc])|(\+$)/', $mode); + } + + /** + * Check whether requested mode is readable mode + * + * @param string $mode + */ + protected function _isReadableMode($mode) { + return !$this->_isWritableMode($mode); + } + + /** + * Check whether file is opened + * + * @throws Mage_Exception + */ + protected function _checkFileOpened() + { + if (!$this->_fileHandler) { + throw new Mage_Exception('File not opened'); + } + } +} diff --git a/lib/Mage/Archive/Helper/File/Bz.php b/lib/Mage/Archive/Helper/File/Bz.php new file mode 100644 index 00000000..770f453e --- /dev/null +++ b/lib/Mage/Archive/Helper/File/Bz.php @@ -0,0 +1,92 @@ + +*/ +class Mage_Archive_Helper_File_Bz extends Mage_Archive_Helper_File +{ + /** + * Open bz archive file + * + * @throws Mage_Exception + * @param string $mode + */ + protected function _open($mode) + { + $this->_fileHandler = @bzopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * Write data to bz archive + * + * @throws Mage_Exception + * @param $data + */ + protected function _write($data) + { + $result = @bzwrite($this->_fileHandler, $data); + + if (false === $result) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * Read data from bz archive + * + * @throws Mage_Exception + * @param int $length + * @return string + */ + protected function _read($length) + { + $data = bzread($this->_fileHandler, $length); + + if (false === $data) { + throw new Mage_Exception('Failed to read data from ' . $this->_filePath); + } + + return $data; + } + + /** + * Close bz archive + */ + protected function _close() + { + bzclose($this->_fileHandler); + } +} + diff --git a/lib/Mage/Archive/Helper/File/Gz.php b/lib/Mage/Archive/Helper/File/Gz.php new file mode 100644 index 00000000..d546cb79 --- /dev/null +++ b/lib/Mage/Archive/Helper/File/Gz.php @@ -0,0 +1,83 @@ + +*/ +class Mage_Archive_Helper_File_Gz extends Mage_Archive_Helper_File +{ + /** + * @see Mage_Archive_Helper_File::_open() + */ + protected function _open($mode) + { + $this->_fileHandler = @gzopen($this->_filePath, $mode); + + if (false === $this->_fileHandler) { + throw new Mage_Exception('Failed to open file ' . $this->_filePath); + } + } + + /** + * @see Mage_Archive_Helper_File::_write() + */ + protected function _write($data) + { + $result = @gzwrite($this->_fileHandler, $data); + + if (empty($result) && !empty($data)) { + throw new Mage_Exception('Failed to write data to ' . $this->_filePath); + } + } + + /** + * @see Mage_Archive_Helper_File::_read() + */ + protected function _read($length) + { + return gzread($this->_fileHandler, $length); + } + + /** + * @see Mage_Archive_Helper_File::_eof() + */ + protected function _eof() + { + return gzeof($this->_fileHandler); + } + + /** + * @see Mage_Archive_Helper_File::_close() + */ + protected function _close() + { + gzclose($this->_fileHandler); + } +} diff --git a/lib/Mage/Archive/Interface.php b/lib/Mage/Archive/Interface.php new file mode 100644 index 00000000..f20977f4 --- /dev/null +++ b/lib/Mage/Archive/Interface.php @@ -0,0 +1,53 @@ + + */ +interface Mage_Archive_Interface +{ + /** + * Pack file or directory. + * + * @param string $source + * @param string $destination + * @return string + */ + public function pack($source, $destination); + + /** + * Unpack file or directory. + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination); +} diff --git a/lib/Mage/Archive/Tar.php b/lib/Mage/Archive/Tar.php new file mode 100644 index 00000000..191c0c89 --- /dev/null +++ b/lib/Mage/Archive/Tar.php @@ -0,0 +1,688 @@ + + */ +class Mage_Archive_Tar extends Mage_Archive_Abstract implements Mage_Archive_Interface +{ + /** + * Tar block size + * + * @const int + */ + const TAR_BLOCK_SIZE = 512; + + /** + * Keep file or directory for packing. + * + * @var string + */ + protected $_currentFile; + + /** + * Keep path to file or directory for packing. + * + * @var mixed + */ + protected $_currentPath; + + /** + * Skip first level parent directory. Example: + * use test/fip.php instead test/test/fip.php; + * + * @var mixed + */ + protected $_skipRoot; + + /** + * Tarball data writer + * + * @var Mage_Archive_Helper_File + */ + protected $_writer; + + /** + * Tarball data reader + * + * @var Mage_Archive_Helper_File + */ + protected $_reader; + + /** + * Path to file where tarball should be placed + * + * @var string + */ + protected $_destinationFilePath; + + /** + * Initialize tarball writer + * + * @return Mage_Archive_Tar + */ + protected function _initWriter() + { + $this->_writer = new Mage_Archive_Helper_File($this->_destinationFilePath); + $this->_writer->open('w'); + + return $this; + } + + /** + * Returns string that is used for tar's header parsing + * + * @return string + */ + protected static final function _getFormatParseHeader() + { + return 'a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100symlink/a6magic/a2version/' + . 'a32uname/a32gname/a8devmajor/a8devminor/a155prefix/a12closer'; + } + + /** + * Destroy tarball writer + * + * @return Mage_Archive_Tar + */ + protected function _destroyWriter() + { + if ($this->_writer instanceof Mage_Archive_Helper_File) { + $this->_writer->close(); + $this->_writer = null; + } + + return $this; + } + + /** + * Get tarball writer + * + * @return Mage_Archive_Helper_File + */ + protected function _getWriter() + { + if (!$this->_writer) { + $this->_initWriter(); + } + + return $this->_writer; + } + + /** + * Initialize tarball reader + * + * @return Mage_Archive_Tar + */ + protected function _initReader() + { + $this->_reader = new Mage_Archive_Helper_File($this->_getCurrentFile()); + $this->_reader->open('r'); + + return $this; + } + + /** + * Destroy tarball reader + * + * @return Mage_Archive_Tar + */ + protected function _destroyReader() + { + if ($this->_reader instanceof Mage_Archive_Helper_File) { + $this->_reader->close(); + $this->_reader = null; + } + + return $this; + } + + /** + * Get tarball reader + * + * @return Mage_Archive_Helper_File + */ + protected function _getReader() + { + if (!$this->_reader) { + $this->_initReader(); + } + + return $this->_reader; + } + + /** + * Set option that define ability skip first catalog level. + * + * @param mixed $skipRoot + * @return Mage_Archive_Tar + */ + protected function _setSkipRoot($skipRoot) + { + $this->_skipRoot = $skipRoot; + return $this; + } + + /** + * Set file which is packing. + * + * @param string $file + * @return Mage_Archive_Tar + */ + protected function _setCurrentFile($file) + { + $this->_currentFile = $file .((!is_link($file) && is_dir($file) && substr($file, -1) != DS) ? DS : ''); + return $this; + } + + /** + * Set path to file where tarball should be placed + * + * @param string $destinationFilePath + * @return Mage_Archive_Tar + */ + protected function _setDestinationFilePath($destinationFilePath) + { + $this->_destinationFilePath = $destinationFilePath; + return $this; + } + + /** + * Retrieve file which is packing. + * + * @return string + */ + protected function _getCurrentFile() + { + return $this->_currentFile; + } + + /** + * Set path to file which is packing. + * + * @param string $path + * @return Mage_Archive_Tar + */ + protected function _setCurrentPath($path) + { + if ($this->_skipRoot && is_dir($path)) { + $this->_currentPath = $path.(substr($path, -1)!=DS?DS:''); + } else { + $this->_currentPath = dirname($path) . DS; + } + return $this; + } + + /** + * Retrieve path to file which is packing. + * + * @return string + */ + protected function _getCurrentPath() + { + return $this->_currentPath; + } + + /** + * Walk through directory and add to tar file or directory. + * Result is packed string on TAR format. + * + * @deprecated after 1.7.0.0 + * @param boolean $skipRoot + * @return string + */ + protected function _packToTar($skipRoot=false) + { + $file = $this->_getCurrentFile(); + $header = ''; + $data = ''; + if (!$skipRoot) { + $header = $this->_composeHeader(); + $data = $this->_readFile($file); + $data = str_pad($data, floor(((is_dir($file) ? 0 : filesize($file)) + 512 - 1) / 512) * 512, "\0"); + } + $sub = ''; + if (is_dir($file)) { + $treeDir = scandir($file); + if (empty($treeDir)) { + throw new Mage_Exception('Can\'t scan dir: ' . $file); + } + array_shift($treeDir); /* remove './'*/ + array_shift($treeDir); /* remove '../'*/ + foreach ($treeDir as $item) { + $sub .= $this->_setCurrentFile($file.$item)->_packToTar(false); + } + } + $tarData = $header . $data . $sub; + $tarData = str_pad($tarData, floor((strlen($tarData) - 1) / 1536) * 1536, "\0"); + return $tarData; + } + + /** + * Recursively walk through file tree and create tarball + * + * @param boolean $skipRoot + * @param boolean $finalize + * @throws Mage_Exception + */ + protected function _createTar($skipRoot = false, $finalize = false) + { + if (!$skipRoot) { + $this->_packAndWriteCurrentFile(); + } + + $file = $this->_getCurrentFile(); + + if (is_dir($file)) { + $dirFiles = scandir($file); + + if (false === $dirFiles) { + throw new Mage_Exception('Can\'t scan dir: ' . $file); + } + + array_shift($dirFiles); /* remove './'*/ + array_shift($dirFiles); /* remove '../'*/ + + foreach ($dirFiles as $item) { + $this->_setCurrentFile($file . $item)->_createTar(); + } + } + + if ($finalize) { + $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12)); + } + } + + /** + * Write current file to tarball + */ + protected function _packAndWriteCurrentFile() + { + $archiveWriter = $this->_getWriter(); + $archiveWriter->write($this->_composeHeader()); + + $currentFile = $this->_getCurrentFile(); + + $fileSize = 0; + + if (is_file($currentFile) && !is_link($currentFile)) { + $fileReader = new Mage_Archive_Helper_File($currentFile); + $fileReader->open('r'); + + while (!$fileReader->eof()) { + $archiveWriter->write($fileReader->read()); + } + + $fileReader->close(); + + $fileSize = filesize($currentFile); + } + + $appendZerosCount = (self::TAR_BLOCK_SIZE - $fileSize % self::TAR_BLOCK_SIZE) % self::TAR_BLOCK_SIZE; + $archiveWriter->write(str_repeat("\0", $appendZerosCount)); + } + + /** + * Compose header for current file in TAR format. + * If length of file's name greater 100 characters, + * method breaks header into two pieces. First contains + * header and data with long name. Second contain only header. + * + * @param boolean $long + * @return string + */ + protected function _composeHeader($long = false) + { + $file = $this->_getCurrentFile(); + $path = $this->_getCurrentPath(); + $infoFile = stat($file); + $nameFile = str_replace($path, '', $file); + $nameFile = str_replace('\\', '/', $nameFile); + $packedHeader = ''; + $longHeader = ''; + if (!$long && strlen($nameFile)>100) { + $longHeader = $this->_composeHeader(true); + $longHeader .= str_pad($nameFile, floor((strlen($nameFile) + 512 - 1) / 512) * 512, "\0"); + } + $header = array(); + $header['100-name'] = $long?'././@LongLink':substr($nameFile, 0, 100); + $header['8-mode'] = $long ? ' ' + : str_pad(substr(sprintf("%07o", $infoFile['mode']),-4), 6, '0', STR_PAD_LEFT); + $header['8-uid'] = $long || $infoFile['uid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['uid']); + $header['8-gid'] = $long || $infoFile['gid']==0?"\0\0\0\0\0\0\0":sprintf("%07o", $infoFile['gid']); + $header['12-size'] = $long ? sprintf("%011o", strlen($nameFile)) : sprintf("%011o", is_dir($file) + ? 0 : filesize($file)); + $header['12-mtime'] = $long?'00000000000':sprintf("%011o", $infoFile['mtime']); + $header['8-check'] = sprintf('% 8s', ''); + $header['1-type'] = $long ? 'L' : (is_link($file) ? 2 : (is_dir($file) ? 5 : 0)); + $header['100-symlink'] = is_link($file) ? readlink($file) : ''; + $header['6-magic'] = 'ustar '; + $header['2-version'] = ' '; + $a=function_exists('posix_getpwuid')?posix_getpwuid (fileowner($file)):array('name'=>''); + $header['32-uname'] = $a['name']; + $a=function_exists('posix_getgrgid')?posix_getgrgid (filegroup($file)):array('name'=>''); + $header['32-gname'] = $a['name']; + $header['8-devmajor'] = ''; + $header['8-devminor'] = ''; + $header['155-prefix'] = ''; + $header['12-closer'] = ''; + + $packedHeader = ''; + foreach ($header as $key=>$element) { + $length = explode('-', $key); + $packedHeader .= pack('a' . $length[0], $element); + } + + $checksum = 0; + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($packedHeader, $i, 1)); + } + $packedHeader = substr_replace($packedHeader, sprintf("%07o", $checksum)."\0", 148, 8); + + return $longHeader . $packedHeader; + } + + /** + * Read TAR string from file, and unpacked it. + * Create files and directories information about discribed + * in the string. + * + * @param string $destination path to file is unpacked + * @return array list of files + * @throws Mage_Exception + */ + protected function _unpackCurrentTar($destination) + { + $archiveReader = $this->_getReader(); + $list = array(); + + while (!$archiveReader->eof()) { + $header = $this->_extractFileHeader(); + + if (!$header) { + continue; + } + + $currentFile = $destination . $header['name']; + $dirname = dirname($currentFile); + + if (in_array($header['type'], array("0",chr(0), ''))) { + + if(!file_exists($dirname)) { + $mkdirResult = @mkdir($dirname, 0777, true); + + if (false === $mkdirResult) { + throw new Mage_Exception('Failed to create directory ' . $dirname); + } + } + + $this->_extractAndWriteFile($header, $currentFile); + $list[] = $currentFile; + + } elseif ($header['type'] == '5') { + + if(!file_exists($dirname)) { + $mkdirResult = @mkdir($currentFile, $header['mode'], true); + + if (false === $mkdirResult) { + throw new Mage_Exception('Failed to create directory ' . $currentFile); + } + } + $list[] = $currentFile . DS; + } elseif ($header['type'] == '2') { + + $symlinkResult = @symlink($header['symlink'], $currentFile); + + if (false === $symlinkResult) { + throw new Mage_Exception('Failed to create symlink ' . $currentFile . ' to ' . $header['symlink']); + } + } + } + + return $list; + } + + /** + * Get header from TAR string and unpacked it by format. + * + * @deprecated after 1.7.0.0 + * @param resource $pointer + * @return string + */ + protected function _parseHeader(&$pointer) + { + $firstLine = fread($pointer, 512); + + if (strlen($firstLine)<512){ + return false; + } + + $fmt = self::_getFormatParseHeader(); + $header = unpack ($fmt, $firstLine); + + $header['mode']=$header['mode']+0; + $header['uid']=octdec($header['uid']); + $header['gid']=octdec($header['gid']); + $header['size']=octdec($header['size']); + $header['mtime']=octdec($header['mtime']); + $header['checksum']=octdec($header['checksum']); + + if ($header['type'] == "5") { + $header['size'] = 0; + } + + $checksum = 0; + $firstLine = substr_replace($firstLine, ' ', 148, 8); + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($firstLine, $i, 1)); + } + + $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); + + $checksumOk = $header['checksum'] == $checksum; + if (isset($header['name']) && $checksumOk) { + if ($header['name'] == '././@LongLink' && $header['type'] == 'L') { + $realName = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); + $headerMain = $this->_parseHeader($pointer); + $headerMain['name'] = $realName; + return $headerMain; + } else { + if ($header['size']>0) { + $header['data'] = substr(fread($pointer, floor(($header['size'] + 512 - 1) / 512) * 512), 0, $header['size']); + } else { + $header['data'] = ''; + } + return $header; + } + } + return false; + } + + /** + * Read and decode file header information from tarball + * + * @return array|boolean + */ + protected function _extractFileHeader() + { + $archiveReader = $this->_getReader(); + + $headerBlock = $archiveReader->read(self::TAR_BLOCK_SIZE); + + if (strlen($headerBlock) < self::TAR_BLOCK_SIZE) { + return false; + } + + $header = unpack(self::_getFormatParseHeader(), $headerBlock); + + $header['mode'] = octdec($header['mode']); + $header['uid'] = octdec($header['uid']); + $header['gid'] = octdec($header['gid']); + $header['size'] = octdec($header['size']); + $header['mtime'] = octdec($header['mtime']); + $header['checksum'] = octdec($header['checksum']); + + if ($header['type'] == "5") { + $header['size'] = 0; + } + + $checksum = 0; + $headerBlock = substr_replace($headerBlock, ' ', 148, 8); + + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($headerBlock, $i, 1)); + } + + $isUstar = 'ustar' == strtolower(substr($header['magic'], 0, 5)); + + $checksumOk = $header['checksum'] == $checksum; + if (isset($header['name']) && $checksumOk) { + + if (!($header['name'] == '././@LongLink' && $header['type'] == 'L')) { + return $header; + } + + $realNameBlockSize = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) + * self::TAR_BLOCK_SIZE; + $realNameBlock = $archiveReader->read($realNameBlockSize); + $realName = substr($realNameBlock, 0, $header['size']); + + $headerMain = $this->_extractFileHeader(); + $headerMain['name'] = $realName; + return $headerMain; + } + + return false; + } + + /** + * Extract next file from tarball by its $header information and save it to $destination + * + * @param array $fileHeader + * @param string $destination + */ + protected function _extractAndWriteFile($fileHeader, $destination) + { + $fileWriter = new Mage_Archive_Helper_File($destination); + $fileWriter->open('w', $fileHeader['mode']); + + $archiveReader = $this->_getReader(); + + $filesize = $fileHeader['size']; + $bytesExtracted = 0; + + while ($filesize > $bytesExtracted && !$archiveReader->eof()) { + $block = $archiveReader->read(self::TAR_BLOCK_SIZE); + $nonExtractedBytesCount = $filesize - $bytesExtracted; + + $data = substr($block, 0, $nonExtractedBytesCount); + $fileWriter->write($data); + + $bytesExtracted += strlen($block); + } + } + + /** + * Pack file to TAR (Tape Archiver). + * + * @param string $source + * @param string $destination + * @param boolean $skipRoot + * @return string + */ + public function pack($source, $destination, $skipRoot = false) + { + $this->_setSkipRoot($skipRoot); + $source = realpath($source); + $tarData = $this->_setCurrentPath($source) + ->_setDestinationFilePath($destination) + ->_setCurrentFile($source); + + $this->_initWriter(); + $this->_createTar($skipRoot, true); + $this->_destroyWriter(); + + return $destination; + } + + /** + * Unpack file from TAR (Tape Archiver). + * + * @param string $source + * @param string $destination + * @return string + */ + public function unpack($source, $destination) + { + $this->_setCurrentFile($source) + ->_setCurrentPath($source); + + $this->_initReader(); + $this->_unpackCurrentTar($destination); + $this->_destroyReader(); + + return $destination; + } + + /** + * Extract one file from TAR (Tape Archiver). + * + * @param string $file + * @param string $source + * @param string $destination + * @return string + */ + public function extract($file, $source, $destination) + { + $this->_setCurrentFile($source); + $this->_initReader(); + + $archiveReader = $this->_getReader(); + $extractedFile = ''; + + while (!$archiveReader->eof()) { + $header = $this->_extractFileHeader(); + if ($header['name'] == $file) { + $extractedFile = $destination . basename($header['name']); + $this->_extractAndWriteFile($header, $extractedFile); + break; + } + + if ($header['type'] != 5){ + $skipBytes = floor(($header['size'] + self::TAR_BLOCK_SIZE - 1) / self::TAR_BLOCK_SIZE) + * self::TAR_BLOCK_SIZE; + $archiveReader->read($skipBytes); + } + } + + $this->_destroyReader(); + return $extractedFile; + } +} diff --git a/lib/Mage/Autoload/Simple.php b/lib/Mage/Autoload/Simple.php new file mode 100644 index 00000000..483a3398 --- /dev/null +++ b/lib/Mage/Autoload/Simple.php @@ -0,0 +1,52 @@ + + */ +class Mage_Backup +{ + /** + * List of supported a backup types + * + * @var array + */ + static protected $_allowedBackupTypes = array('db', 'snapshot', 'filesystem', 'media', 'nomedia'); + + /** + * get Backup Instance By File Name + * + * @param string $type + * @return Mage_Backup_Interface + */ + static public function getBackupInstance($type) + { + $class = 'Mage_Backup_' . ucfirst($type); + + if (!in_array($type, self::$_allowedBackupTypes) || !class_exists($class, true)){ + throw new Mage_Exception('Current implementation not supported this type (' . $type . ') of backup.'); + } + + return new $class(); + } +} diff --git a/lib/Mage/Backup/Abstract.php b/lib/Mage/Backup/Abstract.php new file mode 100644 index 00000000..a1cb5f33 --- /dev/null +++ b/lib/Mage/Backup/Abstract.php @@ -0,0 +1,318 @@ + + */ +abstract class Mage_Backup_Abstract implements Mage_Backup_Interface +{ + /** + * Backup name + * + * @var string + */ + protected $_name; + + /** + * Backup creation date + * + * @var int + */ + protected $_time; + + /** + * Backup file extension + * + * @var string + */ + protected $_backupExtension; + + /** + * Resource model + * + * @var object + */ + protected $_resourceModel; + + /** + * Magento's root directory + * + * @var string + */ + protected $_rootDir; + + /** + * Path to directory where backups stored + * + * @var string + */ + protected $_backupsDir; + + /** + * Is last operation completed successfully + * + * @var bool + */ + protected $_lastOperationSucceed = false; + + /** + * Last failed operation error message + * + * @var string + */ + protected $_lastErrorMessage; + + + /** + * Set Backup Extension + * + * @param string $backupExtension + * @return Mage_Backup_Interface + */ + public function setBackupExtension($backupExtension) + { + $this->_backupExtension = $backupExtension; + return $this; + } + + /** + * Get Backup Extension + * + * @return string + */ + public function getBackupExtension() + { + return $this->_backupExtension; + } + + /** + * Set Resource Model + * + * @param object $resourceModel + * @return Mage_Backup_Interface + */ + public function setResourceModel($resourceModel) + { + $this->_resourceModel = $resourceModel; + return $this; + } + + /** + * Get Resource Model + * + * @return object + */ + public function getResourceModel() + { + return $this->_resourceModel; + } + + /** + * Set Time + * + * @param int $time + * @return Mage_Backup_Interface + */ + public function setTime($time) + { + $this->_time = $time; + return $this; + } + + /** + * Get Time + * + * @return int + */ + public function getTime() + { + return $this->_time; + } + + /** + * Set root directory of Magento installation + * + * @param string $rootDir + * @throws Mage_Exception + * @return Mage_Backup_Interface + */ + public function setRootDir($rootDir) + { + if (!is_dir($rootDir)) { + throw new Mage_Exception('Bad root directory'); + } + + $this->_rootDir = $rootDir; + return $this; + } + + /** + * Get Magento's root directory + * @return string + */ + public function getRootDir() + { + return $this->_rootDir; + } + + /** + * Set path to directory where backups stored + * + * @param string $backupsDir + * @return Mage_Backup_Interface + */ + public function setBackupsDir($backupsDir) + { + $this->_backupsDir = $backupsDir; + return $this; + } + + /** + * Get path to directory where backups stored + * + * @return string + */ + public function getBackupsDir() + { + return $this->_backupsDir; + } + + /** + * Get path to backup + * + * @return string + */ + public function getBackupPath() + { + return $this->getBackupsDir() . DS . $this->getBackupFilename(); + } + + /** + * Get backup file name + * + * @return string + */ + public function getBackupFilename() + { + $filename = $this->getTime() . '_' . $this->getType(); + + $name = $this->getName(); + + if (!empty($name)) { + $filename .= '_' . $name; + } + + $filename .= '.' . $this->getBackupExtension(); + + return $filename; + } + + /** + * Check whether last operation completed successfully + * + * @return bool + */ + public function getIsSuccess() + { + return $this->_lastOperationSucceed; + } + + /** + * Get last error message + * + * @return string + */ + public function getErrorMessage() + { + return $this->_lastErrorMessage; + } + + /** + * Set error message + * + * @param string $errorMessage + * @return string + */ + public function setErrorMessage($errorMessage) + { + $this->_lastErrorMessage = $errorMessage; + } + + /** + * Set backup name + * + * @param string $name + * @param bool $applyFilter + * @return Mage_Backup_Interface + */ + public function setName($name, $applyFilter = true) + { + if ($applyFilter) { + $name = $this->_filterName($name); + } + $this->_name = $name; + return $this; + } + + /** + * Get backup name + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Get backup display name + * + * @return string + */ + public function getDisplayName() + { + return str_replace('_', ' ', $this->_name); + } + + /** + * Removes disallowed characters and replaces spaces with underscores + * + * @param string $name + * @return string + */ + protected function _filterName($name) + { + $name = trim(preg_replace('/[^\da-zA-Z ]/', '', $name)); + $name = preg_replace('/\s{2,}/', ' ', $name); + $name = str_replace(' ', '_', $name); + + return $name; + } +} diff --git a/lib/Mage/Backup/Archive/Tar.php b/lib/Mage/Backup/Archive/Tar.php new file mode 100644 index 00000000..9188113f --- /dev/null +++ b/lib/Mage/Backup/Archive/Tar.php @@ -0,0 +1,82 @@ + + */ +class Mage_Backup_Archive_Tar extends Mage_Archive_Tar +{ + /** + * Filenames or filename parts that are used for filtering files + * + * @var array() + */ + protected $_skipFiles = array(); + + /** + * Overridden Mage_Archive_Tar::_createTar method that does the same actions as it's parent but filters + * files using Mage_Backup_Filesystem_Iterator_Filter + * + * @see Mage_Archive_Tar::_createTar() + * @param bool $skipRoot + * @param bool $finalize + */ + protected function _createTar($skipRoot = false, $finalize = false) + { + $path = $this->_getCurrentFile(); + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_skipFiles); + + foreach ($iterator as $item) { + $this->_setCurrentFile($item->getPathname()); + $this->_packAndWriteCurrentFile(); + } + + if ($finalize) { + $this->_getWriter()->write(str_repeat("\0", self::TAR_BLOCK_SIZE * 12)); + } + } + + /** + * Set files that shouldn't be added to tarball + * + * @param array $skipFiles + * @return Mage_Backup_Archive_Tar + */ + public function setSkipFiles(array $skipFiles) + { + $this->_skipFiles = $skipFiles; + return $this; + } +} diff --git a/lib/Mage/Backup/Db.php b/lib/Mage/Backup/Db.php new file mode 100644 index 00000000..baece22a --- /dev/null +++ b/lib/Mage/Backup/Db.php @@ -0,0 +1,119 @@ + + */ +class Mage_Backup_Db extends Mage_Backup_Abstract +{ + /** + * Implements Rollback functionality for Db + * + * @return bool + */ + public function rollback() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $archiveManager = new Mage_Archive(); + $source = $archiveManager->unpack($this->getBackupPath(), $this->getBackupsDir()); + + $file = new Mage_Backup_Filesystem_Iterator_File($source); + foreach ($file as $statement) { + $this->getResourceModel()->runCommand($statement); + } + @unlink($source); + + $this->_lastOperationSucceed = true; + + return true; + } + + /** + * Checks whether the line is last in sql command + * + * @param $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0){ + $lastSymbolIndex = $lineLength-1; + if ($cleanLine[$lastSymbolIndex] == ';'){ + $returnResult = true; + } + } + + return $returnResult; + } + + /** + * Implements Create Backup functionality for Db + * + * @return bool + */ + public function create() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $backup = Mage::getModel('backup/backup') + ->setTime($this->getTime()) + ->setType($this->getType()) + ->setPath($this->getBackupsDir()) + ->setName($this->getName()); + + $backupDb = Mage::getModel('backup/db'); + $backupDb->createBackup($backup); + + $this->_lastOperationSucceed = true; + + return true; + } + + /** + * Get Backup Type + * + * @return string + */ + public function getType() + { + return 'db'; + } +} diff --git a/lib/Mage/Backup/Exception.php b/lib/Mage/Backup/Exception.php new file mode 100644 index 00000000..f8283800 --- /dev/null +++ b/lib/Mage/Backup/Exception.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception extends Mage_Exception +{ +} diff --git a/lib/Mage/Backup/Exception/CantLoadSnapshot.php b/lib/Mage/Backup/Exception/CantLoadSnapshot.php new file mode 100644 index 00000000..ad534352 --- /dev/null +++ b/lib/Mage/Backup/Exception/CantLoadSnapshot.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_CantLoadSnapshot extends Mage_Backup_Exception +{ +} diff --git a/lib/Mage/Backup/Exception/FtpConnectionFailed.php b/lib/Mage/Backup/Exception/FtpConnectionFailed.php new file mode 100644 index 00000000..a8d5185f --- /dev/null +++ b/lib/Mage/Backup/Exception/FtpConnectionFailed.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_FtpConnectionFailed extends Mage_Backup_Exception +{ +} diff --git a/lib/Mage/Backup/Exception/FtpValidationFailed.php b/lib/Mage/Backup/Exception/FtpValidationFailed.php new file mode 100644 index 00000000..e1db5b2d --- /dev/null +++ b/lib/Mage/Backup/Exception/FtpValidationFailed.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_FtpValidationFailed extends Mage_Backup_Exception +{ +} diff --git a/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php b/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php new file mode 100644 index 00000000..87cede9b --- /dev/null +++ b/lib/Mage/Backup/Exception/NotEnoughFreeSpace.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_NotEnoughFreeSpace extends Mage_Backup_Exception +{ +} diff --git a/lib/Mage/Backup/Exception/NotEnoughPermissions.php b/lib/Mage/Backup/Exception/NotEnoughPermissions.php new file mode 100644 index 00000000..dec4fdb5 --- /dev/null +++ b/lib/Mage/Backup/Exception/NotEnoughPermissions.php @@ -0,0 +1,36 @@ + + */ +class Mage_Backup_Exception_NotEnoughPermissions extends Mage_Backup_Exception +{ +} diff --git a/lib/Mage/Backup/Filesystem.php b/lib/Mage/Backup/Filesystem.php new file mode 100644 index 00000000..a8824aff --- /dev/null +++ b/lib/Mage/Backup/Filesystem.php @@ -0,0 +1,284 @@ + + */ +class Mage_Backup_Filesystem extends Mage_Backup_Abstract +{ + /** + * Paths that ignored when creating or rolling back snapshot + * + * @var array + */ + protected $_ignorePaths = array(); + + /** + * Whether use ftp account for rollback procedure + * + * @var bool + */ + protected $_useFtp = false; + + /** + * Ftp host + * + * @var string + */ + protected $_ftpHost; + + /** + * Ftp username + * + * @var string + */ + protected $_ftpUser; + + /** + * Password to ftp account + * + * @var string + */ + protected $_ftpPass; + + /** + * Ftp path to Magento installation + * + * @var string + */ + protected $_ftpPath; + + /** + * Implementation Rollback functionality for Filesystem + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_lastOperationSucceed = false; + + set_time_limit(0); + ignore_user_abort(true); + + $rollbackWorker = $this->_useFtp ? new Mage_Backup_Filesystem_Rollback_Ftp($this) + : new Mage_Backup_Filesystem_Rollback_Fs($this); + $rollbackWorker->run(); + + $this->_lastOperationSucceed = true; + } + + /** + * Implementation Create Backup functionality for Filesystem + * + * @throws Mage_Exception + * @return boolean + */ + public function create() + { + set_time_limit(0); + ignore_user_abort(true); + + $this->_lastOperationSucceed = false; + + $this->_checkBackupsDir(); + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $filesInfo = $fsHelper->getInfo( + $this->getRootDir(), + Mage_Backup_Filesystem_Helper::INFO_READABLE | Mage_Backup_Filesystem_Helper::INFO_SIZE, + $this->getIgnorePaths() + ); + + if (!$filesInfo['readable']) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Not enough permissions to read files for backup'); + } + + $freeSpace = disk_free_space($this->getBackupsDir()); + + if (2 * $filesInfo['size'] > $freeSpace) { + throw new Mage_Backup_Exception_NotEnoughFreeSpace('Not enough free space to create backup'); + } + + $tarTmpPath = $this->_getTarTmpPath(); + + $tarPacker = new Mage_Backup_Archive_Tar(); + $tarPacker->setSkipFiles($this->getIgnorePaths()) + ->pack($this->getRootDir(), $tarTmpPath, true); + + if (!is_file($tarTmpPath) || filesize($tarTmpPath) == 0) { + throw new Mage_Exception('Failed to create backup'); + } + + $backupPath = $this->getBackupPath(); + + $gzPacker = new Mage_Archive_Gz(); + $gzPacker->pack($tarTmpPath, $backupPath); + + if (!is_file($backupPath) || filesize($backupPath) == 0) { + throw new Mage_Exception('Failed to create backup'); + } + + @unlink($tarTmpPath); + + $this->_lastOperationSucceed = true; + } + + /** + * Force class to use ftp for rollback procedure + * + * @param string $host + * @param string $username + * @param string $password + * @param string $path + * @return Mage_Backup_Filesystem + */ + public function setUseFtp($host, $username, $password, $path) + { + $this->_useFtp = true; + $this->_ftpHost = $host; + $this->_ftpUser = $username; + $this->_ftpPass = $password; + $this->_ftpPath = $path; + return $this; + } + + /** + * Get backup type + * + * @see Mage_Backup_Interface::getType() + * @return string + */ + public function getType() + { + return 'filesystem'; + } + + /** + * Add path that should be ignoring when creating or rolling back backup + * + * @param string|array $paths + * @return Mage_Backup_Filesystem + */ + public function addIgnorePaths($paths) + { + if (is_string($paths)) { + if (!in_array($paths, $this->_ignorePaths)) { + $this->_ignorePaths[] = $paths; + } + } + else if (is_array($paths)) { + foreach ($paths as $path) { + $this->addIgnorePaths($path); + } + } + + return $this; + } + + /** + * Get paths that should be ignored while creating or rolling back backup procedure + * + * @return array + */ + public function getIgnorePaths() + { + return $this->_ignorePaths; + } + + /** + * Set directory where backups saved and add it to ignore paths + * + * @see Mage_Backup_Abstract::setBackupsDir() + * @param string $backupsDir + * @return Mage_Backup_Filesystem + */ + public function setBackupsDir($backupsDir) + { + parent::setBackupsDir($backupsDir); + $this->addIgnorePaths($backupsDir); + return $this; + } + + /** + * getter for $_ftpPath variable + * + * @return string + */ + public function getFtpPath() + { + return $this->_ftpPath; + } + + /** + * Get ftp connection string + * + * @return string + */ + public function getFtpConnectString() + { + return 'ftp://' . $this->_ftpUser . ':' . $this->_ftpPass . '@' . $this->_ftpHost . $this->_ftpPath; + } + + /** + * Check backups directory existance and whether it's writeable + * + * @throws Mage_Exception + */ + protected function _checkBackupsDir() + { + $backupsDir = $this->getBackupsDir(); + + if (!is_dir($backupsDir)) { + $backupsDirParentDirectory = basename($backupsDir); + + if (!is_writeable($backupsDirParentDirectory)) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Cant create backups directory'); + } + + mkdir($backupsDir); + chmod($backupsDir, 0777); + } + + if (!is_writable($backupsDir)) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Backups directory is not writeable'); + } + } + + /** + * Generate tmp name for tarball + */ + protected function _getTarTmpPath() + { + $tmpName = '~tmp-'. microtime(true) . '.tar'; + return $this->getBackupsDir() . DS . $tmpName; + } +} diff --git a/lib/Mage/Backup/Filesystem/Helper.php b/lib/Mage/Backup/Filesystem/Helper.php new file mode 100644 index 00000000..8403779b --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Helper.php @@ -0,0 +1,137 @@ + + */ +class Mage_Backup_Filesystem_Helper +{ + /** + * Constant can be used in getInfo() function as second parameter. + * Check whether directory and all files/sub directories are writable + * + * @const int + */ + const INFO_WRITABLE = 1; + + /** + * Constant can be used in getInfo() function as second parameter. + * Check whether directory and all files/sub directories are readable + * + * @const int + */ + const INFO_READABLE = 2; + + /** + * Constant can be used in getInfo() function as second parameter. + * Get directory size + * + * @const int + */ + const INFO_SIZE = 4; + + /** + * Constant can be used in getInfo() function as second parameter. + * Combination of INFO_WRITABLE, INFO_READABLE, INFO_SIZE + * + * @const int + */ + const INFO_ALL = 7; + + /** + * Recursively delete $path + * + * @param string $path + * @param array $skipPaths + * @param bool $removeRoot + * @throws Mage_Exception + */ + public function rm($path, $skipPaths = array(), $removeRoot = false) + { + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $skipPaths); + + foreach ($iterator as $item) { + $item->isDir() ? @rmdir($item->__toString()) : @unlink($item->__toString()); + } + + if ($removeRoot && is_dir($path)) { + @rmdir($path); + } + } + + /** + * Get information (readable, writable, size) about $path + * + * @param string $path + * @param int $infoOptions + * @param array $skipFiles + */ + public function getInfo($path, $infoOptions = self::INFO_ALL, $skipFiles = array()) + { + $info = array(); + if ($infoOptions & self::INFO_READABLE) { + $info['readable'] = true; + } + + if ($infoOptions & self::INFO_WRITABLE) { + $info['writable'] = true; + } + + if ($infoOptions & self::INFO_SIZE) { + $info['size'] = 0; + } + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $skipFiles); + + foreach ($iterator as $item) { + if (($infoOptions & self::INFO_WRITABLE) && !$item->isWritable()) { + $info['writable'] = false; + } + + if (($infoOptions & self::INFO_READABLE) && !$item->isReadable()) { + $info['readable'] = false; + } + + if ($infoOptions & self::INFO_SIZE && !$item->isDir()) { + $info['size'] += $item->getSize(); + } + } + + return $info; + } +} diff --git a/lib/Mage/Backup/Filesystem/Iterator/File.php b/lib/Mage/Backup/Filesystem/Iterator/File.php new file mode 100644 index 00000000..5e48a9fd --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Iterator/File.php @@ -0,0 +1,112 @@ + + */ +class Mage_Backup_Filesystem_Iterator_File extends SplFileObject +{ + /** + * The statement that was last read during iteration + * + * @var string + */ + protected $_currentStatement = ''; + + /** + * Return current sql statement + * + * @return string + */ + public function current() + { + return $this->_currentStatement; + } + + /** + * Iterate to next sql statement in file + */ + public function next() + { + $this->_currentStatement = ''; + while (!$this->eof()) { + $line = $this->fgets(); + if (strlen(trim($line))) { + $this->_currentStatement .= $line; + if ($this->_isLineLastInCommand($line)) { + break; + } + } + } + } + + /** + * Return to first statement + */ + public function rewind() + { + parent::rewind(); + $this->next(); + } + + /** + * Check whether provided string is comment + * + * @param string $line + * @return bool + */ + protected function _isComment($line) + { + return $line[0] == '#' || substr($line, 0, 2) == '--'; + } + + /** + * Check is line a last in sql command + * + * @param string $line + * @return bool + */ + protected function _isLineLastInCommand($line) + { + $cleanLine = trim($line); + $lineLength = strlen($cleanLine); + + $returnResult = false; + if ($lineLength > 0) { + $lastSymbolIndex = $lineLength - 1; + if ($cleanLine[$lastSymbolIndex] == ';') { + $returnResult = true; + } + } + + return $returnResult; + } +} diff --git a/lib/Mage/Backup/Filesystem/Iterator/Filter.php b/lib/Mage/Backup/Filesystem/Iterator/Filter.php new file mode 100644 index 00000000..8fb432f9 --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Iterator/Filter.php @@ -0,0 +1,77 @@ + + */ +class Mage_Backup_Filesystem_Iterator_Filter extends FilterIterator +{ + /** + * Array that is used for filtering + * + * @var array + */ + protected $_filters; + + /** + * Constructor + * + * @param Iterator $iterator + * @param array $filters list of files to skip + */ + public function __construct(Iterator $iterator, array $filters) + { + parent::__construct($iterator); + $this->_filters = $filters; + } + + /** + * Check whether the current element of the iterator is acceptable + * + * @return bool + */ + public function accept() + { + $current = $this->current()->__toString(); + $currentFilename = $this->current()->getFilename(); + + if ($currentFilename == '.' || $currentFilename == '..') { + return false; + } + + foreach ($this->_filters as $filter) { + if (false !== strpos($current, $filter)) { + return false; + } + } + + return true; + } +} diff --git a/lib/Mage/Backup/Filesystem/Rollback/Abstract.php b/lib/Mage/Backup/Filesystem/Rollback/Abstract.php new file mode 100644 index 00000000..d34f90a4 --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Rollback/Abstract.php @@ -0,0 +1,57 @@ + + */ +abstract class Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Snapshot object + * + * @var Mage_Backup_Filesystem + */ + protected $_snapshot; + + /** + * Default worker constructor + * + * @param Mage_Backup_Filesystem $snapshotObject + */ + public function __construct(Mage_Backup_Filesystem $snapshotObject) + { + $this->_snapshot = $snapshotObject; + } + + /** + * Main worker's function that makes files rollback + */ + abstract public function run(); +} diff --git a/lib/Mage/Backup/Filesystem/Rollback/Fs.php b/lib/Mage/Backup/Filesystem/Rollback/Fs.php new file mode 100644 index 00000000..599b85cc --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Rollback/Fs.php @@ -0,0 +1,78 @@ + + */ +class Mage_Backup_Filesystem_Rollback_Fs extends Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Files rollback implementation via local filesystem + * + * @see Mage_Backup_Filesystem_Rollback_Abstract::run() + * @throws Mage_Exception + */ + public function run() + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + if (!is_file($snapshotPath) || !is_readable($snapshotPath)) { + throw new Mage_Backup_Exception_CantLoadSnapshot('Cant load snapshot archive'); + } + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $filesInfo = $fsHelper->getInfo( + $this->_snapshot->getRootDir(), + Mage_Backup_Filesystem_Helper::INFO_WRITABLE, + $this->_snapshot->getIgnorePaths() + ); + + if (!$filesInfo['writable']) { + throw new Mage_Backup_Exception_NotEnoughPermissions( + 'Unable to make rollback because not all files are writable' + ); + } + + $archiver = new Mage_Archive(); + + /** + * we need these fake initializations because all magento's files in filesystem will be deleted and autoloader + * wont be able to load classes that we need for unpacking + */ + new Mage_Archive_Tar(); + new Mage_Archive_Gz(); + new Mage_Archive_Helper_File(''); + new Mage_Archive_Helper_File_Gz(''); + + $fsHelper->rm($this->_snapshot->getRootDir(), $this->_snapshot->getIgnorePaths()); + $archiver->unpack($snapshotPath, $this->_snapshot->getRootDir()); + } +} diff --git a/lib/Mage/Backup/Filesystem/Rollback/Ftp.php b/lib/Mage/Backup/Filesystem/Rollback/Ftp.php new file mode 100644 index 00000000..06c45628 --- /dev/null +++ b/lib/Mage/Backup/Filesystem/Rollback/Ftp.php @@ -0,0 +1,198 @@ + + */ +class Mage_Backup_Filesystem_Rollback_Ftp extends Mage_Backup_Filesystem_Rollback_Abstract +{ + /** + * Ftp client + * + * @var Mage_System_Ftp + */ + protected $_ftpClient; + + /** + * Files rollback implementation via ftp + * + * @see Mage_Backup_Filesystem_Rollback_Abstract::run() + * @throws Mage_Exception + */ + public function run() + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + if (!is_file($snapshotPath) || !is_readable($snapshotPath)) { + throw new Mage_Backup_Exception_CantLoadSnapshot('Cant load snapshot archive'); + } + + $this->_initFtpClient(); + $this->_validateFtp(); + + $tmpDir = $this->_createTmpDir(); + $this->_unpackSnapshot($tmpDir); + + $fsHelper = new Mage_Backup_Filesystem_Helper(); + + $this->_cleanupFtp(); + $this->_uploadBackupToFtp($tmpDir); + + $fsHelper->rm($tmpDir, array(), true); + } + + /** + * Initialize ftp client and connect to ftp + * + * @throws Mage_Backup_Exception_FtpConnectionFailed + */ + protected function _initFtpClient() + { + try { + $this->_ftpClient = new Mage_System_Ftp(); + $this->_ftpClient->connect($this->_snapshot->getFtpConnectString()); + } catch (Exception $e) { + throw new Mage_Backup_Exception_FtpConnectionFailed($e->getMessage()); + } + } + + /** + * Perform ftp validation. Check whether ftp account provided points to current magento installation + * + * @throws Mage_Exception + */ + protected function _validateFtp() + { + $validationFilename = '~validation-' . microtime(true) . '.tmp'; + $validationFilePath = $this->_snapshot->getBackupsDir() . DS . $validationFilename; + + $fh = @fopen($validationFilePath, 'w'); + @fclose($fh); + + if (!is_file($validationFilePath)) { + throw new Mage_Exception('Unable to validate ftp account'); + } + + $rootDir = $this->_snapshot->getRootDir(); + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($rootDir, '', $validationFilePath); + + $fileExistsOnFtp = $this->_ftpClient->fileExists($ftpPath); + @unlink($validationFilePath); + + if (!$fileExistsOnFtp) { + throw new Mage_Backup_Exception_FtpValidationFailed('Failed to validate ftp account'); + } + } + + /** + * Unpack snapshot + * + * @param string $tmpDir + */ + protected function _unpackSnapshot($tmpDir) + { + $snapshotPath = $this->_snapshot->getBackupPath(); + + $archiver = new Mage_Archive(); + $archiver->unpack($snapshotPath, $tmpDir); + } + + /** + * @throws Mage_Exception + * @return string + */ + protected function _createTmpDir() + { + $tmpDir = $this->_snapshot->getBackupsDir() . DS . '~tmp-' . microtime(true); + + $result = @mkdir($tmpDir); + + if (false === $result) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Failed to create directory ' . $tmpDir); + } + + return $tmpDir; + } + + /** + * Delete magento and all files from ftp + */ + protected function _cleanupFtp() + { + $rootDir = $this->_snapshot->getRootDir(); + + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($rootDir), RecursiveIteratorIterator::CHILD_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_snapshot->getIgnorePaths()); + + foreach ($iterator as $item) { + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($rootDir, '', $item->__toString()); + $ftpPath = str_replace(DS, '/', $ftpPath); + + $this->_ftpClient->delete($ftpPath); + } + } + + /** + * Perform files rollback + * + * @param string $tmpDir + * @throws Mage_Exception + */ + protected function _uploadBackupToFtp($tmpDir) + { + $filesystemIterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($tmpDir), RecursiveIteratorIterator::SELF_FIRST + ); + + $iterator = new Mage_Backup_Filesystem_Iterator_Filter($filesystemIterator, $this->_snapshot->getIgnorePaths()); + + foreach ($filesystemIterator as $item) { + $ftpPath = $this->_snapshot->getFtpPath() . DS . str_replace($tmpDir, '', $item->__toString()); + $ftpPath = str_replace(DS, '/', $ftpPath); + + if ($item->isLink()) { + continue; + } + + if ($item->isDir()) { + $this->_ftpClient->mkdirRecursive($ftpPath); + } else { + $result = $this->_ftpClient->put($ftpPath, $item->__toString()); + if (false === $result) { + throw new Mage_Backup_Exception_NotEnoughPermissions('Failed to upload file ' + . $item->__toString() . ' to ftp'); + } + } + } + } +} diff --git a/lib/Mage/Backup/Interface.php b/lib/Mage/Backup/Interface.php new file mode 100644 index 00000000..a4a8d885 --- /dev/null +++ b/lib/Mage/Backup/Interface.php @@ -0,0 +1,88 @@ + + */ +interface Mage_Backup_Interface +{ + /** + * Create Backup + * + * @return boolean + */ + public function create(); + + /** + * Rollback Backup + * + * @return boolean + */ + public function rollback(); + + /** + * Set Backup Extension + * + * @param string $backupExtension + * @return Mage_Backup_Interface + */ + public function setBackupExtension($backupExtension); + + /** + * Set Resource Model + * + * @param object $resourceModel + * @return Mage_Backup_Interface + */ + public function setResourceModel($resourceModel); + + /** + * Set Time + * + * @param int $time + * @return Mage_Backup_Interface + */ + public function setTime($time); + + /** + * Get Backup Type + * + * @return string + */ + public function getType(); + + /** + * Set path to directory where backups stored + * + * @param string $backupsDir + * @return Mage_Backup_Interface + */ + public function setBackupsDir($backupsDir); +} diff --git a/lib/Mage/Backup/Media.php b/lib/Mage/Backup/Media.php new file mode 100644 index 00000000..b833ac6e --- /dev/null +++ b/lib/Mage/Backup/Media.php @@ -0,0 +1,99 @@ + + */ +class Mage_Backup_Media extends Mage_Backup_Snapshot +{ + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_prepareIgnoreList(); + return parent::rollback(); + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_prepareIgnoreList(); + return parent::create(); + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'media'; + } + + /** + * Add all folders and files except media and db backup to ignore list + * + * @return Mage_Backup_Media + */ + protected function _prepareIgnoreList() + { + $iterator = new DirectoryIterator($this->getRootDir()); + + foreach ($iterator as $item) { + $filename = $item->getFilename(); + if (!in_array($filename, array('media', 'var'))) { + $this->addIgnorePaths($item->getPathname()); + } + } + + $iterator = new DirectoryIterator($this->getRootDir() . DS . 'var'); + $dbBackupFilename = $this->_getDbBackupManager()->getBackupFilename(); + + foreach ($iterator as $item) { + $filename = $item->getFilename(); + if ($filename != $dbBackupFilename) { + $this->addIgnorePaths($item->getPathname()); + } + } + + return $this; + } +} diff --git a/lib/Mage/Backup/Nomedia.php b/lib/Mage/Backup/Nomedia.php new file mode 100644 index 00000000..dbdc8f66 --- /dev/null +++ b/lib/Mage/Backup/Nomedia.php @@ -0,0 +1,82 @@ + + */ +class Mage_Backup_Nomedia extends Mage_Backup_Snapshot +{ + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $this->_prepareIgnoreList(); + return parent::rollback(); + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_prepareIgnoreList(); + return parent::create(); + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'nomedia'; + } + + /** + * Add media folder to ignore list + * + * @return Mage_Backup_Media + */ + protected function _prepareIgnoreList() + { + $this->addIgnorePaths($this->getRootDir() . DS . 'media'); + + return $this; + } +} diff --git a/lib/Mage/Backup/Snapshot.php b/lib/Mage/Backup/Snapshot.php new file mode 100644 index 00000000..68b41695 --- /dev/null +++ b/lib/Mage/Backup/Snapshot.php @@ -0,0 +1,140 @@ + + */ +class Mage_Backup_Snapshot extends Mage_Backup_Filesystem +{ + /** + * Database backup manager + * + * @var Mage_Backup_Db + */ + protected $_dbBackupManager; + + /** + * Implementation Rollback functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function rollback() + { + $result = parent::rollback(); + + $this->_lastOperationSucceed = false; + + try { + $this->_getDbBackupManager()->rollback(); + } catch (Exception $e) { + $this->_removeDbBackup(); + throw $e; + } + + $this->_removeDbBackup(); + $this->_lastOperationSucceed = true; + + return $result; + } + + /** + * Implementation Create Backup functionality for Snapshot + * + * @throws Mage_Exception + * @return bool + */ + public function create() + { + $this->_getDbBackupManager()->create(); + + try { + $result = parent::create(); + } catch (Exception $e) { + $this->_removeDbBackup(); + throw $e; + } + + $this->_lastOperationSucceed = false; + $this->_removeDbBackup(); + $this->_lastOperationSucceed = true; + + return $result; + } + + /** + * Overlap getType + * + * @return string + * @see Mage_Backup_Interface::getType() + */ + public function getType() + { + return 'snapshot'; + } + + /** + * Create Db Instance + * + * @return Mage_Backup_Interface + */ + protected function _createDbBackupInstance() + { + return Mage_Backup::getBackupInstance(Mage_Backup_Helper_Data::TYPE_DB) + ->setBackupExtension(Mage::helper('backup')->getExtensionByType(Mage_Backup_Helper_Data::TYPE_DB)) + ->setTime($this->getTime()) + ->setBackupsDir(Mage::getBaseDir("var")) + ->setResourceModel($this->getResourceModel()); + } + + /** + * Get database backup manager + * + * @return Mage_Backup_Db + */ + protected function _getDbBackupManager() + { + if (is_null($this->_dbBackupManager)) { + $this->_dbBackupManager = $this->_createDbBackupInstance(); + } + + return $this->_dbBackupManager; + } + + /** + * Remove Db backup after added it to the snapshot + * + * @return Mage_Backup_Snapshot + */ + protected function _removeDbBackup(){ + @unlink($this->_getDbBackupManager()->getBackupPath()); + return $this; + } +} diff --git a/lib/Mage/Connect/Channel/Generator.php b/lib/Mage/Connect/Channel/Generator.php new file mode 100644 index 00000000..663b0e9b --- /dev/null +++ b/lib/Mage/Connect/Channel/Generator.php @@ -0,0 +1,63 @@ +_file = $file; + } + return $this; + } + + public function getFile() + { + return $this->_file; + } + + public function getGenerator() + { + if (is_null($this->_generator)) { + $this->_generator = new Mage_Xml_Generator(); + } + return $this->_generator; + } + + /** + * @param array $content + */ + public function save($content) + { + $xmlContent = $this->getGenerator() + ->arrayToXml($content) + ->save($this->getFile()); + return $this; + } +} \ No newline at end of file diff --git a/lib/Mage/Connect/Channel/Parser.php b/lib/Mage/Connect/Channel/Parser.php new file mode 100644 index 00000000..0088176d --- /dev/null +++ b/lib/Mage/Connect/Channel/Parser.php @@ -0,0 +1,25 @@ + '', + 'uri' => '', + 'summary' => '', + ); + + public function rewind() { + reset($this->properties); + } + + public function valid() { + return current($this->properties) !== false; + } + + public function key() { + return key($this->properties); + } + + public function current() { + return current($this->properties); + } + + public function next() { + next($this->properties); + } + + public function __get($var) + { + if (isset($this->properties[$var])) { + return $this->properties[$var]; + } + return null; + } + + public function __set($var, $value) + { + if (is_string($value)) { + $value = trim($value); + } + if (isset($this->properties[$var])) { + if ($value === null) { + $value = ''; + } + $this->properties[$var] = $value; + } + } + + public function toArray() + { + return array('channel' => $this->properties); + } + + public function fromArray(array $arr) + { + foreach($arr as $k=>$v) { + $this->$k = $v; + } + } + + + private function validator() + { + if(is_null($this->_validator)) { + $this->_validator = new Mage_Connect_Validator(); + } + return $this->_validator; + } + + /** + Stub for validation result + */ + public function validate() + { + $v = $this->validator(); + if(!$v->validatePackageName($this->name)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Command.php b/lib/Mage/Connect/Command.php new file mode 100644 index 00000000..2cd9ece6 --- /dev/null +++ b/lib/Mage/Connect/Command.php @@ -0,0 +1,390 @@ +_class = get_class($this); + if(__CLASS__ == $class) { + throw new Exception("You shouldn't instantiate {$class} directly!"); + } + $this->commandsInfo = self::$_commandsByClass[$class]; + } + + + /** + * Get command info (static) + * @param string $name command name + * @return array/bool + */ + public static function commandInfo($name) + { + $name = strtolower($name); + if(!isset(self::$_commandsAll[$name])) { + return false; + } + return self::$_commandsAll[$name]; + } + + /** + * Get command info for current command object + * @param string $name + * @return array/bool + */ + + public function getCommandInfo($name) + { + if(!isset(self::$_commandsByClass[$this->_class][$name])) { + return false; + } + return self::$_commandsByClass[$this->_class][$name]; + } + + /** + * Run command + * @param string $command + * @param string $options + * @param string $params + * @throws Exception if there's no needed method + * @return mixed + */ + public function run($command, $options, $params) + { + $data = $this->getCommandInfo($command); + $method = $data['function']; + if(! method_exists($this, $method)) { + throw new Exception("$method does't exist in class ".$this->_class); + } + return $this->$method($command, $options, $params); + } + + /** + * Static functions + */ + + /** + * Static + * @param $commandName + * @return unknown_type + */ + public static function getInstance($commandName) + { + if(!isset(self::$_commandsAll[$commandName])) { + throw new UnexpectedValueException("Cannot find command $commandName"); + } + $currentCommand = self::$_commandsAll[$commandName]; + return new $currentCommand['class'](); + } + + + public static function setSconfig($obj) + { + self::$_sconfig = $obj; + } + + /** + * + * @return Mage_Connect_Singleconfig + */ + public function getSconfig() + { + return self::$_sconfig; + } + + + /** + * Sets frontend object for all commands + * + * @param Mage_Connect_Frontend $obj + * @return void + */ + public static function setFrontendObject($obj) + { + self::$_frontend = $obj; + } + + + /** + * Set config object for all commands + * @param Mage_Connect_Config $obj + * @return void + */ + public static function setConfigObject($obj) + { + self::$_config = $obj; + } + + + /** + * Non-static getter for config + * @return Mage_Connect_Config + */ + public function config() + { + return self::$_config; + } + + /** + * Non-static getter for UI + * @return Mage_Connect_Frontend + */ + public function ui() + { + return self::$_frontend; + } + + + /** + * Get validator object + * @return Mage_Connect_Validator + */ + public function validator() + { + if(is_null(self::$_validator)) { + self::$_validator = new Mage_Connect_Validator(); + } + return self::$_validator; + } + + /** + * Get rest object + * @return Mage_Connect_Rest + */ + public function rest() + { + if(is_null(self::$_rest)) { + self::$_rest = new Mage_Connect_Rest(self::config()->protocol); + } + return self::$_rest; + } + + + /** + * Get commands list sorted + * @return array + */ + public static function getCommands() + { + if(!count(self::$_commandsAll)) { + self::registerCommands(); + } + ksort(self::$_commandsAll); + return self::$_commandsAll; + } + + + /** + * Get Getopt args from command definitions + * and parse them + * @param $command + * @return array + */ + public static function getGetoptArgs($command) + { + $commandInfo = self::commandInfo($command); + $short_args = ''; + $long_args = array(); + if (empty($commandInfo) || empty($commandInfo['options'])) { + return; + } + reset($commandInfo['options']); + while (list($option, $info) = each($commandInfo['options'])) { + $larg = $sarg = ''; + if (isset($info['arg'])) { + if ($info['arg']{0} == '(') { + $larg = '=='; + $sarg = '::'; + $arg = substr($info['arg'], 1, -1); + } else { + $larg = '='; + $sarg = ':'; + $arg = $info['arg']; + } + } + if (isset($info['shortopt'])) { + $short_args .= $info['shortopt'] . $sarg; + } + $long_args[] = $option . $larg; + } + return array($short_args, $long_args); + } + + /** + * Try to register commands automatically + * @return void + */ + public static function registerCommands() + { + $pathCommands = dirname(__FILE__).DIRECTORY_SEPARATOR.basename(__FILE__, ".php"); + $f = new DirectoryIterator($pathCommands); + foreach($f as $file) { + if (! $file->isFile()) { + continue; + } + $pattern = preg_match("/(.*)_Header\.php/imsu", $file->getFilename(), $matches); + if(! $pattern) { + continue; + } + include($file->getPathname()); + if(! isset($commands)) { + continue; + } + $class = __CLASS__."_".$matches[1]; + foreach ($commands as $k=>$v) { + $commands[$k]['class'] = $class; + self::$_commandsAll[$k] = $commands[$k]; + } + self::$_commandsByClass[$class] = $commands; + } + } + + public function doError($command, $message) + { + return $this->ui()->doError($command, $message); + } + + + /** + * Set command return + * @param string $key + * @param mixed $val + * @return void + */ + public static function setReturn($key, $val) + { + self::$_return[$key] = $val; + } + + /** + * Get command return + * @param $key + * @param $clear + * @return mixed + */ + public static function getReturn($key, $clear = true) + { + if(isset(self::$_return[$key])) { + $out = self::$_return[$key]; + if($clear) { + unset(self::$_return[$key]); + } + return $out; + } + return null; + } + + /** + * Cleanup command params from empty strings + * + * @param array $params by reference + */ + public function cleanupParams(array & $params) + { + $newParams = array(); + if(!count($params)) { + return; + } + foreach($params as $k=>$v) { + if(is_string($v)) { + $v = trim($v); + if(!strlen($v)) { + continue; + } + } + $newParams[] = $v; + } + $params = $newParams; + } + + /** + * Splits first command argument: channel/package + * to two arguments if found in top of array + * + * @param array $params + */ + public function splitPackageArgs(array & $params) + { + if(!count($params) || !isset($params[0])) { + return; + } + if($this->validator()->validateUrl($params[0])) { + return; + } + if(preg_match("@([a-zA-Z0-9_]+)/([a-zA-Z0-9_]+)@ims", $params[0], $subs)) { + $params[0] = $subs[2]; + array_unshift($params, $subs[1]); + } + } + + + /** + * Get packager instance + * @return Mage_Connect_Pacakger + */ + public function getPackager() + { + if(!self::$_packager) { + self::$_packager = new Mage_Connect_Packager(); + } + return self::$_packager; + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Channels.php b/lib/Mage/Connect/Command/Channels.php new file mode 100644 index 00000000..e0638966 --- /dev/null +++ b/lib/Mage/Connect/Command/Channels.php @@ -0,0 +1,189 @@ +getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $data = $cache->getData(); + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $data = $cache->getData(); + } + $out = array($command => array('data'=>$data, 'title'=>$title, 'title_aliases'=>$aliasT)); + $this->ui()->output($out); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * channel-delete callback method + * @param string $command + * @param array $options + * @param array $params + */ + public function doDelete($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 1) { + throw new Exception("Parameters count should be equal to 1"); + } + $packager = $this->getPackager(); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->deleteChannel($params[0]); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $config = $this->config(); + $cache = $this->getSconfig(); + $cache->deleteChannel($params[0]); + } + $this->ui()->output("Successfully deleted"); + + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Channel-add callback + * @param string $command + * @param array $options + * @param array $params + */ + public function doAdd($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 1) { + throw new Exception("Parameters count should be equal to 1"); + } + $url = $params[0]; + $rest = $this->rest(); + $rest->setChannel($url); + $data = $rest->getChannelInfo(); + $data->url = $url; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->addChannel($data->name, $url); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $cache->addChannel($data->name, $url); + } + + $this->ui()->output("Successfully added: ".$url); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + /** + * Get information about given channel callback + * @param string $command + * @param array $options + * @param array $params + */ + public function doInfo($command, $options, $params) + { + + } + + /** + * channel-alias + * @param $command + * @param $options + * @param $params + * @return unknown_type + */ + public function doAlias($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) != 2) { + throw new Exception("Parameters count should be equal to 2"); + } + + $packager = $this->getPackager(); + $chanUrl = $params[0]; + $alias = $params[1]; + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + $cache->addChannelAlias($chanUrl, $alias); + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + $cache->addChannelAlias($chanUrl, $alias); + } + $this->ui()->output("Successfully added: ".$alias); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + public function doLogin($command, $options, $params) + { + + } + + public function doLogout($command, $options, $params) + { + + } + +} diff --git a/lib/Mage/Connect/Command/Channels_Header.php b/lib/Mage/Connect/Command/Channels_Header.php new file mode 100644 index 00000000..823bfc6f --- /dev/null +++ b/lib/Mage/Connect/Command/Channels_Header.php @@ -0,0 +1,105 @@ + array( + 'summary' => 'List Available Channels', + 'function' => 'doList', + 'shortcut' => 'lc', + 'options' => array(), + 'doc' => ' +List all available channels for installation. +', + ), + 'channel-delete' => array( + 'summary' => 'Remove a Channel From the List', + 'function' => 'doDelete', + 'shortcut' => 'cde', + 'options' => array(), + 'doc' => ' +Delete a channel from the registry. You may not +remove any channel that has installed packages. +' + ), + 'channel-add' => array( + 'summary' => 'Add a Channel', + 'function' => 'doAdd', + 'shortcut' => 'ca', + 'options' => array(), + 'doc' => ' +Add a private channel to the channel list. Note that all +public channels should be synced using "update-channels". +Parameter may be either a local file or remote URL to a +channel.xml. +' + ), + 'channel-info' => array( + 'summary' => 'Retrieve Information on a Channel', + 'function' => 'doInfo', + 'shortcut' => 'ci', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'channel-alias' => array( + 'summary' => 'Specify an alias to a channel name', + 'function' => 'doAlias', + 'shortcut' => 'cha', + 'options' => array(), + 'doc' => ' +Specify a specific alias to use for a channel name. +The alias may not be an existing channel name or +alias. +' + ), + 'channel-login' => array( + 'summary' => 'Connects and authenticates to remote channel server', + 'shortcut' => 'cli', + 'function' => 'doLogin', + 'options' => array(), + 'doc' => ' +Log in to a remote channel server. If is not supplied, +the default channel is used. To use remote functions in the installer +that require any kind of privileges, you need to log in first. The +username and password you enter here will be stored in your per-user +PEAR configuration (~/.pearrc on Unix-like systems). After logging +in, your username and password will be sent along in subsequent +operations on the remote server.', + ), + 'channel-logout' => array( + 'summary' => 'Logs out from the remote channel server', + 'shortcut' => 'clo', + 'function' => 'doLogout', + 'options' => array(), + 'doc' => ' +Logs out from a remote channel server. If is not supplied, +the default channel is used. This command does not actually connect to the +remote server, it only deletes the stored username and password from your user +configuration.', + ), + ); + \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Config.php b/lib/Mage/Connect/Command/Config.php new file mode 100644 index 00000000..1265b009 --- /dev/null +++ b/lib/Mage/Connect/Command/Config.php @@ -0,0 +1,211 @@ +cleanupParams($params); + + try { + $values = array(); + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + foreach( $config as $k=>$v ) { + $values[$k] = $v; + } + if($ftp) { + @unlink($config->getFilename()); + } + $data = array($command => array('data'=>$values)); + $this->ui()->output($data); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + + /** + * Set config variable + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigSet($command, $options, $params) + { + $this->cleanupParams($params); + + try { + if(count($params) < 2) { + throw new Exception("Parameters count should be >= 2"); + } + $key = strtolower($params[self::PARAM_KEY]); + $val = strval($params[self::PARAM_VAL]); + $packager = $this->getPackager(); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + + if(!$config->hasKey($key)) { + throw new Exception ("No such config variable: {$key}!"); + } + if(!$config->validate($key, $val)) { + $possible = $this->config()->possible($key); + $type = $this->config()->type($key); + $errString = "Invalid value specified for $key!"; + throw new Exception($errString); + } + if($ftp) { + $packager->writeToRemoteConfig($config, $ftpObj); + } + $this->config()->$key = $val; + $this->ui()->output('Success'); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Get config var + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigGet($command, $options, $params) + { + $this->cleanupParams($params); + + try { + if(count($params) < 1) { + throw new Exception("Parameters count should be >= 1"); + } + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + $key = strtolower($params[self::PARAM_KEY]); + if(!$config->hasKey($key)) { + throw new Exception("No such config variable '{$key}'!"); + } + if($ftp) { + @unlink($config->getFilename()); + } + $this->ui()->output($config->$key); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Config help + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doConfigHelp($command, $options, $params) + { + try { + $this->cleanupParams($params); + if(count($params) < 1) { + throw new Exception( "Parameters count should be >= 1"); + } + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($config, $ftpObj) = $packager->getRemoteConfig($ftp); + } else { + $config = $this->config(); + } + + $key = strtolower($params[self::PARAM_KEY]); + if(!$this->config()->hasKey($key)) { + throw new Exception("No such config variable '{$key}'!"); + } + + $possible = $config->possible($key); + $type = $config->type($key); + $doc = $config->doc($key); + if($ftp) { + @unlink($config->getFilename()); + } + $data = array(); + $data[$command]['data'] = array( + 'name' => array('Variable name', $key), + 'type' => array('Value type', $type), + 'possible' => array('Possible values', $possible), + 'doc' => $doc, + ); + $this->ui()->output($data); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + +} + + diff --git a/lib/Mage/Connect/Command/Config_Header.php b/lib/Mage/Connect/Command/Config_Header.php new file mode 100644 index 00000000..94b0dd68 --- /dev/null +++ b/lib/Mage/Connect/Command/Config_Header.php @@ -0,0 +1,100 @@ + array( + 'summary' => 'Show All Settings', + 'function' => 'doConfigShow', + 'shortcut' => 'csh', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => '[layer] +Displays all configuration values. An optional argument +may be used to tell which configuration layer to display. Valid +configuration layers are "user", "system" and "default". To display +configurations for different channels, set the default_channel +configuration variable and run config-show again. +', +), + 'config-get' => array( + 'summary' => 'Show One Setting', + 'function' => 'doConfigGet', + 'shortcut' => 'cg', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => ' [layer] +Displays the value of one configuration parameter. The +first argument is the name of the parameter, an optional second argument +may be used to tell which configuration layer to look in. Valid configuration +layers are "user", "system" and "default". If no layer is specified, a value +will be picked from the first layer that defines the parameter, in the order +just specified. The configuration value will be retrieved for the channel +specified by the default_channel configuration variable. +', +), + 'config-set' => array( + 'summary' => 'Change Setting', + 'function' => 'doConfigSet', + 'shortcut' => 'cs', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'show configuration variables for another channel', + 'arg' => 'CHAN', +), +), + 'doc' => ' [layer] +Sets the value of one configuration parameter. The first argument is +the name of the parameter, the second argument is the new value. Some +parameters are subject to validation, and the command will fail with +an error message if the new value does not make sense. An optional +third argument may be used to specify in which layer to set the +configuration parameter. The default layer is "user". The +configuration value will be set for the current channel, which +is controlled by the default_channel configuration variable. +', +), + 'config-help' => array( + 'summary' => 'Show Information About Setting', + 'function' => 'doConfigHelp', + 'shortcut' => 'ch', + 'options' => array(), + 'doc' => '[parameter] +Displays help for a configuration parameter. Without arguments it +displays help for all configuration parameters. +', +), +); \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Install.php b/lib/Mage/Connect/Command/Install.php new file mode 100644 index 00000000..2dd4b96a --- /dev/null +++ b/lib/Mage/Connect/Command/Install.php @@ -0,0 +1,448 @@ +cleanupParams($params); + + $installFileMode = $command === 'install-file'; + + + + + try { + $packager = $this->getPackager(); + $forceMode = isset($options['force']); + $upgradeAllMode = $command == 'upgrade-all'; + $upgradeMode = $command == 'upgrade' || $command == 'upgrade-all'; + $noFilesInstall = isset($options['nofiles']); + $withDepsMode = !isset($options['nodeps']); + $ignoreModifiedMode = true || !isset($options['ignorelocalmodification']); + + $rest = $this->rest(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $config = $this->config(); + $cache = $this->getSconfig(); + } + if($installFileMode) { + if(count($params) < 1) { + throw new Exception("Argument should be: filename"); + } + $filename = $params[0]; + if(!@file_exists($filename)) { + throw new Exception("File '{$filename}' not found"); + } + if(!@is_readable($filename)) { + throw new Exception("File '{$filename}' is not readable"); + } + + $package = new Mage_Connect_Package($filename); + $package->validate(); + $errors = $package->getErrors(); + if(count($errors)) { + throw new Exception("Package file is invalid\n".implode("\n", $errors)); + } + + $pChan = $package->getChannel(); + $pName = $package->getName(); + $pVer = $package->getVersion(); + + + if(!$cache->isChannel($pChan)) { + throw new Exception("'{$pChan}' is not installed channel"); + } + + $conflicts = $cache->hasConflicts($pChan, $pName, $pVer); + + if(false !== $conflicts) { + $conflicts = implode(", ",$conflicts); + if($forceMode) { + $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts); + } else { + throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts); + } + } + + $conflicts = $package->checkPhpDependencies(); + if(true !== $conflicts) { + $confilcts = implode(",",$conflicts); + $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: ".$conflicts; + if($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + $conflicts = $package->checkPhpVersion(); + if(true !== $conflicts) { + $err = "Package {$pChan}/{$pName} {$pVer}: ".$conflicts; + if($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + + if(!$noFilesInstall) { + if($ftp) { + $packager->processInstallPackageFtp($package, $filename, $config, $ftpObj); + } else { + $packager->processInstallPackage($package, $filename, $config); + } + } + $cache->addPackage($package); + $installedDeps = array(); + $installedDepsAssoc = array(); + $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer); + $installedDeps[] = array($pChan, $pName, $pVer); + + + $title = isset($options['title']) ? $options['title'] : "Package installed: "; + $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc, 'title'=>$title)); + + if($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + + $this->ui()->output($out); + return $out[$command]['data']; + } + + if(!$upgradeAllMode) { + + if(count($params) < 2) { + throw new Exception("Argument should be: channelName packageName"); + } + $channel = $params[0]; + $package = $params[1]; + $argVersionMax = isset($params[2]) ? $params[2]: false; + $argVersionMin = false; + + if($cache->isChannelName($channel)) { + $uri = $cache->chanUrl($channel); + } elseif($this->validator()->validateUrl($channel)) { + $uri = $channel; + } elseif($channel) { + $uri = $config->protocol.'://'.$channel; + } else { + throw new Exception("'{$channel}' is not existant channel name / valid uri"); + } + + if($uri && !$cache->isChannel($uri)) { + $rest->setChannel($uri); + $data = $rest->getChannelInfo(); + $data->uri = $uri; + $cache->addChannel($data->name, $uri); + $this->ui()->output("Successfully added channel: ".$uri); + } + $channelName = $cache->chanName($channel); + //var_dump($channelName); + $packagesToInstall = $packager->getDependenciesList( $channelName, $package, $cache, $config, $argVersionMax, $argVersionMin, $withDepsMode); + $packagesToInstall = $packagesToInstall['result']; + //var_dump($packagesToInstall); + + } else { + if(empty($params[0])) { + $channels = $cache->getChannelNames(); + } else { + $channel = $params[0]; + if(!$cache->isChannel($channel)) { + throw new Exception("'{$channel}' is not existant channel name / valid uri"); + } + $channels = $cache->chanName($channel); + } + $packagesToInstall = array(); + $neededToUpgrade = $packager->getUpgradesList($channels, $cache, $config); + foreach($neededToUpgrade as $chan=>$packages) { + foreach($packages as $name=>$data) { + $versionTo = $data['to']; + $tmp = $packager->getDependenciesList( $chan, $name, $cache, $config, $versionTo, $versionTo, $withDepsMode); + if(count($tmp['result'])) { + $packagesToInstall = array_merge($packagesToInstall, $tmp['result']); + } + } + } + } + + /** + * Make installation + */ + $installedDeps = array(); + $installedDepsAssoc = array(); + $keys = array(); + + foreach($packagesToInstall as $package) { + try { + $pName = $package['name']; + $pChan = $package['channel']; + $pVer = $package['downloaded_version']; + $rest->setChannel($cache->chanUrl($pChan)); + + /** + * Upgrade mode + */ + if($upgradeMode && $cache->hasPackage($pChan, $pName, $pVer, $pVer)) { + $this->ui()->output("Already installed: {$pChan}/{$pName} {$pVer}, skipping"); + continue; + } + + $conflicts = $cache->hasConflicts($pChan, $pName, $pVer); + + if(false !== $conflicts) { + $conflicts = implode(", ",$conflicts); + if($forceMode) { + $this->doError($command, "Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts); + } else { + throw new Exception("Package {$pChan}/{$pName} {$pVer} conflicts with: ".$conflicts); + } + } + + + /** + * Modifications + */ + if ($upgradeMode && !$ignoreModifiedMode) { + if($ftp) { + $modifications = $packager->getRemoteModifiedFiles($pChan, $pName, $cache, $config, $ftp); + } else { + $modifications = $packager->getLocalModifiedFiles($pChan, $pName, $cache, $config); + } + if (count($modifications) > 0) { + $this->ui()->output('Changed locally: '); + foreach ($modifications as $row) { + if(!$ftp) { + $this->ui()->output($config->magento_root.DS.$row); + } else { + $this->ui()->output($row); + } + } + /*$this->ui()->confirm('Do you want rewrite all files?'); + continue;*/ + } + } + + $dir = $config->getChannelCacheDir($pChan); + @mkdir($dir, 0777, true); + $file = $dir.DIRECTORY_SEPARATOR.$pName."-".$pVer.".tgz"; + if(!@file_exists($file)) { + $rest->downloadPackageFileOfRelease($pName, $pVer, $file); + } + $package = new Mage_Connect_Package($file); + + + + $conflicts = $package->checkPhpDependencies(); + if(true !== $conflicts) { + $confilcts = implode(",",$conflicts); + $err = "Package {$pChan}/{$pName} {$pVer} depends on PHP extensions: ".$conflicts; + if($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + $conflicts = $package->checkPhpVersion(); + if(true !== $conflicts) { + $err = "Package {$pChan}/{$pName} {$pVer}: ".$conflicts; + if($forceMode) { + $this->doError($command, $err); + } else { + throw new Exception($err); + } + } + + if(!$noFilesInstall) { + if($ftp) { + $packager->processInstallPackageFtp($package, $file, $config, $ftpObj); + } else { + $packager->processInstallPackage($package, $file, $config); + } + } + $cache->addPackage($package); + + $installedDepsAssoc[] = array('channel'=>$pChan, 'name'=>$pName, 'version'=>$pVer); + $installedDeps[] = array($pChan, $pName, $pVer); + + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + + + $title = isset($options['title']) ? $options['title'] : "Package installed: "; + $out = array($command => array('data'=>$installedDeps, 'assoc'=>$installedDepsAssoc, 'title'=>$title)); + + if($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + + $this->ui()->output($out); + return $out[$command]['data']; + + } catch (Exception $e) { + if($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + return $this->doError($command, $e->getMessage()); + } + } + + /** + * Upgrade action callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doUpgrade($command, $options, $params) + { + $options['title'] = "Package upgraded: "; + return $this->doInstall($command, $options, $params); + } + + /** + * Updgrade action callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doUpgradeAll($command, $options, $params) + { + $options['title'] = "Package upgraded: "; + return $this->doInstall($command, $options, $params); + } + + /** + * Uninstall package callback + * @param string $command + * @param array $options + * @param array $params + * @return unknown_type + */ + public function doUninstall($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + + try { + if(count($params) != 2) { + throw new Exception("Argument count should be = 2"); + } + + $channel = $params[0]; + $package = $params[1]; + $packager = $this->getPackager(); + $withDepsMode = !isset($options['nodeps']); + $forceMode = isset($options['force']); + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + $chan = $cache->getChannel($channel); + $channel = $cache->chanName($channel); + if(!$cache->hasPackage($channel, $package)) { + throw new Exception("Package is not installed"); + } + + $deletedPackages = array(); + $list = $packager->getUninstallList($channel, $package, $cache, $config, $withDepsMode); + foreach($list['list'] as $packageData) { + try { + $reqd = $cache->requiredByOtherPackages($packageData['channel'], $packageData['name'], $list['list']); + if(count($reqd)) { + $errMessage = "{$packageData['channel']}/{$packageData['name']} {$packageData['version']} is required by: "; + $t = array(); + foreach($reqd as $r) { + $t[] = $r['channel']."/".$r['name']. " ".$r['version']; + } + $errMessage .= implode(", ", $t); + if($forceMode) { + $this->ui()->output("Warning: ".$errMessage); + } else { + throw new Exception($errMessage); + } + } + + list($chan, $pack) = array($packageData['channel'], $packageData['name']); + if($ftp) { + $packager->processUninstallPackageFtp($chan, $pack, $cache, $config, $ftp); + } else { + $packager->processUninstallPackage($chan, $pack, $cache, $config); + } + $cache->deletePackage($chan, $pack); + $deletedPackages[] = array($chan, $pack); + + } catch(Exception $e) { + if($forceMode) { + $this->doError($command, $e->getMessage()); + } else { + throw new Exception($e->getMessage()); + } + } + } + if($ftp) { + $packager->writeToRemoteCache($cache, $ftpObj); + @unlink($config->getFilename()); + } + $out = array($command=>array('data'=>$deletedPackages, 'title'=>'Package deleted: ')); + $this->ui()->output($out); + + } catch (Exception $e) { + return $this->doError($command, $e->getMessage()); + } + + } + +} + diff --git a/lib/Mage/Connect/Command/Install_Header.php b/lib/Mage/Connect/Command/Install_Header.php new file mode 100644 index 00000000..daf010c6 --- /dev/null +++ b/lib/Mage/Connect/Command/Install_Header.php @@ -0,0 +1,237 @@ + array( + 'summary' => 'Install Package Archive File', + 'function' => 'doInstall', + 'shortcut' => 'if', + 'options' => array(), + 'doc' => '', + ), + 'install' => array( + 'summary' => 'Install Package', + 'function' => 'doInstall', + 'shortcut' => 'i', + 'options' => array( + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'will overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, install anyway', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + 'ftp=' => array( + 'shortopt' => 'r=', + 'doc' => 'Remote side FTP connect string', + ), + ), + 'doc' => '[channel/] ... +Installs one or more PEAR packages. You can specify a package to +install in four ways: + +"Package-1.0.tgz" : installs from a local file + +"http://example.com/Package-1.0.tgz" : installs from +anywhere on the net. + +"package.xml" : installs the package described in +package.xml. Useful for testing, or for wrapping a PEAR package in +another package manager such as RPM. + +"Package[-version/state][.tar]" : queries your default channel\'s server +({config master_server}) and downloads the newest package with +the preferred quality/state ({config preferred_state}). + +To retrieve Package version 1.1, use "Package-1.1," to retrieve +Package state beta, use "Package-beta." To retrieve an uncompressed +file, append .tar (make sure there is no file by the same name first) + +To download a package from another channel, prefix with the channel name like +"channel/Package" + +More than one package may be specified at once. It is ok to mix these +four ways of specifying packages. +'), + 'upgrade' => array( + 'summary' => 'Upgrade Package', + 'function' => 'doUpgrade', + 'shortcut' => 'up', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'force' => array( + 'shortopt' => 'f', + 'doc' => 'overwrite newer installed packages', + ), + 'loose' => array( + 'shortopt' => 'l', + 'doc' => 'do not check for recommended dependency version', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT)', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'alldeps' => array( + 'shortopt' => 'a', + 'doc' => 'install all required and optional dependencies', + ), + 'onlyreqdeps' => array( + 'shortopt' => 'o', + 'doc' => 'install all required dependencies', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to download any urls or contact channels', + ), + 'pretend' => array( + 'shortopt' => 'p', + 'doc' => 'Only list the packages that would be downloaded', + ), + ), + 'doc' => ' ... +Upgrades one or more PEAR packages. See documentation for the +"install" command for ways to specify a package. + +When upgrading, your package will be updated if the provided new +package has a higher version number (use the -f option if you need to +upgrade anyway). + +More than one package may be specified at once. +'), + 'upgrade-all' => array( + 'summary' => 'Upgrade All Packages', + 'function' => 'doUpgradeAll', + 'shortcut' => 'ua', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'upgrade packages from a specific channel', + 'arg' => 'CHAN', + ), + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, upgrade anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not install files, only register the package as upgraded', + ), + 'nobuild' => array( + 'shortopt' => 'B', + 'doc' => 'don\'t build C extensions', + ), + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'request uncompressed files when downloading', + ), + 'installroot' => array( + 'shortopt' => 'R', + 'arg' => 'DIR', + 'doc' => 'root directory used when installing files (ala PHP\'s INSTALL_ROOT), use packagingroot for RPM', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'loose' => array( + 'doc' => 'do not check for recommended dependency version', + ), + ), + 'doc' => ' +WARNING: This function is deprecated in favor of using the upgrade command with no params + +Upgrades all packages that have a newer release available. Upgrades are +done only if there is a release available of the state specified in +"preferred_state" (currently {config preferred_state}), or a state considered +more stable. +'), + 'uninstall' => array( + 'summary' => 'Un-install Package', + 'function' => 'doUninstall', + 'shortcut' => 'un', + 'options' => array( + 'nodeps' => array( + 'shortopt' => 'n', + 'doc' => 'ignore dependencies, uninstall anyway', + ), + 'register-only' => array( + 'shortopt' => 'r', + 'doc' => 'do not remove files, only register the packages as not installed', + ), + 'ignore-errors' => array( + 'doc' => 'force install even if there were errors', + ), + 'offline' => array( + 'shortopt' => 'O', + 'doc' => 'do not attempt to uninstall remotely', + ), + ), + 'doc' => '[channel/] ... +Uninstalls one or more PEAR packages. More than one package may be +specified at once. Prefix with channel name to uninstall from a +channel not in your default channel ({config default_channel}) +'), + ); diff --git a/lib/Mage/Connect/Command/Package.php b/lib/Mage/Connect/Command/Package.php new file mode 100644 index 00000000..ad9c8407 --- /dev/null +++ b/lib/Mage/Connect/Command/Package.php @@ -0,0 +1,134 @@ +cleanupParams($params); + + if(count($params) < 1) { + return $this->doError($command, "Parameters count should be >= 1"); + } + + $file = strtolower($params[0]); + $file = realpath($file); + + if(!file_exists($file)) { + return $this->doError($command, "File {$params[0]} doesn't exist"); + } + + try { + $packager = new Mage_Connect_Package($file); + $res = $packager->validate(); + if(!$res) { + $this->doError($command, implode("\n", $packager->getErrors())); + return; + } + $packager->save(dirname($file)); + $this->ui()->output('Done building package'); + } catch (Exception $e) { + $this->doError( $command, $e->getMessage() ); + } + } + + + /** + * Display/get dependencies + * @param string $command + * @param array $options + * @param array $params + * @return void/array + */ + public function doPackageDependencies($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) < 2) { + return $this->doError($command, "Argument count should be >= 2"); + } + + $channel = $params[0]; + $package = $params[1]; + + $argVersionMin = isset($params[3]) ? $params[3] : false; + $argVersionMax = isset($params[2]) ? $params[2] : false; + + $ftp = empty($options['ftp']) ? false : $options['ftp']; + $packager = $this->getPackager(); + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + $data = $packager->getDependenciesList($channel, $package, $cache, $config, $argVersionMax, $argVersionMin); + $this->ui()->output(array($command=> array('data'=>$data['deps'], 'title'=>"Package deps for {$params[1]}: "))); + + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + public function doConvert($command, $options, $params) + { + $this->cleanupParams($params); + try { + if(count($params) < 1) { + throw new Exception("Arguments should be: source.tgz [target.tgz]"); + } + $sourceFile = $params[0]; + $converter = new Mage_Connect_Converter(); + $targetFile = isset($params[1]) ? $params[1] : false; + $result = $converter->convertPearToMage($sourceFile, $targetFile); + $this->ui()->output("Saved to: ".$result); + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Package_Header.php b/lib/Mage/Connect/Command/Package_Header.php new file mode 100644 index 00000000..b8c38128 --- /dev/null +++ b/lib/Mage/Connect/Command/Package_Header.php @@ -0,0 +1,67 @@ + array( + 'summary' => 'Build Package', + 'function' => 'doPackage', + 'shortcut' => 'p', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'Do not gzip the package file' + ), + 'showname' => array( + 'shortopt' => 'n', + 'doc' => 'Print the name of the packaged file.', + ), + ), + 'doc' => '[descfile] [descfile2] +Creates a PEAR package from its description file (usually called +package.xml). If a second packagefile is passed in, then +the packager will check to make sure that one is a package.xml +version 1.0, and the other is a package.xml version 2.0. The +package.xml version 1.0 will be saved as "package.xml" in the archive, +and the other as "package2.xml" in the archive" +' + ), + 'package-dependencies' => array( + 'summary' => 'Show package dependencies', + 'function' => 'doPackageDependencies', + 'shortcut' => 'pd', + 'options' => array(), + 'doc' => ' or or +List all dependencies the package has. +Can take a tgz / tar file, package.xml or a package name of an installed package.' + ), + 'convert' => array( + 'summary' => 'Convert old magento PEAR package to new format', + 'function' => 'doConvert', + 'shortcut' => 'conv', + 'options' => array(), + 'doc' => '' + ), + ); \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Registry.php b/lib/Mage/Connect/Command/Registry.php new file mode 100644 index 00000000..10f766d3 --- /dev/null +++ b/lib/Mage/Connect/Command/Registry.php @@ -0,0 +1,175 @@ +cleanupParams($params); + try { + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + } else { + $cache = $this->getSconfig(); + } + if(!empty($params[0])) { + $chanName = $conf->chanName($params[0]); + $data = $cache->getInstalledPackages($chanName); + } else { + $data = $cache->getInstalledPackages(); + } + if($ftp) { + @unlink($cache->getFilename()); + } + $this->ui()->output(array($command=>array('data'=>$data, 'channel-title'=>"Installed package for channel '%s' :"))); + } catch (Exception $e) { + if($ftp) { + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + + } + + /** + * list-files callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doFileList($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + try { + $channel = false; + if(count($params) < 2) { + throw new Exception("Argument count should be = 2"); + } + $channel = $params[0]; + $package = $params[1]; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $confif = $this->config(); + } + if(!$cache->hasPackage($channel, $package)) { + return $this->ui()->output("No package found: {$channel}/{$package}"); + } + + $p = $cache->getPackageObject($channel, $package); + $contents = $p->getContents(); + if($ftp) { + $ftpObj->close(); + } + if(!count($contents)) { + return $this->ui()->output("No contents for package {$package}"); + } + $title = ("Contents of '{$package}': "); + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + + $this->ui()->output(array($command=>array('data'=>$contents, 'title'=>$title))); + + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + + } + + /** + * Installed package info + * info command callback + * @param string $command + * @param array $options + * @param array $params + * @return + */ + public function doInfo($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + + try { + $channel = false; + if(count($params) < 2) { + throw new Exception("Argument count should be = 2"); + } + $channel = $params[0]; + $package = $params[1]; + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + } else { + $cache = $this->getSconfig(); + } + + if(!$cache->isChannel($channel)) { + throw new Exception("'{$channel}' is not a valid installed channel name/uri"); + } + $channelUri = $cache->chanUrl($channel); + $rest = $this->rest(); + $rest->setChannel($channelUri); + $releases = $rest->getReleases($package); + if(false === $releases) { + throw new Exception("No information found about {$channel}/{$package}"); + } + $data = array($command => array('releases'=>$releases)); + if($ftp) { + @unlink($cache->getFilename()); + } + $this->ui()->output($data); + } catch (Exception $e) { + if($ftp) { + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Registry_Header.php b/lib/Mage/Connect/Command/Registry_Header.php new file mode 100644 index 00000000..bdce5c65 --- /dev/null +++ b/lib/Mage/Connect/Command/Registry_Header.php @@ -0,0 +1,68 @@ + array( + 'summary' => 'List Installed Packages In The Default Channel', + 'function' => 'doList', + 'shortcut' => 'l', + 'options' => array( + 'channel' => array( + 'shortopt' => 'c', + 'doc' => 'list installed packages from this channel', + 'arg' => 'CHAN', + ), + 'allchannels' => array( + 'shortopt' => 'a', + 'doc' => 'list installed packages from all channels', + ), + ), + 'doc' => ' +If invoked without parameters, this command lists the PEAR packages +installed in your php_dir ({config php_dir}). With a parameter, it +lists the files in a package. +', + ), + 'list-files' => array( + 'summary' => 'List Files In Installed Package', + 'function' => 'doFileList', + 'shortcut' => 'fl', + 'options' => array(), + 'doc' => ' +List the files in an installed package. +' + ), + 'info' => array( + 'summary' => 'Display information about a package', + 'function' => 'doInfo', + 'shortcut' => 'in', + 'options' => array(), + 'doc' => ' +Displays information about a package. The package argument may be a +local package file, an URL to a package file, or the name of an +installed package.' + ) + ); \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Remote.php b/lib/Mage/Connect/Command/Remote.php new file mode 100644 index 00000000..b10ae5a0 --- /dev/null +++ b/lib/Mage/Connect/Command/Remote.php @@ -0,0 +1,226 @@ +cleanupParams($params); + try { + $packager = new Mage_Connect_Packager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + if(!empty($params[0])) { + $channels = $params[0]; + $cache->getChannel($channels); + } else { + $channels = $cache->getChannelNames(); + } + $ups = $packager->getUpgradesList($channels, $cache, $config); + + if(count($ups)) { + $data = array($command => array('data'=>$ups)); + } else { + $data = "No upgrades available"; + } + $this->ui()->output($data); + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + + /** + * List available + * @param $command + * @param $options + * @param $params + * @return unknown_type + */ + + public function doListAvailable($command, $options, $params) + { + $this->cleanupParams($params); + + try { + $packager = new Mage_Connect_Packager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + if(!empty($params[0])) { + $channels = array($params[0]); + $cache->getChannel($channels[0]); + } else { + $channels = $cache->getChannelNames(); + } + + + + $packs = array(); + foreach ($channels as $channel) { + try { + $chan = $cache->getChannel($channel); + $uri = $cache->chanUrl($channel); + + $rest = $this->rest(); + $rest->setChannel($uri); + + $packages = $rest->getPackages(); + if(!count($packages)) { + $this->ui()->output("Channel '{$channel}' has no packages"); + continue; + } + $packs[$channel]['title'] = "Packages for channel '".$channel."':"; + foreach($packages as $p) { + $packageName = $p['n']; + $releases = array(); + foreach($p['r'] as $k=>$r) { + $releases[$r] = $rest->shortStateToLong($k); + } + $packs[$channel]['packages'][$packageName]['releases'] = $releases; + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + $dataOut = array(); + $dataOut[$command]= array('data'=>$packs); + $this->ui()->output($dataOut); + + } catch(Exception $e) { + $this->doError($command, $e->getMessage()); + } + + } + + /** + * Download command callback + * + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doDownload($command, $options, $params) + { + $this->cleanupParams($params); + //$this->splitPackageArgs($params); + try { + if(count($params) < 2) { + throw new Exception("Arguments should be: channel Package"); + } + + $channel = $params[0]; + $package = $params[1]; + + $packager = $this->getPackager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $config, $ftpObj) = $packager->getRemoteConf($ftp); + } else { + $cache = $this->getSconfig(); + $config = $this->config(); + } + + $chan = $cache->getChannel($channel); + $uri = $cache->chanUrl($channel); + + $rest = $this->rest(); + $rest->setChannel($uri); + $c = $rest->getReleases($package); + if(!count($c)) { + throw new Exception("No releases found for package"); + } + $version = $cache->detectVersionFromRestArray($c); + $dir = $config->getChannelCacheDir($channel); + $file = $dir.DIRECTORY_SEPARATOR.$package."-".$version.".tgz"; + $rest->downloadPackageFileOfRelease($package, $version, $file); + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->ui()->output("Saved to: ". $file); + } catch (Exception $e) { + if($ftp) { + @unlink($config->getFilename()); + @unlink($cache->getFilename()); + } + $this->doError($command, $e->getMessage()); + } + } + + /** + * Clear cache command callback + * @param string $command + * @param array $options + * @param array $params + * @return void + */ + public function doClearCache($command, $options, $params) + { + $this->cleanupParams($params); + try { + $packager = new Mage_Connect_Packager(); + $ftp = empty($options['ftp']) ? false : $options['ftp']; + if($ftp) { + list($cache, $ftpObj) = $packager->getRemoteCache($ftp); + $cache->clear(); + $packager->writeToRemoteCache($cache, $ftpObj); + } else { + $cache = $this->getSconfig(); + $cache->clear(); + } + } catch (Exception $e) { + $this->doError($command, $e->getMessage()); + } + } + + + + + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Command/Remote_Header.php b/lib/Mage/Connect/Command/Remote_Header.php new file mode 100644 index 00000000..c59af4b2 --- /dev/null +++ b/lib/Mage/Connect/Command/Remote_Header.php @@ -0,0 +1,88 @@ + array( + 'summary' => 'List Available Upgrades', + 'function' => 'doListUpgrades', + 'shortcut' => 'lu', + 'options' => array( + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => '[preferred_state] +List releases on the server of packages you have installed where +a newer version is available with the same release state (stable etc.) +or the state passed as the second parameter.' + ), + 'list-available' => array( + 'summary' => 'List Available Packages', + 'function' => 'doListAvailable', + 'shortcut' => 'la', + 'options' => array( + 'channel' => + array( + 'shortopt' => 'c', + 'doc' => 'specify a channel other than the default channel', + 'arg' => 'CHAN', + ), + 'channelinfo' => array( + 'shortopt' => 'i', + 'doc' => 'output fully channel-aware data, even on failure', + ), + ), + 'doc' => ' +Lists the packages available on the configured server along with the +latest stable release of each package.', + ), + 'download' => array( + 'summary' => 'Download Package', + 'function' => 'doDownload', + 'shortcut' => 'd', + 'options' => array( + 'nocompress' => array( + 'shortopt' => 'Z', + 'doc' => 'download an uncompressed (.tar) file', + ), + ), + 'doc' => '... +Download package tarballs. The files will be named as suggested by the +server, for example if you download the DB package and the latest stable +version of DB is 1.6.5, the downloaded file will be DB-1.6.5.tgz.', + ), + 'clear-cache' => array( + 'summary' => 'Clear Web Services Cache', + 'function' => 'doClearCache', + 'shortcut' => 'cc', + 'options' => array(), + 'doc' => ' +Clear the XML-RPC/REST cache. See also the cache_ttl configuration +parameter. +', + ), + ); \ No newline at end of file diff --git a/lib/Mage/Connect/Config.php b/lib/Mage/Connect/Config.php new file mode 100644 index 00000000..3d1b5522 --- /dev/null +++ b/lib/Mage/Connect/Config.php @@ -0,0 +1,265 @@ +properties = array ( + 'php_ini' => array( + 'type' => 'file', + 'value' => '', + 'prompt' => 'location of php.ini', + 'doc' => "It's a location of PHP.ini to use blah", + 'possible' => '/path/php.ini', + ), + 'protocol' => array( + 'type' => 'set', + 'value' => 'http', + 'prompt' => 'preffered protocol', + 'doc' => 'preffered protocol', + 'rules' => array('http', 'ftp') + ), + 'preferred_state' => array( + 'type' => 'set', + 'value' => 'stable', + 'prompt' => 'preferred package state', + 'doc' => 'preferred package state', + 'rules' => array('beta','alpha','stable','devel') + ), + 'global_dir_mode' => array ( + 'type' => 'octal', + 'value' => 0777, + 'prompt' => 'directory creation mode', + 'doc' => 'directory creation mode', + 'possible' => '0777, 0666 etc.', + ), + 'global_file_mode' => array ( + 'type' => 'octal', + 'value' => 0666, + 'prompt' => 'file creation mode', + 'doc' => 'file creation mode', + 'possible' => '0777, 0666 etc.', + ), + 'downloader_path' => array( + 'type' => 'dir', + 'value' => 'downloader', + 'prompt' => 'relative path, location of magento downloader', + 'doc' => "relative path, location of magento downloader", + 'possible' => 'path', + ), + 'magento_root' => array( + 'type' => 'dir', + 'value' => '', + 'prompt' => 'location of magento root dir', + 'doc' => "Location of magento", + 'possible' => '/path', + ), + 'root_channel' => array( + 'type' => 'string', + 'value' => 'core', + 'prompt' => '', + 'doc' => "", + 'possible' => '', + ), + + ); + + } + + public function getDownloaderPath() + { + return $this->magento_root . DIRECTORY_SEPARATOR . $this->downloader_path; + } + + public function getPackagesCacheDir() + { + return $this->getDownloaderPath() . DIRECTORY_SEPARATOR . self::DEFAULT_CACHE_PATH; + } + + public function getChannelCacheDir($channel) + { + $channel = trim( $channel, "\\/"); + return $this->getPackagesCacheDir(). DIRECTORY_SEPARATOR . $channel; + } + + + public function __construct($configFile = "connect.cfg") + { + $this->initProperties(); + $this->_configFile = $configFile; + $this->load(); + } + + public function getFilename() + { + return $this->_configFile; + } + + public function load() + { + /** + * Trick: open in append mode to read, + * place pointer to begin + * create if not exists + */ + $f = fopen($this->_configFile, "a+"); + fseek($f, 0, SEEK_SET); + $size = filesize($this->_configFile); + if(!$size) { + $this->store(); + return; + } + + $headerLen = strlen(self::HEADER); + $contents = fread($f, $headerLen); + + if(self::HEADER != $contents) { + $this->store(); + return; + } + + $size -= $headerLen; + $contents = fread($f, $size); + + $data = @unserialize($contents); + if($data === unserialize(false)) { + $this->store(); + return; + } + foreach($data as $k=>$v) { + $this->$k = $v; + } + fclose($f); + } + + public function store() + { + $data = serialize($this->toArray()); + $f = @fopen($this->_configFile, "w+"); + @fwrite($f, self::HEADER); + @fwrite($f, $data); + @fclose($f); + } + + + public function validate($key, $val) + { + $rules = $this->extractField($key, 'rules'); + if(null === $rules) { + return true; + } elseif( is_array($rules) ) { + return in_array($val, $rules); + } + return false; + } + + public function possible($key) + { + $data = $this->getKey($key); + if(! $data) { + return null; + } + if('set' == $data['type']) { + return implode("|", $data['rules']); + } + if(!empty($data['possible'])) { + return $data['possible']; + } + return "<".$data['type'].">"; + } + + public function type($key) + { + return $this->extractField($key, 'type'); + } + + public function doc($key) + { + return $this->extractField($key, 'doc'); + } + + + public function extractField($key, $field) + { + if(!isset($this->properties[$key][$field])) { + return null; + } + return $this->properties[$key][$field]; + } + + + public function hasKey($fld) + { + return isset($this->properties[$fld]); + } + + public function getKey($fld) + { + if($this->hasKey($fld)) { + return $this->properties[$fld]; + } + return null; + } + + public function rewind() { + reset($this->properties); + } + + public function valid() { + return current($this->properties) !== false; + } + + public function key() { + return key($this->properties); + } + + public function current() { + return current($this->properties); + } + + public function next() { + next($this->properties); + } + + public function __get($var) + { + if (isset($this->properties[$var]['value'])) { + return $this->properties[$var]['value']; + } + return null; + } + + public function __set($var, $value) + { + if (is_string($value)) { + $value = trim($value); + } + if (isset($this->properties[$var])) { + if ($value === null) { + $value = ''; + } + if($this->properties[$var]['value'] !== $value) { + $this->properties[$var]['value'] = $value; + $this->store(); + } + } + } + + public function toArray($withRules = false) + { + $out = array(); + foreach($this as $k=>$v) { + $out[$k] = $withRules ? $v : $v['value']; + } + return $out; + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Converter.php b/lib/Mage/Connect/Converter.php new file mode 100644 index 00000000..f396b61f --- /dev/null +++ b/lib/Mage/Connect/Converter.php @@ -0,0 +1,337 @@ + + */ + +final class Mage_Connect_Converter +{ + protected $_archiver; + + /** + * + * @return Mage_Archive + */ + public function arc() + { + if(!$this->_archiver) { + $this->_archiver = new Mage_Archive(); + } + return $this->_archiver; + } + + public function newPackage() + { + return new Mage_Connect_Package(); + } + + /** + * + * @return Pear_Package_Parser_v2 + */ + public function oldPackageReader() + { + return new Pear_Package_Parser_v2(); + } + + + public function __construct() + { + + } + + + public function convertChannelName($channel) + { + return str_replace("connect.magentocommerce.com/", "", $channel); + } + + /** + * Convert package dependencies - urls - by ref + * @param array $deps ref to array + * @return void + */ + public function convertPackageDependencies($oldDeps) + { + $out = array(); + if(empty($oldDeps['required']['package'])) { + return $out; + } + $deps = $oldDeps['required']['package']; + if(!isset($deps[0])) { + $deps = array($deps); + } + for($i=0, $c=count($deps); $i<$c; $i++) { + $deps[$i]['min_version'] = isset($deps[$i]['min']) ? $deps[$i]['min'] : false; + $deps[$i]['max_version'] = isset($deps[$i]['max']) ? $deps[$i]['max'] : false; + $deps[$i]['channel'] = $this->convertChannelName($deps[$i]['channel']); + $out[] = $deps[$i]; + } + + return $out; + } + + public function convertLicense($oldLicense) + { + if(is_scalar($oldLicense)) { + return $oldLicense; + } + return array($oldLicense['_content'], $oldLicense['attribs']['uri']); + } + + public function convertMaintainers($maintainers) + { + if(!is_array($maintainers) || !count($maintainers)) { + return array(); + } + $out = array(); + foreach($maintainers as $row) { + $out[] = array('name'=>$row['name'], 'email'=>$row['email'], 'user'=>'auto-converted'); + } + return $out; + } + + protected $fileMap = array(); + + + /** + * Conver pear package object to magento object + * @param Pear_Package_V2 $pearObject + * @return Mage_Connect_Package + */ + + public function convertPackageObject($pearObject) + { + $data = array(); + $mageObject = $this->newPackage(); + + + + $map = array ( + 'name' => null, + 'version' => array('getterArgs' => array('release') + ), + 'package_deps' => array( 'getter'=>'getDependencies', + 'converter'=>'convertPackageDependencies', + 'setter'=>'setDependencyPackages', + ), + 'stability' => array( 'getter'=>'getState', + 'getterArgs' => array('release'), + ), + 'license' => array( 'getterArgs' => array(true), + 'converter' => 'convertLicense', + 'noArrayWrap' => true, + ), + 'summary' => null, + 'description' => null, + 'notes' => null, + 'date' => null, + 'time' => null, + 'authors' => array( 'converter' => 'convertMaintainers', + 'getter' => 'getMaintainers', + ), + 'channel' => array( 'converter' => 'convertChannelName', + + ), + + ); + foreach($map as $field=>$rules) { + + if(empty($rules)) { + $rules = array('setter'=> '', 'getter'=> ''); + } + + if(empty($rules['getter'])) { + $rules['getter'] = 'get'. ucfirst($field); + } + + $useSetter = empty($rules['noSetter']); + $useGetter = empty($rules['noGetter']); + + + if(empty($rules['setter'])) { + $rules['setter'] = 'set'. ucfirst($field); + } + if(empty($rules['getterArgs'])) { + $rules['getterArgs'] = array(); + } elseif(!is_array($rules['getterArgs'])) { + throw new Exception("Invalid 'getterArgs' for '{$field}', should be array"); + } + + if($useGetter && !method_exists($pearObject, $rules['getter'])) { + $mName = get_class($pearObject)."::".$rules['getter']; + throw new Exception('No getter method exists: '.$mName); + } + + if($useSetter && !method_exists($mageObject, $rules['setter'])) { + $mName = get_class($mageObject)."::".$rules['setter']; + throw new Exception('No setter method exists: '.$mName); + } + + $useConverter = !empty($rules['converter']); + + if($useConverter && false === method_exists($this, $rules['converter'])) { + $mName = get_class($this)."::".$rules['converter']; + throw new Exception('No converter method exists: '.$mName); + } + + if($useGetter) { + $getData = call_user_func_array(array($pearObject, $rules['getter']), $rules['getterArgs']); + } else { + $getData = array(); + } + + if($useConverter) { + $args = array(); + if(!$useGetter && !$useSetter) { + $args = array($pearObject, $mageObject); + } elseif(!$useSetter) { + $args = array($mageObject, $getData); + } else { + $args = array($getData); + } + $getData = call_user_func_array(array($this, $rules['converter']), $args); + } + + $noWrap = !empty($rules['noArrayWrap']); + if($useSetter) { + $setData = call_user_func_array(array($mageObject, $rules['setter']), $noWrap ? $getData : array($getData)); + } + } + return $mageObject; + } + + /** + * Convert PEAR package to Magento package + * @param string $sourceFile path to PEAR .tgz + * @param string|false $destFile path to newly-created Magento .tgz, false to specify auto + * @return bool + */ + public function convertPearToMage($sourceFile, $destFile = false) + { + try { + if(!file_exists($sourceFile)) { + throw new Exception("File doesn't exist: {$sourceFile}"); + } + $arc = $this->arc(); + $tempDir = "tmp-".basename($sourceFile).uniqid(); + $outDir = "out-".basename($sourceFile).uniqid(); + $outDir = rtrim($outDir, "\\/"); + Mage_System_Dirs::mkdirStrict($outDir); + Mage_System_Dirs::mkdirStrict($tempDir); + + $result = $arc->unpack($sourceFile, $tempDir); + if(!$result) { + throw new Exception("'{$sourceFile}' was not unpacked"); + } + + $result = rtrim($result, "\\/"); + $packageXml = $result . DS . "package.xml"; + if(!file_exists($packageXml)) { + throw new Exception("No package.xml found inside '{$sourceFile}'"); + } + + $reader = $this->oldPackageReader(); + $data = file_get_contents($packageXml); + + $pearObject = $reader->parsePackage($data, $packageXml); + $mageObject = $this->convertPackageObject($pearObject); + if(!$mageObject->validate()) { + throw new Exception("Package validation failed.\n". implode("\n", $mageObject->getErrors())); + } + + /** + * Calculate destination file if false + */ + if(false === $destFile) { + $pathinfo = pathinfo($sourceFile); + $destFile = $pathinfo['dirname'] . DS .$pathinfo['filename'].'-converted'; + if(isset($pathinfo['extension'])) { + $destFile .= ".".$pathinfo['extension']; + } + } + + $target = new Mage_Connect_Package_Target("target.xml"); + $targets = $target->getTargets(); + $mageObject->setTarget($target); + $validRoles = array_keys($targets); + $data = $pearObject->getFilelist(); + $pathSource = dirname($pearObject->getPackageFile()).DS.$pearObject->getName()."-".$pearObject->getVersion(); + + $filesToDo = array(); + foreach($data as $file =>$row) { + $name = $row['name']; + $role = $row['role']; + if(!in_array($role, $validRoles)) { + $role = 'mage'; + } + $baseName = ltrim($targets[$role], "\\/."); + $baseName = rtrim($baseName, "\\/"); + $sourceFile = $pathSource.DS.$name; + $targetFile = $outDir . DS . $baseName . DS. $name; + if(file_exists($sourceFile)) { + Mage_System_Dirs::mkdirStrict(dirname($targetFile)); + $copy = @copy($sourceFile, $targetFile); + if(false === $copy) { + throw new Exception("Cannot copy '{$sourceFile}' to '{$targetFile}'"); + } + } + $filesToDo[] = array ('name'=> $name, 'role'=>$role); + } + $cwd = getcwd(); + @chdir($outDir); + foreach($filesToDo as $fileToDo) { + $mageObject->addContent($fileToDo['name'], $fileToDo['role']); + } + $mageObject->save(getcwd()); + @chdir($cwd); + $filename = $outDir. DS . $mageObject->getReleaseFilename().".tgz"; + if(@file_exists($targetArchive)) { + @unlink($targetArchive); + } + Mage_System_Dirs::mkdirStrict(dirname($destFile)); + $copy = @copy($filename, $destFile); + if(false === $copy) { + throw new Exception("Cannot copy '{$filename}' to '{$targetArchive}'"); + } + Mage_System_Dirs::rm($tempDir); + Mage_System_Dirs::rm($outDir); + + } catch (Exception $e) { + throw $e; + } + return $destFile; + } + + + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Frontend.php b/lib/Mage/Connect/Frontend.php new file mode 100644 index 00000000..2c38e32d --- /dev/null +++ b/lib/Mage/Connect/Frontend.php @@ -0,0 +1,251 @@ +_errors[] = $data; + } + + /** + * Get errors, clear errors list with first param + * @param bool $clear + * @return array + */ + public function getErrors($clear = true) + { + if(!$clear) { + return $this->_errors; + } + $out = $this->_errors; + $this->clearErrors(); + return $out; + } + + /** + * Clear errors array + * @return void + */ + public function clearErrors() + { + $this->_errors = array(); + } + + /** + * Are there any errros? + * @return bool + */ + public function hasErrors() + { + return count($this->_errors) != 0; + } + + /** + * Error processing + * @param string $command + * @param stting $message + * @return void + */ + public function doError($command, $message) + { + $this->addError(array($command, $message)); + } + + /** + * Save capture state + * @return Mage_Connect_Frontend + */ + public function pushCapture() + { + array_push($this->_captureSaved, $this->_capture); + return $this; + } + + /** + * Restore capture state + * @return Mage_Connect_Frontend + */ + public function popCapture() + { + $this->_capture = array_pop($this->_captureSaved); + return $this; + } + + /** + * Set capture mode + * @param bool $arg true by default + * @return Mage_Connect_Frontend + */ + public function setCapture($arg = true) + { + $this->_capture = $arg; + return $this; + } + + /** + * Getter for capture mode + * @return bool + */ + public function isCapture() + { + return $this->_capture; + } + + /** + * Log stub + * @param $msg + * @return + */ + public function log($msg) + { + + } + + /** + * Ouptut method + * @param array $data + * @return void + */ + public function output($data) + { + + } + + /** + * Get instance of derived class + * + * @param $class CLI for example will produce Mage_Connect_Frontend_CLI + * @return object + */ + public static function getInstance($class) + { + $class = __CLASS__."_".$class; + return new $class(); + } + + /** + * Get output if capture mode set + * Clear prevoius if needed + * @param bool $clearPrevious + * @return mixed + */ + public function getOutput($clearPrevious = true) + { + + } + + + /** + * Save silent mode + * @return Mage_Connect_Frontend + */ + public function pushSilent() + { + array_push($this->_silentSaved, $this->_silent); + return $this; + } + + /** + * Restore silent mode + * @return Mage_Connect_Frontend + */ + public function popSilent() + { + $this->_silent = array_pop($this->_silentSaved); + return $this; + } + + /** + * Set silent mode + * @param bool $value + * @return Mage_Connect_Frontend + */ + public function setSilent($value = true) + { + $this->_silent = (bool) $value; + return $this; + } + + /** + * Is silent mode? + * @return bool + */ + public function isSilent() + { + return (bool) $this->_silent; + } + + /** + * Method for ask client about rewrite all files. + * + * @param $string + */ + public function confirm($string) + { + + } +} + diff --git a/lib/Mage/Connect/Frontend/CLI.php b/lib/Mage/Connect/Frontend/CLI.php new file mode 100644 index 00000000..f1601c3a --- /dev/null +++ b/lib/Mage/Connect/Frontend/CLI.php @@ -0,0 +1,441 @@ + + */ + +class Mage_Connect_Frontend_CLI +extends Mage_Connect_Frontend +{ + + /** + * Collected output + * @var array + */ + protected $_output = array(); + + /** + * Output error + * @param string $command + * @param string $message + * @return void + */ + public function doError($command, $message) + { + parent::doError($command, $message); + $this->writeln("Error: "); + $this->writeln("$command: $message"); + } + + + /** + * Output config help + * @param array $data + * @return void + */ + public function outputConfigHelp($data) + { + foreach($data['data'] as $k=>$v) { + if(is_scalar($v)) { + $this->writeln($v); + } elseif(is_array($v)) { + $this->writeln(implode(": ", $v)); + } + } + } + + + /** + * Output info + * @param array $data + * @return void + */ + public function outputRemoteInfo($data) + { + if(!is_array($data['releases'])) { + return; + } + foreach ($data['releases'] as $r) { + $this->writeln(implode(" ", $r)); + } + } + + + public function detectMethodByType($type) + { + $defaultMethod = "output"; + $methodMap = array( + 'list-upgrades'=> 'outputUpgrades', + 'list-available' => 'outputChannelsPackages', + 'list-installed' => 'writeInstalledList', + 'package-dependencies' => 'outputPackageDeps', + 'list-files' => 'outputPackageContents', + 'config-help' => 'outputConfigHelp', + 'info' => 'outputRemoteInfo', + 'config-show' => 'outputConfig', + 'install' => 'outputInstallResult', + 'install-file' => 'outputInstallResult', + 'upgrade' => 'outputInstallResult', + 'upgrade-all' => 'outputInstallResult', + 'uninstall' => 'outputDeleted', + 'list-channels' => 'outputListChannels', + ); + if(isset($methodMap[$type])) { + return $methodMap[$type]; + } + return $defaultMethod; + } + + + public function outputDeleted($data) + { + if(!count($data['data'])) { + return; + } + $this->writeln($data['title']); + foreach($data['data'] as $row) { + $this->writeln("$row[0]/$row[1]"); + } + } + + public function outputListChannels($data) + { + $this->writeln($data['title']); + + $channels =& $data['data'][Mage_Connect_Singleconfig::K_CHAN]; + foreach($channels as $name => $v) { + $this->writeln("$name: {$v[Mage_Connect_Singleconfig::K_URI]}"); + } + $aliases =& $data['data'][Mage_Connect_Singleconfig::K_CHAN_ALIAS]; + if(count($aliases)) { + $this->writeln(); + $this->writeln($data['title_aliases']); + foreach($aliases as $k=>$v) { + $this->writeln("$k => $v"); + } + } + + } + + /** + * Output install result + * @param array $data + * @return void + */ + public function outputInstallResult($data) + { + if(isset($data['title'])) { + $title = trim($data['title'])." "; + } else { + $title = ''; + } + foreach($data['assoc'] as $row) { + $this->printf("%s%s/%s %s\n", $title, $row['channel'], $row['name'], $row['version']); + } + } + + /** + * Ouptut package contents + * @param array $data + * @return void + */ + public function outputPackageContents($data) + { + $this->writeln($data['title']); + foreach($data['data'] as $file) { + $this->writeln($file); + } + } + + /** + * Output package dependencies + * @param $data + * @return void + */ + public function outputPackageDeps($data) + { + $title = $data['title']; + $this->writeln($title); + foreach($data['data'] as $package) { + $this->printf("%-20s %-20s %-20s %-20s\n", $package['channel'], $package['name'], $package['min'], $package['max']); + } + } + + /** + * Ouptut channel packages + * @param $data + * @return unknown_type + */ + public function outputChannelsPackages($data) + { + foreach($data['data'] as $channelInfo) { + $title =& $channelInfo['title']; + $packages =& $channelInfo['packages']; + $this->writeln($title); + foreach($packages as $name=>$package) { + $releases =& $package['releases']; + $tmp = array(); + foreach($releases as $ver=>$state) { + $tmp[] = "$ver $state"; + } + $tmp = implode(',',$tmp); + $this->writeln($name.": ".$tmp); + } + } + } + + + /** + * Make output + * + * @param array $data + * @return void + */ + + public function output($data) + { + $capture = $this->isCapture(); + if($capture) { + $this->_output[] = $data; + return; + } + + if(is_array($data)) { + foreach($data as $type=>$params) { + $method = $this->detectMethodByType($type); + if($method) { + $this->$method($params); + } else { + $this->writeln(__METHOD__." handler not found for {$type}"); + } + } + } else { + $this->writeln($data); + } + } + + + /** + * Detailed package info + * @param Mage_Connect_Package $package + * @return void + */ + public function outputPackage($package) + { + $fields = array( + 'Name'=>'name', + 'Version'=>'version', + 'Stability'=>'stability', + 'Description' => 'description', + 'Date' => 'date', + 'Authors' => 'authors', + ); + + foreach($fields as $title => $fld) { + $method = "get".ucfirst($fld); + $data = $package->$method(); + if(empty($data)) { + continue; + } + $this->write($title.": "); + if(is_array($data)) { + $this->write(print_r($data,true)); + } else { + $this->write($data); + } + $this->writeln(''); + } + } + + + /** + * Write channels list + * @param array $data + * @return void + */ + public function writeChannelsList($data) + { + $this->writeln("Channels available: "); + $this->writeln("==================="); + $out = $data['byName']; + ksort($out); + foreach($out as $k=>$v) { + $this->printf ("%-20s %-20s\n", $k, $v); + } + } + + /** + * Write installed list + * @param array $data + * @return void + */ + public function writeInstalledList($data) + { + $totalCount = 0; + foreach($data['data'] as $channel=>$packages) { + $title = sprintf($data['channel-title'], $channel); + $c = count($packages); + $totalCount += $c; + if(!$c) { + continue; + } + $this->writeln($title); + foreach($packages as $name=>$row) { + $this->printf("%-20s %-20s\n", $name, $row['version']." ".$row['stability']); + } + } + if($totalCount === 0) { + $this->writeln("No installed packages"); + } + } + + /** + * Output commands list + * @param array $data + * @return void + */ + public function outputCommandList($data) + { + $this->writeln("Connect commands available:"); + $this->writeln("==========================="); + foreach ($data as $k=>$v) { + $this->printf ("%-20s %-20s\n", $k, $v['summary']); + } + } + + /** + * Output config + * @param array $data + * @return void + */ + public function outputConfig($data) + { + foreach($data['data'] as $name=>$row) { + $value = $row['value'] === '' ? "" : strval($row['value']); + $this->printf("%-30s %-20s %-20s\n", $row['prompt'], $name, $value); + } + } + + /** + * Output config variable + * @param string $key + * @param string $value + * @return void + */ + public function outputConfigVariable($key, $value) + { + if($value === '') { + $value = ''; + } + $this->writeln("Config variable '{$key}': {$value}"); + } + + /** + * Write data and "\n" afterwards + * @param string $data + * @return void + */ + public function writeln($data = '') + { + $this->write($data."\n"); + } + + + /** + * get output, clear if needed + * + * @param bool $clearPrevoius optional, true by default + * @return array + */ + public function getOutput($clearPrevious = true) + { + $out = $this->_output; + if($clearPrevious) { + $this->_output = array(); + } + return $out; + } + + /** + * Write data to console + * @param string $data + * @return void + */ + public function write($data) + { + if($this->isSilent()) { + return; + } + print $data; + } + + /** + * Output printf-stlye formatted string and args + * @return void + */ + public function printf() + { + $args = func_get_args(); + $this->write(call_user_func_array('sprintf', $args)); + } + + /** + * Readline from console + * @return string + */ + public function readln() + { + $out = ""; + $key = fgetc(STDIN); + while ($key!="\n") { + $out.= $key; + $key = fread(STDIN, 1); + } + return $out; + } + + /** + * Output upgrades + * @param array $data + * @return void + */ + public function outputUpgrades($data) + { + foreach($data['data'] as $chan => $packages) { + $this->writeln("Updates for ".$chan.": "); + foreach($packages as $name => $data) { + $this->writeln(" $name: {$data['from']} => {$data['to']}"); + } + } + } + +} + diff --git a/lib/Mage/Connect/Ftp.php b/lib/Mage/Connect/Ftp.php new file mode 100644 index 00000000..577ccdbd --- /dev/null +++ b/lib/Mage/Connect/Ftp.php @@ -0,0 +1,36 @@ + + */ +class Mage_Connect_Ftp extends Mage_System_Ftp +{ +} diff --git a/lib/Mage/Connect/Loader.php b/lib/Mage/Connect/Loader.php new file mode 100644 index 00000000..be0fad83 --- /dev/null +++ b/lib/Mage/Connect/Loader.php @@ -0,0 +1,51 @@ + + */ +class Mage_Connect_Loader +{ + + /** + * Factory for HTTP client + * @param string/false $protocol 'curl'/'socket' or false for auto-detect + * @return Mage_HTTP_Client/Mage_Connect_Loader_Ftp + */ + public static function getInstance($protocol='') + { + if ($protocol == 'ftp') { + return new Mage_Connect_Loader_Ftp(); + } else { + return Mage_HTTP_Client::getInstance(); + } + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Loader/Ftp.php b/lib/Mage/Connect/Loader/Ftp.php new file mode 100644 index 00000000..bc49630f --- /dev/null +++ b/lib/Mage/Connect/Loader/Ftp.php @@ -0,0 +1,121 @@ + + */ +class Mage_Connect_Loader_Ftp +{ + + const TEMPORARY_DIR = 'var/package/tmp'; + + const FTP_USER = 'magconnect'; + + const FTP_PASS = '4SyTUxPts0o2'; + + /** + * Object of Ftp + * + * @var Mage_Connect_Ftp + */ + protected $_ftp = null; + + /** + * Response body + * @var string + */ + protected $_responseBody = ''; + + /** + * Response status + * @var int + */ + protected $_responseStatus = 0; + + /** + * Constructor + */ + public function __construct() + { + $this->_ftp = new Mage_Connect_Ftp(); + } + + public function getFtp() + { + return $this->_ftp; + } + + /** + * Retrieve file from URI + * + * @param mixed $uri + * @return bool + */ + public function get($uri) + { + $remoteFile = basename($uri); + $uri = dirname($uri); + $uri = str_replace('http://', '', $uri); + $uri = str_replace('ftp://', '', $uri); + $uri = self::FTP_USER.":".self::FTP_PASS."@".$uri; + $this->getFtp()->connect("ftp://".$uri); + $this->getFtp()->pasv(true); + $localFile = self::TEMPORARY_DIR.DS.time().".xml"; + + if ($this->getFtp()->get($localFile, $remoteFile)) { + $this->_responseBody = file_get_contents($localFile); + $this->_responseStatus = 200; + } + @unlink($localFile); + $this->getFtp()->close(); + return $out; + } + + /** + * Get response status code + * + * @return string + */ + public function getStatus() + { + return $this->_responseStatus; + } + + /** + * put your comment there... + * + * @return string + */ + public function getBody() + { + return $this->_responseBody; + } + +} \ No newline at end of file diff --git a/lib/Mage/Connect/Package.php b/lib/Mage/Connect/Package.php new file mode 100644 index 00000000..7afc7249 --- /dev/null +++ b/lib/Mage/Connect/Package.php @@ -0,0 +1,1490 @@ + + */ +class Mage_Connect_Package +{ + /* + * Current version of magento connect package format + */ + const PACKAGE_VERSION_2X = '2'; + + /* + * Previous version of magento connect package format + */ + const PACKAGE_VERSION_1X = '1'; + + /** + * Contain SimpleXMLElement for composing document. + * + * @var SimpleXMLElement + */ + protected $_packageXml; + + /** + * Internal cache + * + * @var array + */ + protected $_authors; + + /** + * Internal cache + * + * @var array + */ + protected $_contents; + + /** + * Internal cache + * + * @var array + */ + protected $_hashContents; + + /** + * Internal cache + * + * @var array + */ + protected $_compatible; + + /** + * Internal cache + * + * @var array + */ + protected $_dependencyPhpExtensions; + + /** + * Internal cache + * + * @var array + */ + protected $_dependencyPackages; + + /** + * A helper object that can read from a package archive + * + * @var Mage_Connect_Package_Reader + */ + protected $_reader; + + /** + * A helper object that can create and write to a package archive + * + * @var Mage_Connect_Package_Writer + */ + protected $_writer; + + /** + * Validator object + * + * @var Mage_Connect_Validator + */ + protected $_validator = null; + + /** + * Validation errors + * + * @var array + */ + protected $_validationErrors = array(); + + /** + * Object with target + * + * @var Mage_Connect_Package_Target + */ + protected $_target = null; + + /** + * Creates a package object (empty, or from existing archive, or from package definition xml) + * + * @param null|string|resource $source + */ + public function __construct($source=null) + { + libxml_use_internal_errors(true); + + if (is_string($source)) { + // check what's in the string (a package definition or a package filename) + if (0 === strpos($source, "_init($source); + } elseif (is_file($source) && is_readable($source)) { + // package archive filename + $this->_loadFile($source); + } else { + throw new Mage_Exception('Invalid package source'); + } + } elseif (is_resource($source)) { + $this->_loadResource($source); + } elseif (is_null($source)) { + $this->_init(); + } else { + throw new Mage_Exception('Invalid package source'); + } + } + + /** + * Initializes an empty package object + * + * @param null|string $definition optional package definition xml + * @return Mage_Connect_Package + */ + protected function _init($definition=null) + { + + if (!is_null($definition)) { + $this->_packageXml = simplexml_load_string($definition); + } else { + $packageXmlStub = << + + + + + + + + + + + + +